├── .github └── FUNDING.yml ├── .gitignore ├── Dockerfile ├── License ├── README.md ├── build.gradle.kts ├── docker-entrypoint.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── run.sh ├── settings.gradle.kts └── src └── main ├── kotlin └── me │ └── kuku │ └── telegram │ ├── TelegramApplication.kt │ ├── config │ ├── KtorConfig.kt │ └── TelegramConfig.kt │ ├── context │ ├── Async.kt │ ├── Context.kt │ ├── ExceptionHandler.kt │ ├── TelegramExtensions.kt │ ├── TelegramSubscriber.kt │ └── WaitNextMessage.kt │ ├── entity │ ├── BaiduEntity.kt │ ├── BaseEntity.kt │ ├── BiliBiliEntity.kt │ ├── BotConfigEntity.kt │ ├── ConfigEntity.kt │ ├── DouYuEntity.kt │ ├── ECloudEntity.kt │ ├── HostLocEntity.kt │ ├── HuYaEntity.kt │ ├── KuGouEntity.kt │ ├── LeiShenEntity.kt │ ├── LinuxDoEntity.kt │ ├── LogEntity.kt │ ├── MiHoYoEntity.kt │ ├── NodeSeekEntity.kt │ ├── OciEntity.kt │ ├── PushEntity.kt │ ├── SmZdmEntity.kt │ ├── StepEntity.kt │ ├── SwitchEntity.kt │ └── WeiboEntity.kt │ ├── exception │ └── QrcodeException.kt │ ├── extension │ ├── ConfigExtension.kt │ ├── DeleteExtension.kt │ ├── ErrorHandler.kt │ ├── ExecExtension.kt │ ├── IndexExtension.kt │ ├── LogExtension.kt │ ├── LoginExtension.kt │ ├── ManagerExtension.kt │ ├── OciExtension.kt │ ├── OpenaiExtension.kt │ ├── OtherExtension.kt │ ├── PushExtension.kt │ ├── SettingExtension.kt │ ├── SwitchExtension.kt │ ├── ToolExtension.kt │ └── UpdateExtension.kt │ ├── ktor │ ├── KtorConfiguration.kt │ └── context │ │ ├── KtorModule.kt │ │ └── KtorRouter.kt │ ├── logic │ ├── BaiduLogic.kt │ ├── BiliBiliLogic.kt │ ├── CaptchaLogic.kt │ ├── DouYuLogic.kt │ ├── ECloudLogic.kt │ ├── HostLocLogic.kt │ ├── HuYaLogic.kt │ ├── KuGouLogic.kt │ ├── LeiShenLogic.kt │ ├── LinuxDoLogic.kt │ ├── MiHoYoLogic.kt │ ├── NodeSeekLogic.kt │ ├── OciLogic.kt │ ├── SmZdmLogic.kt │ ├── StepLogic.kt │ ├── ToolLogic.kt │ ├── V2exLogic.kt │ ├── WeiboLogic.kt │ └── YgoLogic.kt │ ├── scheduled │ ├── BaiduScheduled.kt │ ├── BiliBilliScheduled.kt │ ├── BotConfigScheduled.kt │ ├── ConfigScheduled.kt │ ├── DouYuScheduled.kt │ ├── ECloudScheduled.kt │ ├── EpicScheduled.kt │ ├── HostLocScheduled.kt │ ├── HuYaScheduled.kt │ ├── KuGouScheduled.kt │ ├── LeiShenScheduled.kt │ ├── LinuxDoScheduled.kt │ ├── MiHoYoScheduled.kt │ ├── NodeSeekScheduled.kt │ ├── SmZdmScheduled.kt │ ├── StepScheduled.kt │ ├── V2exScheduled.kt │ └── WeiboScheduled.kt │ └── utils │ ├── CacheManager.kt │ ├── EncryptUtils.kt │ ├── FuntionUtils.kt │ ├── Jackson.kt │ ├── KtorClient.kt │ ├── RandomUtils.kt │ ├── RegexUtils.kt │ └── SpringUtils.kt └── resources ├── application.yml └── image ├── 1.jpg ├── 10.jpg ├── 11.jpg ├── 12.jpg ├── 13.jpg ├── 14.jpg ├── 15.jpg ├── 16.jpg ├── 17.jpg ├── 18.jpg ├── 19.jpg ├── 2.jpg ├── 20.jpg ├── 21.jpg ├── 22.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── 8.jpg └── 9.jpg /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [kukume] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | custom: ['https://donate.stripe.com/cN2eWneTA5TD7cY007'] 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | build/ 4 | /kuku 5 | tmp/ 6 | application.pid 7 | plugins/ 8 | **/application-dev.yml 9 | logs/ 10 | config/ 11 | cache/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:21-bullseye 2 | RUN apt update -y && apt install ffmpeg -y 3 | ADD tgbot.jar /opt/kuku/tgbot.jar 4 | ADD application.yml /opt/kuku/application.yml 5 | ADD docker-entrypoint.sh /opt/kuku/docker-entrypoint.sh 6 | WORKDIR /opt/kuku 7 | ENTRYPOINT ["/opt/kuku/docker-entrypoint.sh"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TelegramBot 2 | 3 | [SpringBoot](https://spring.io/projects/spring-boot) + [Spring-Data-Mongodb-Reactive](https://spring.io/projects/spring-data-mongodb) + [Java-Telegram-Bot-Api](https://github.com/pengrad/java-telegram-bot-api) 4 | 5 | Demo:https://t.me/kukume_bot (可能不稳定) 6 | 7 | ## Environment 8 | 9 | JDK21 + Mongodb 10 | 11 | ## Commands 12 | 13 | | 指令 | 说明 | 参数 | 14 | |---------------|------------------------|--------| 15 | | /login | 登陆账号 | 无 | 16 | | /exec | 手动执行签到 | 无 | 17 | | /manager | 管理自动签到状态,默认全为关 | 无 | 18 | | /delete | 删除登陆的账号 | 无 | 19 | | /switch | 切换身份以支持多账号 | 无 | 20 | | /log | 自动签到日志 | 无 | 21 | | /oci | oracle cloud 管理 | 无 | 22 | | /config | 用户的配置和不需要登陆的推送 | 无 | 23 | | /setting | 机器人的配置(creatorId可用) | 无 | 24 | | /push | 通过http api进行消息推送 | 无 | 25 | | /ygo | 游戏王查卡 | 卡片名称 | 26 | | /update | 更新程序 | 无 | 27 | | /updatelog | github提交信息 | 无 | 28 | | /bv | 获取bv视频 | bv开头id | 29 | | /x | 获取x帖子的详情 | 链接或id | 30 | | /neteasesmall | 添加网易云小号((creatorId可用)) | 无 | 31 | 32 | ## Docker 33 | 34 | https://hub.docker.com/r/kukume/tgbot 35 | 36 | ## Jar 37 | 38 | https://pan.kuku.me/tgbot 39 | 40 | ## Config 41 | 42 | ### application.yml 43 | 44 | ```yaml 45 | kuku: 46 | telegram: 47 | # @BotFather获取到的token 48 | token: 49 | # 机器人管理员的id 50 | creatorId: 0 51 | # 代理地址 52 | proxyHost: 53 | # 代理端口 54 | proxyPort: 0 55 | # 代理类型,可选 DIRECT(不设置代理)、HTTP、SOCKS 56 | proxyType: DIRECT 57 | # 自建的api服务器的地址(包含http://或者https://),如果不填,动态推送将不能推送50M以上的视频 58 | url: 59 | # 填写自建telegram的api服务器的配置目录,该机器人程序和自建api必须在一台服务器上 60 | # 如果是https://www.kuku.me/archives/41/的搭建api,且docker-compose.yml在/root/telegram-bot-api目录下 61 | # 该参数为 /root/telegram-bot-api/data 62 | # 如果不是使用docker,该参数为 / 63 | localPath: 64 | # 填写自建api,用于无头浏览器执行的签到,见custom api项 65 | api: 66 | ``` 67 | 68 | ### docker-compose.yml 69 | 70 | ```yaml 71 | version: "3" 72 | services: 73 | tgbot: 74 | image: kukume/tgbot 75 | container_name: tgbot 76 | # 如果不需要使用http api进行消息推送,可不需要 77 | ports: 78 | - 8080:8080 79 | environment: 80 | # @BotFather获取到的token 81 | KUKU_TELEGRAM_TOKEN: 82 | # 机器人管理员的id 83 | KUKU_TELEGRAM_CREATOR_ID: 0 84 | # 代理地址 85 | KUKU_TELEGRAM_PROXY_HOST: 86 | # 代理端口 87 | KUKU_TELEGRAM_PROXY_PORT: 0 88 | # 代理类型,可选 DIRECT(不设置代理)、HTTP、SOCKS 89 | KUKU_TELEGRAM_PROXY_TYPE: DIRECT 90 | # 自建的tg服务器的地址(包含http://或者https://),如果填了, 91 | # 上传文件到机器人的功能均会失效,如果不填,动态推送将不能推送50M以上的视频 92 | KUKU_TELEGRAM_URL: 93 | # 自建的api,用于无头浏览器执行签到或者加密参数 94 | KUKU_API: 95 | depends_on: 96 | - mongo 97 | 98 | mongo: 99 | image: mongo:4 100 | volumes: 101 | - ./db:/data/db 102 | - ./dump:/dump 103 | ``` 104 | 105 | ## Custom api 106 | 107 | https://hub.docker.com/r/kukume/sk 108 | 109 | ## Message Push 110 | 111 | url: `/push` 112 | 113 | method: get or post 114 | 115 | params: key(get from commands /push) and text, parseMode (optional) 116 | 117 | ## Features 118 | 119 | * 120 | 121 | ## Log 122 | 123 | * `/info`可查看发送人的`id` ,设置`creatorId`,`/setting`中可下载日志 124 | * `/log`中可查看失败任务日志 125 | * 如果为`docker compose`安装方式,请在其目录下执行`docker-compose logs`查看日志 126 | 127 | ## LICENSE 128 | `AGPLv3` 129 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("VulnerableLibrariesLocal") 2 | 3 | plugins { 4 | val kotlinVersion = "2.1.0" 5 | val ktorVersion = "3.0.1" 6 | kotlin("jvm") version kotlinVersion 7 | kotlin("plugin.spring") version kotlinVersion 8 | id("org.springframework.boot") version "3.4.0" 9 | id("io.spring.dependency-management") version "1.1.6" 10 | id("io.ktor.plugin") version ktorVersion 11 | } 12 | 13 | group = "me.kuku" 14 | version = "1.0-SNAPSHOT" 15 | 16 | repositories { 17 | // mavenLocal() 18 | maven("https://nexus.kuku.me/repository/maven-public/") 19 | mavenCentral() 20 | } 21 | 22 | fun DependencyHandlerScope.ktor() { 23 | implementation("io.ktor:ktor-server-core") 24 | implementation("io.ktor:ktor-server-status-pages") 25 | implementation("io.ktor:ktor-server-call-logging") 26 | implementation("io.ktor:ktor-server-content-negotiation") 27 | implementation("io.ktor:ktor-server-netty") 28 | 29 | implementation("io.ktor:ktor-serialization-jackson") 30 | 31 | implementation("io.ktor:ktor-client-core") 32 | implementation("io.ktor:ktor-client-okhttp") 33 | implementation("io.ktor:ktor-client-content-negotiation") 34 | implementation("io.ktor:ktor-client-logging") 35 | } 36 | 37 | dependencies { 38 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") 39 | implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive") 40 | implementation("com.github.pengrad:java-telegram-bot-api:7.11.0") 41 | implementation("org.jsoup:jsoup:1.17.2") 42 | val ociVersion = "3.48.0" 43 | implementation("com.oracle.oci.sdk:oci-java-sdk-core:$ociVersion") 44 | implementation("com.oracle.oci.sdk:oci-java-sdk-identity:$ociVersion") 45 | implementation("com.oracle.oci.sdk:oci-java-sdk-common-httpclient-jersey3:$ociVersion") { 46 | exclude("commons-logging", "commons-logging") 47 | } 48 | annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") 49 | implementation("com.google.zxing:javase:3.5.3") 50 | implementation("com.aallam.openai:openai-client:3.8.2") 51 | ktor() 52 | testImplementation("org.springframework.boot:spring-boot-starter-test") 53 | } 54 | 55 | java { 56 | toolchain.languageVersion.set(JavaLanguageVersion.of(21)) 57 | } 58 | 59 | tasks.compileKotlin { 60 | compilerOptions { 61 | freeCompilerArgs = listOf("-Xjsr305=strict", "-Xcontext-receivers") 62 | } 63 | } 64 | 65 | tasks.compileJava { 66 | options.encoding = "utf-8" 67 | } 68 | 69 | kotlin { 70 | jvmToolchain(21) 71 | } 72 | 73 | tasks.test { 74 | useJUnitPlatform() 75 | } 76 | 77 | tasks.bootJar { 78 | archiveFileName.set("tgbot.jar") 79 | } 80 | 81 | application { 82 | mainClass.set("me.kuku.telegram.TelegramApplicationKt") 83 | } 84 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | jar_name=tgbot.jar 4 | jar_new_name=tgbot-new.jar 5 | rm -f update.pid 6 | 7 | java -Dspring.profiles.active=prod -Duser.timezone=Asia/Shanghai -jar $jar_name & 8 | 9 | function kill_java { 10 | pid=$(cat application.pid) 11 | kill "$pid" 12 | exit 1 13 | } 14 | 15 | trap "kill_java" SIGINT 16 | 17 | while true; do 18 | if [ -e "update.pid" ] ; 19 | then 20 | rm -f update.pid 21 | pid=$(cat application.pid) 22 | kill "$pid" 23 | sleep 3 24 | rm $jar_name 25 | mv tmp/$jar_new_name $jar_name 26 | java -Dspring.profiles.active=prod -Duser.timezone=Asia/Shanghai -jar $jar_name & 27 | else 28 | sleep 5 29 | fi 30 | done -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kapt.use.k2=true 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # jdk的目录 4 | JAVA_HOME=~/jdk/jdk-21+35 5 | java=$JAVA_HOME/bin/java 6 | 7 | jar_name=tgbot.jar 8 | jar_new_name=tgbot-new.jar 9 | rm -f update.pid 10 | 11 | "${java}" -Dspring.profiles.active=prod -jar $jar_name & 12 | 13 | function kill_java { 14 | pid=`cat application.pid` 15 | kill $pid 16 | exit 1 17 | } 18 | 19 | trap "kill_java" SIGINT 20 | 21 | while true; do 22 | if [ -e "update.pid" ] ; 23 | then 24 | rm -f update.pid 25 | pid=`cat application.pid` 26 | kill $pid 27 | sleep 3 28 | rm $jar_name 29 | mv tmp/$jar_new_name $jar_name 30 | "${java}" -Dspring.profiles.active=prod -jar $jar_name & 31 | else 32 | sleep 5 33 | fi 34 | done -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { url = uri("https://nexus.kuku.me/repository/maven-public/") } 4 | gradlePluginPortal() 5 | } 6 | } 7 | rootProject.name = "tgbot" 8 | 9 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/TelegramApplication.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram 2 | 3 | import org.springframework.boot.SpringApplication 4 | import org.springframework.boot.autoconfigure.SpringBootApplication 5 | import org.springframework.boot.context.ApplicationPidFileWriter 6 | import org.springframework.data.mongodb.config.EnableReactiveMongoAuditing 7 | import org.springframework.scheduling.annotation.EnableScheduling 8 | 9 | @SpringBootApplication 10 | @EnableReactiveMongoAuditing 11 | @EnableScheduling 12 | class TelegramApplication 13 | 14 | fun main(args: Array) { 15 | SpringApplication(TelegramApplication::class.java) 16 | .also { it.addListeners(ApplicationPidFileWriter()) }.run(*args) 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/config/KtorConfig.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.config 2 | 3 | import io.ktor.http.* 4 | import io.ktor.server.application.* 5 | import io.ktor.server.plugins.* 6 | import io.ktor.server.plugins.statuspages.* 7 | import io.ktor.server.response.* 8 | 9 | 10 | fun Application.statusPages() { 11 | 12 | install(StatusPages) { 13 | 14 | exception { call, cause -> 15 | call.respond( 16 | HttpStatusCode.BadRequest, 17 | mapOf("code" to 400, "message" to cause.message) 18 | ) 19 | } 20 | 21 | exception { call, cause -> 22 | call.respond(HttpStatusCode.InternalServerError, mapOf("code" to 500, "message" to cause.message)) 23 | throw cause 24 | } 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/context/Async.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.context 2 | 3 | import com.pengrad.telegrambot.Callback 4 | import com.pengrad.telegrambot.TelegramBot 5 | import com.pengrad.telegrambot.request.BaseRequest 6 | import com.pengrad.telegrambot.response.BaseResponse 7 | import kotlinx.coroutines.CompletableDeferred 8 | import java.io.IOException 9 | 10 | suspend fun , R : BaseResponse> TelegramBot.asyncExecute(request: T): R { 11 | val completableDeferred = CompletableDeferred() 12 | this.execute(request, object: Callback { 13 | override fun onResponse(request: T, response: R) { 14 | if (response.isOk) { 15 | completableDeferred.complete(response) 16 | } else { 17 | completableDeferred.completeExceptionally(IllegalStateException(response.description())) 18 | } 19 | } 20 | 21 | override fun onFailure(request: T, e: IOException) { 22 | completableDeferred.completeExceptionally(e) 23 | } 24 | }) 25 | return completableDeferred.await() 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/context/ExceptionHandler.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DuplicatedCode") 2 | 3 | package me.kuku.telegram.context 4 | 5 | import org.slf4j.LoggerFactory 6 | import kotlin.reflect.KClass 7 | import kotlin.reflect.full.isSubclassOf 8 | import kotlin.reflect.full.superclasses 9 | 10 | private val superClassCache: MutableMap, Set>> = mutableMapOf() 11 | 12 | class TelegramExceptionContext ( 13 | val throwable: R, 14 | val telegramContext: TelegramContext 15 | ) 16 | 17 | class AbilityExceptionContext ( 18 | val throwable: R, 19 | val abilityContext: AbilityContext 20 | ) 21 | 22 | private typealias AbilityBody = suspend AbilityExceptionContext.() -> Unit 23 | private typealias TelegramBody = suspend TelegramExceptionContext.() -> Unit 24 | 25 | class TelegramExceptionHandler { 26 | 27 | val abilityExceptions = mutableMapOf, MutableList>() 28 | 29 | val telegramExceptions = mutableMapOf, MutableList>() 30 | 31 | @Suppress("UNCHECKED_CAST") 32 | inline fun handler(noinline block: suspend TelegramExceptionContext.() -> Unit) { 33 | val key = T::class 34 | telegramExceptions[key] = (telegramExceptions[key] ?: mutableListOf()).also { it.add(block as TelegramBody) } 35 | } 36 | 37 | @Suppress("UNCHECKED_CAST") 38 | inline fun abilityHandler(noinline block: suspend AbilityExceptionContext.() -> Unit) { 39 | val key = T::class 40 | abilityExceptions[key] = (abilityExceptions[key] ?: mutableListOf()).also { it.add(block as AbilityBody) } 41 | } 42 | 43 | } 44 | 45 | private val logger = LoggerFactory.getLogger(TelegramExceptionHandler::class.java) 46 | 47 | suspend fun TelegramExceptionHandler.invokeHandler(telegramContext: TelegramContext, block: suspend () -> Unit) { 48 | kotlin.runCatching { 49 | block() 50 | }.onFailure { 51 | val nowThrowableClass = it::class 52 | val exceptions = this.telegramExceptions 53 | val context = TelegramExceptionContext(it, telegramContext) 54 | val throwableClassSet = superClassCache[nowThrowableClass] ?: superclasses(nowThrowableClass).also { set -> superClassCache[nowThrowableClass] = set } 55 | val newMap = exceptions.filterKeys(throwableClassSet::contains).toSortedMap { o1, o2 -> 56 | if (o1.isSubclassOf(o2)) -1 57 | else if (o2.isSubclassOf(o1)) 1 58 | else 0 59 | } 60 | if (newMap.isNotEmpty()) { 61 | val value = newMap.iterator().next().value 62 | for (func in value) { 63 | func.invoke(context) 64 | } 65 | } 66 | logger.error("Unexpected error occurred in telegram subscribe", it) 67 | // throw it 68 | } 69 | } 70 | 71 | suspend fun TelegramExceptionHandler.invokeHandler(abilityContext: AbilityContext, block: suspend () -> Unit) { 72 | kotlin.runCatching { 73 | block() 74 | }.onFailure { 75 | val nowThrowableClass = it::class 76 | val exceptions = this.abilityExceptions 77 | val context = AbilityExceptionContext(it, abilityContext) 78 | for (entry in exceptions) { 79 | val throwableClass = entry.key 80 | val throwableClassSet = superClassCache[nowThrowableClass] ?: superclasses(nowThrowableClass).also { set -> superClassCache[nowThrowableClass] = set } 81 | if (throwableClassSet.contains(throwableClass)) { 82 | for (func in entry.value) { 83 | func.invoke(context) 84 | } 85 | } 86 | } 87 | logger.error("Unexpected error occurred in telegram subscribe", it) 88 | // throw it 89 | } 90 | } 91 | 92 | fun superclasses(kClass: KClass<*>?, set: MutableSet> = mutableSetOf()): Set> { 93 | kClass?.let { set.add(it) } 94 | val superclasses = kClass?.superclasses ?: return set 95 | set.addAll(superclasses) 96 | for (superclass in superclasses) { 97 | val suSuper = superclass.superclasses 98 | if (suSuper.isNotEmpty()) { 99 | suSuper.forEach { superclasses(it, set) } 100 | } 101 | } 102 | return set 103 | } 104 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/context/TelegramExtensions.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.context 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.model.File 5 | import com.pengrad.telegrambot.model.PhotoSize 6 | import com.pengrad.telegrambot.model.request.InlineKeyboardButton 7 | import com.pengrad.telegrambot.model.request.InputMediaPhoto 8 | import com.pengrad.telegrambot.request.SendMediaGroup 9 | import com.pengrad.telegrambot.request.SendMessage 10 | import com.pengrad.telegrambot.request.SendPhoto 11 | import io.ktor.client.call.* 12 | import io.ktor.client.request.* 13 | import me.kuku.telegram.config.TelegramConfig 14 | import me.kuku.telegram.utils.SpringUtils 15 | import me.kuku.telegram.utils.client 16 | 17 | fun inlineKeyboardButton(text: String, callbackData: String): InlineKeyboardButton = InlineKeyboardButton(text).callbackData(callbackData) 18 | 19 | suspend fun TelegramBot.sendPic(tgId: Long, text: String, picUrl: List, messageThreadId: Int? = null) { 20 | if (picUrl.size == 1) { 21 | val url = picUrl[0] 22 | val bytes = client.get(url).body() 23 | val sendPhoto = SendPhoto(tgId.toString(), bytes).caption(text) 24 | messageThreadId?.let { 25 | sendPhoto.messageThreadId(it) 26 | } 27 | try { 28 | asyncExecute(sendPhoto) 29 | } catch (e: Exception) { 30 | sendPhoto.caption("") 31 | asyncExecute(sendPhoto) 32 | sendTextMessage(tgId, text, messageThreadId) 33 | } 34 | } else if (picUrl.isEmpty()) { 35 | sendTextMessage(tgId, text, messageThreadId) 36 | } else { 37 | val inputMediaList = mutableListOf() 38 | for ((i, imageUrl) in picUrl.withIndex()) { 39 | val bytes = client.get(imageUrl).body() 40 | val name = imageUrl.substring(imageUrl.lastIndexOf('/') + 1) 41 | val mediaPhoto = InputMediaPhoto(bytes).fileName(name) 42 | if (i == 0) mediaPhoto.caption(text) 43 | inputMediaList.add(mediaPhoto) 44 | } 45 | val sendMediaGroup = SendMediaGroup(tgId.toString(), *inputMediaList.toTypedArray()) 46 | messageThreadId?.let { 47 | sendMediaGroup.messageThreadId(it) 48 | } 49 | asyncExecute(sendMediaGroup) 50 | } 51 | } 52 | 53 | suspend fun TelegramBot.sendTextMessage(tgId: Long, text: String, messageThreadId: Int? = null) { 54 | val sendMessage = SendMessage(tgId, text) 55 | messageThreadId?.let { sendMessage.messageThreadId(it) } 56 | asyncExecute(sendMessage) 57 | } 58 | 59 | suspend fun File.byteArray(): ByteArray { 60 | val filePath = this.filePath() 61 | val telegramConfig = SpringUtils.getBean() 62 | return if (telegramConfig.url.isNotEmpty()) { 63 | var localPath = telegramConfig.localPath 64 | if (localPath.isEmpty()) error("获取文件失败,localPath未设置") 65 | val newPath = if (localPath == "/") filePath.substring(1) else filePath.substring(26) 66 | if (localPath.last() != '/') localPath = "$localPath/" 67 | val file = java.io.File(localPath + newPath) 68 | if (!file.exists()) error("获取文件失败,localPath设置有误") 69 | file.readBytes() 70 | } else { 71 | val url = "https://api.telegram.org/file/bot${telegramConfig.token}/$filePath" 72 | client.get(url).body() 73 | } 74 | } 75 | 76 | fun Array.max(): PhotoSize? { 77 | return this.maxByOrNull { it.fileSize() } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/context/WaitNextMessage.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.context 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.model.Message 5 | import com.pengrad.telegrambot.model.Update 6 | import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup 7 | import com.pengrad.telegrambot.request.DeleteMessage 8 | import com.pengrad.telegrambot.request.EditMessageText 9 | import kotlinx.coroutines.* 10 | import org.springframework.stereotype.Service 11 | import java.util.concurrent.ConcurrentHashMap 12 | import kotlin.coroutines.Continuation 13 | import kotlin.coroutines.resume 14 | import kotlin.coroutines.suspendCoroutine 15 | 16 | private val updateContextSessionCacheMap = ConcurrentHashMap>() 17 | 18 | private fun updateWaitNextMessageCommon(code: String, maxTime: Long): Message { 19 | return runBlocking { 20 | try { 21 | withTimeout(maxTime){ 22 | val msg = suspendCoroutine { 23 | updateContextSessionCacheMap.merge(code, it) { _, _ -> 24 | throw IllegalStateException("Account $code was still waiting.") 25 | } 26 | } 27 | msg 28 | } 29 | }catch (e: Exception){ 30 | updateContextSessionCacheMap.remove(code) 31 | error(e.message ?: "") 32 | } 33 | } 34 | } 35 | 36 | fun Update.nextMessage(maxTime: Long = 30000): Message { 37 | return updateWaitNextMessageCommon(message().chat().id().toString(), maxTime) 38 | } 39 | 40 | @Service 41 | class ContextSession( 42 | private val telegramBot: TelegramBot 43 | ) { 44 | 45 | fun Update.waitMessage() { 46 | val message = message() ?: return 47 | updateContextSessionCacheMap.remove(message.chat().id().toString())?.resume(message) 48 | } 49 | 50 | suspend fun Update.ss() { 51 | if (message() == null) return 52 | val tgId = message().from().id().toString() 53 | val value = contextSessionCacheMap[tgId] ?: return 54 | if (value.filter.invoke(message())) { 55 | contextSessionCacheMap.remove(tgId)?.let { 56 | value.continuation.resume(message()).also { 57 | val deleteMessage = DeleteMessage(message().chat().id().toString(), message().messageId()) 58 | telegramBot.asyncExecute(deleteMessage) 59 | } 60 | } 61 | } else { 62 | val deleteMessage = DeleteMessage(message().chat().id().toString(), message().messageId()) 63 | telegramBot.asyncExecute(deleteMessage) 64 | val lastMessage = value.lastMessage 65 | val editMessageText = EditMessageText(lastMessage.chatId, lastMessage.messageId, value.errMessage) 66 | .replyMarkup(lastMessage.replyMarkup) 67 | telegramBot.asyncExecute(editMessageText) 68 | } 69 | } 70 | 71 | fun Update.clear() { 72 | callbackQuery()?.data() ?: return 73 | contextSessionCacheMap.remove(callbackQuery().from().id().toString())?.continuation?.cancel() 74 | } 75 | 76 | 77 | } 78 | 79 | private data class LastMessage(val text: String, val chatId: Long, val replyMarkup: InlineKeyboardMarkup, val messageId: Int) 80 | 81 | private typealias FilterMessage = suspend Message.() -> Boolean 82 | 83 | private data class NextMessageValue(val continuation: CancellableContinuation, val errMessage: String, val lastMessage: LastMessage, val filter: FilterMessage) 84 | 85 | private val contextSessionCacheMap = ConcurrentHashMap() 86 | 87 | suspend fun TelegramContext.nextMessage(maxTime: Long = 30000, errMessage: String = "您发送的信息有误,请重新发送", 88 | waitText: String = "请稍后......", filter: FilterMessage = { true }): Message { 89 | val lastMessage = LastMessage(message.text(), chatId, 90 | InlineKeyboardMarkup(arrayOf(inlineKeyboardButton("返回", query.data()))), message.messageId()) 91 | val message = waitNextMessageCommon(tgId.toString(), maxTime, errMessage, lastMessage, filter) 92 | ?: throw CancelNextMessageException() 93 | editMessageText(waitText) 94 | return message 95 | } 96 | 97 | fun AbilityContext.nextMessage(maxTime: Long = 30000): Message { 98 | return updateWaitNextMessageCommon(message.chat().id().toString(), maxTime) 99 | } 100 | 101 | class CancelNextMessageException: RuntimeException() 102 | 103 | private suspend fun waitNextMessageCommon(code: String, maxTime: Long, errMessage: String, lastMessage: LastMessage, 104 | filter: FilterMessage 105 | ): Message? { 106 | return withContext(Dispatchers.IO) { 107 | try { 108 | withTimeout(maxTime){ 109 | val msg = suspendCancellableCoroutine { 110 | val value = NextMessageValue(it, errMessage, lastMessage, filter) 111 | contextSessionCacheMap.merge(code, value) { _, _ -> 112 | error("Account $code was still waiting.") 113 | } 114 | } 115 | msg 116 | } 117 | }catch (e: Exception){ 118 | contextSessionCacheMap.remove(code) 119 | if (e is TimeoutCancellationException) 120 | throw e 121 | null 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/BaiduEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import kotlinx.coroutines.flow.toList 4 | import org.springframework.data.annotation.Id 5 | import org.springframework.data.mongodb.core.mapping.Document 6 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 7 | import org.springframework.stereotype.Service 8 | import org.springframework.transaction.annotation.Transactional 9 | 10 | @Document("baidu") 11 | class BaiduEntity: BaseEntity() { 12 | @Id 13 | var id: String? = null 14 | var cookie: String = "" 15 | var tieBaSToken: String = "" 16 | var sign: Status = Status.OFF 17 | 18 | private fun otherCookie(sToken: String): String { 19 | return "BDUSS=.*?;".toRegex().find(cookie)!!.value + "STOKEN=$sToken; " 20 | } 21 | 22 | fun teiBaCookie(): String { 23 | return otherCookie(tieBaSToken) 24 | } 25 | } 26 | 27 | interface BaiduRepository: CoroutineCrudRepository { 28 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): BaiduEntity? 29 | suspend fun findBySign(sign: Status): List 30 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 31 | } 32 | 33 | @Service 34 | class BaiduService( 35 | private val baiduRepository: BaiduRepository 36 | ) { 37 | 38 | suspend fun findByTgId(tgId: Long) = baiduRepository.findEnableEntityByTgId(tgId) as? BaiduEntity 39 | 40 | suspend fun save(baiduEntity: BaiduEntity) = baiduRepository.save(baiduEntity) 41 | 42 | suspend fun findBySign(sign: Status): List = baiduRepository.findBySign(sign) 43 | 44 | suspend fun findAll(): List = baiduRepository.findAll().toList() 45 | 46 | @Transactional 47 | suspend fun deleteByTgId(tgId: Long) = baiduRepository.deleteEnableEntityByTgId(tgId) 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/BaseEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat 4 | import kotlinx.coroutines.reactive.awaitFirst 5 | import me.kuku.telegram.context.AbilityContext 6 | import me.kuku.telegram.context.TelegramContext 7 | import me.kuku.telegram.utils.SpringUtils 8 | import org.springframework.aop.framework.AopProxyUtils 9 | import org.springframework.data.annotation.CreatedDate 10 | import org.springframework.data.annotation.LastModifiedDate 11 | import org.springframework.data.mongodb.core.index.Indexed 12 | import org.springframework.data.repository.NoRepositoryBean 13 | import org.springframework.data.repository.Repository 14 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 15 | import reactor.core.publisher.Flux 16 | import java.time.LocalDateTime 17 | import kotlin.reflect.full.callSuspend 18 | import kotlin.reflect.full.declaredFunctions 19 | import kotlin.reflect.full.functions 20 | 21 | open class BaseEntity { 22 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 23 | @CreatedDate 24 | var createTime: LocalDateTime = LocalDateTime.now() 25 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 26 | @LastModifiedDate 27 | var updateTime: LocalDateTime = LocalDateTime.now() 28 | @Indexed(name = "tgId") 29 | open var tgId: Long = 0 30 | var tgName: String? = null 31 | 32 | context(TelegramContext) 33 | suspend inline fun init(): T{ 34 | this@BaseEntity.tgId = this@TelegramContext.tgId 35 | this@BaseEntity.tgName = tgId.tgName() 36 | return this as T 37 | } 38 | 39 | context(AbilityContext) 40 | suspend inline fun init(): T{ 41 | this@BaseEntity.tgId = this@AbilityContext.tgId 42 | this@BaseEntity.tgName = tgId.tgName() 43 | return this as T 44 | } 45 | 46 | } 47 | 48 | enum class Status { 49 | OFF, ON; 50 | 51 | fun str() : String{ 52 | return if (this.name == "ON") "√" else "×" 53 | } 54 | 55 | fun reverse(): Status { 56 | return if (this.name == "ON") OFF else ON 57 | } 58 | 59 | operator fun not(): Status { 60 | return this.reverse() 61 | } 62 | 63 | override fun toString(): String { 64 | return str() 65 | } 66 | } 67 | 68 | private val switchRepository by lazy { 69 | SpringUtils.getBean() 70 | } 71 | 72 | suspend fun Long.tgName(): String? { 73 | val findList = switchRepository.findByTgIdAndStatus(this, Status.ON) 74 | return if (findList.isEmpty()) null 75 | else findList.first().name 76 | } 77 | 78 | suspend fun Repository.findEnableEntityByTgId(tgId: Long): BaseEntity? { 79 | val clazz = AopProxyUtils.proxiedUserInterfaces(this)[0].kotlin 80 | val name = tgId.tgName() 81 | val function = clazz.functions.find { it.name == "findByTgIdAndTgName" } 82 | ?: error("当前身份是${name ?: "主身份"},但是该功能没有提供多账号查询函数") 83 | val result = function.callSuspend(this, tgId, name) 84 | return if (result is Flux<*>) { 85 | result.collectList().awaitFirst().firstOrNull() as? BaseEntity 86 | } else { 87 | result as? BaseEntity 88 | } 89 | } 90 | 91 | suspend fun Repository.deleteEnableEntityByTgId(tgId: Long) { 92 | val clazz = AopProxyUtils.proxiedUserInterfaces(this)[0].kotlin 93 | val name = tgId.tgName() 94 | val function = clazz.functions.find { it.name == "deleteByTgIdAndTgName" } 95 | ?: error("当前身份是${name ?: "主身份"},但是该功能没有提供多账号查询函数") 96 | function.callSuspend(this, tgId, name) 97 | } 98 | 99 | @NoRepositoryBean 100 | interface CoroutineCrudMultipleRepository: CoroutineCrudRepository { 101 | 102 | fun findByTgIdAndTgName(tgId: Long, tgName: String?): T? 103 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 104 | 105 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/BiliBiliEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import kotlinx.coroutines.flow.toList 4 | import org.springframework.data.annotation.Id 5 | import org.springframework.data.mongodb.core.mapping.Document 6 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 7 | import org.springframework.stereotype.Service 8 | import org.springframework.transaction.annotation.Transactional 9 | 10 | @Document("bili_bili") 11 | class BiliBiliEntity: BaseEntity() { 12 | @Id 13 | var id: String? = null 14 | var cookie: String = "" 15 | var userid: String = "" 16 | var token: String = "" 17 | var push: Status = Status.OFF 18 | var sign: Status = Status.OFF 19 | var live: Status = Status.OFF 20 | // var coin: Status = Status.OFF 21 | } 22 | 23 | interface BiliBiliRepository: CoroutineCrudRepository { 24 | 25 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): BiliBiliEntity? 26 | 27 | suspend fun findByPush(push: Status): List 28 | 29 | suspend fun findBySign(sign: Status): List 30 | 31 | suspend fun findByLive(live: Status): List 32 | 33 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 34 | 35 | } 36 | 37 | @Service 38 | class BiliBiliService( 39 | private val biliBiliRepository: BiliBiliRepository 40 | ) { 41 | 42 | suspend fun findByTgId(tgId: Long) = biliBiliRepository.findEnableEntityByTgId(tgId) as? BiliBiliEntity 43 | 44 | suspend fun findByPush(push: Status): List = biliBiliRepository.findByPush(push) 45 | 46 | suspend fun findBySign(sign: Status): List = biliBiliRepository.findBySign(sign) 47 | 48 | suspend fun findByLive(live: Status): List = biliBiliRepository.findByLive(live) 49 | 50 | suspend fun save(biliEntity: BiliBiliEntity) = biliBiliRepository.save(biliEntity) 51 | 52 | suspend fun findAll(): List = biliBiliRepository.findAll().toList() 53 | 54 | @Transactional 55 | suspend fun deleteByTgId(tgId: Long) = biliBiliRepository.deleteEnableEntityByTgId(tgId) 56 | 57 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/BotConfigEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import me.kuku.telegram.config.TelegramConfig 4 | import org.springframework.data.annotation.Id 5 | import org.springframework.data.mongodb.core.index.Indexed 6 | import org.springframework.data.mongodb.core.mapping.Document 7 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 8 | import org.springframework.stereotype.Service 9 | 10 | @Document("bot_config") 11 | class BotConfigEntity { 12 | @Id 13 | var id: String? = null 14 | @Indexed(unique = true) 15 | var token: String = "" 16 | var blacklist: MutableList = mutableListOf() 17 | var admins: MutableList = mutableListOf() 18 | var pushUrl: String = "" 19 | var twoCaptchaKey: String = "" 20 | 21 | var updatePush: Status = Status.OFF 22 | 23 | var openaiToken: String = "" 24 | var openaiUrl: String = "" 25 | var openaiModel: String = "" 26 | 27 | fun twoCaptchaKey() = twoCaptchaKey.ifEmpty { null } 28 | } 29 | 30 | 31 | interface BotConfigRepository: CoroutineCrudRepository { 32 | 33 | suspend fun findByToken(token: String): BotConfigEntity? 34 | 35 | } 36 | 37 | @Service 38 | class BotConfigService( 39 | private val botConfigRepository: BotConfigRepository, 40 | private val telegramConfig: TelegramConfig 41 | ) { 42 | 43 | suspend fun findByToken(token: String) = botConfigRepository.findByToken(token) 44 | 45 | suspend fun save(entity: BotConfigEntity) = botConfigRepository.save(entity) 46 | 47 | suspend fun find() = init() 48 | 49 | suspend fun init(): BotConfigEntity { 50 | val token = telegramConfig.token 51 | return findByToken(token) ?: kotlin.run { 52 | val botConfigEntity = BotConfigEntity() 53 | botConfigEntity.token = token 54 | botConfigEntity.also { save(botConfigEntity) } 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/ConfigEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import kotlinx.coroutines.flow.toList 4 | import org.springframework.data.annotation.Id 5 | import org.springframework.data.mongodb.core.mapping.Document 6 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 7 | import org.springframework.stereotype.Service 8 | import org.springframework.transaction.annotation.Transactional 9 | 10 | @Document("config") 11 | class ConfigEntity: BaseEntity() { 12 | @Id 13 | var id: String? = null 14 | // 正能量推送 15 | var positiveEnergy: Status = Status.OFF 16 | var twoCaptchaKey: String = "" 17 | var v2exPush: Status = Status.OFF 18 | var xianBaoPush: Status = Status.OFF // http://new.xianbao.fun/ 19 | var epicFreeGamePush: Status = Status.OFF 20 | 21 | fun twoCaptchaKey() = twoCaptchaKey.ifEmpty { null } 22 | } 23 | 24 | interface ConfigRepository: CoroutineCrudRepository { 25 | suspend fun findByTgId(tgId: Long): List 26 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): ConfigEntity? 27 | suspend fun findByPositiveEnergy(positiveEnergy: Status): List 28 | suspend fun findByV2exPush(v2exPush: Status): List 29 | suspend fun findByXianBaoPush(push: Status): List 30 | suspend fun findByEpicFreeGamePush(push: Status): List 31 | 32 | } 33 | 34 | @Service 35 | class ConfigService( 36 | private val configRepository: ConfigRepository 37 | ) { 38 | 39 | suspend fun save(configEntity: ConfigEntity): ConfigEntity = configRepository.save(configEntity) 40 | 41 | @Transactional 42 | suspend fun findByTgId(tgId: Long): ConfigEntity? { 43 | val list = configRepository.findByTgId(tgId) 44 | if (list.size > 1) { 45 | val subList = list.subList(1, list.size) 46 | configRepository.deleteAll(subList) 47 | } 48 | return list.firstOrNull() 49 | } 50 | 51 | suspend fun findAll(): List = configRepository.findAll().toList() 52 | 53 | suspend fun findByPositiveEnergy(positiveEnergy: Status): List = configRepository.findByPositiveEnergy(positiveEnergy) 54 | suspend fun findByV2exPush(v2exPush: Status): List = configRepository.findByV2exPush(v2exPush) 55 | suspend fun findByXianBaoPush(push: Status): List = configRepository.findByXianBaoPush(push) 56 | 57 | suspend fun findByEpicFreeGamePush(push: Status) = configRepository.findByEpicFreeGamePush(push) 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/DouYuEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import kotlinx.coroutines.flow.toList 4 | import org.springframework.data.annotation.Id 5 | import org.springframework.data.mongodb.core.mapping.Document 6 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 7 | import org.springframework.stereotype.Service 8 | import org.springframework.transaction.annotation.Transactional 9 | 10 | @Document("dou_yu") 11 | class DouYuEntity: BaseEntity() { 12 | @Id 13 | var id: String? = null 14 | var cookie: String = "" 15 | var live: Status = Status.OFF 16 | var fishGroup: Status = Status.OFF 17 | var push: Status = Status.OFF 18 | var titleChange: Status = Status.OFF 19 | } 20 | 21 | interface DouYuRepository: CoroutineCrudRepository { 22 | 23 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): DouYuEntity? 24 | 25 | suspend fun findByLive(live: Status): List 26 | 27 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 28 | 29 | suspend fun findByFishGroup(fishGroup: Status): List 30 | 31 | suspend fun findByPush(push: Status): List 32 | 33 | suspend fun findByTitleChange(titleChange: Status): List 34 | 35 | } 36 | 37 | @Service 38 | class DouYuService( 39 | private val douYuRepository: DouYuRepository 40 | ) { 41 | suspend fun findByLive(live: Status): List = douYuRepository.findByLive(live) 42 | 43 | suspend fun save(douYuEntity: DouYuEntity) = douYuRepository.save(douYuEntity) 44 | 45 | suspend fun findByTgId(tgId: Long) = douYuRepository.findEnableEntityByTgId(tgId) as? DouYuEntity 46 | 47 | suspend fun findAll(): List = douYuRepository.findAll().toList() 48 | 49 | @Transactional 50 | suspend fun deleteByTgId(tgId: Long) = douYuRepository.deleteEnableEntityByTgId(tgId) 51 | 52 | suspend fun findByFishGroup(fishGroup: Status): List = douYuRepository.findByFishGroup(fishGroup) 53 | 54 | suspend fun findByPush(push: Status): List = douYuRepository.findByPush(push) 55 | 56 | suspend fun findByTitleChange(titleChange: Status): List = douYuRepository.findByTitleChange(titleChange) 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/ECloudEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import org.springframework.data.annotation.Id 4 | import org.springframework.data.mongodb.core.mapping.Document 5 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 6 | import org.springframework.stereotype.Service 7 | 8 | @Document("e_cloud") 9 | class ECloudEntity: BaseEntity() { 10 | @Id 11 | var id: String? = null 12 | var cookie: String = "" 13 | var eCookie: String = "" 14 | var sign: Status = Status.OFF 15 | } 16 | 17 | interface ECloudRepository: CoroutineCrudRepository { 18 | suspend fun findBySign(sign: Status): List 19 | 20 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): ECloudEntity? 21 | 22 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 23 | } 24 | 25 | @Service 26 | class ECloudService( 27 | private val eCloudRepository: ECloudRepository 28 | ) { 29 | 30 | suspend fun findBySign(sign: Status) = eCloudRepository.findBySign(sign) 31 | 32 | suspend fun save(entity: ECloudEntity) = eCloudRepository.save(entity) 33 | 34 | suspend fun findByTgId(tgId: Long) = eCloudRepository.findEnableEntityByTgId(tgId) as? ECloudEntity 35 | 36 | suspend fun deleteByTgId(tgId: Long) = eCloudRepository.deleteEnableEntityByTgId(tgId) 37 | 38 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/HostLocEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import kotlinx.coroutines.flow.toList 4 | import org.springframework.data.annotation.Id 5 | import org.springframework.data.mongodb.core.mapping.Document 6 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 7 | import org.springframework.stereotype.Service 8 | import org.springframework.transaction.annotation.Transactional 9 | 10 | @Document("host_loc") 11 | class HostLocEntity: BaseEntity() { 12 | @Id 13 | var id: String? = null 14 | var cookie: String = "" 15 | var push: Status = Status.OFF 16 | var sign: Status = Status.OFF 17 | } 18 | 19 | interface HostLocRepository: CoroutineCrudRepository { 20 | 21 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): HostLocEntity? 22 | 23 | suspend fun findByPush(push: Status): List 24 | 25 | suspend fun findBySign(sign: Status): List 26 | 27 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 28 | 29 | } 30 | 31 | @Service 32 | class HostLocService( 33 | private val hostLocRepository: HostLocRepository 34 | ) { 35 | suspend fun findByTgId(tgId: Long) = hostLocRepository.findEnableEntityByTgId(tgId) as? HostLocEntity 36 | 37 | suspend fun findByPush(push: Status): List = hostLocRepository.findByPush(push) 38 | 39 | suspend fun findBySign(sign: Status): List = hostLocRepository.findBySign(sign) 40 | 41 | suspend fun save(hostLocEntity: HostLocEntity): HostLocEntity = hostLocRepository.save(hostLocEntity) 42 | 43 | suspend fun findAll(): List = hostLocRepository.findAll().toList() 44 | 45 | @Transactional 46 | suspend fun deleteByTgId(tgId: Long) = hostLocRepository.deleteEnableEntityByTgId(tgId) 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/HuYaEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import kotlinx.coroutines.flow.toList 4 | import org.springframework.data.mongodb.core.mapping.Document 5 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 6 | import org.springframework.stereotype.Service 7 | import org.springframework.transaction.annotation.Transactional 8 | 9 | @Document("hu_ya") 10 | class HuYaEntity: BaseEntity() { 11 | var id: String? = null 12 | var cookie: String = "" 13 | var live: Status = Status.OFF 14 | } 15 | 16 | interface HuYaRepository: CoroutineCrudRepository { 17 | 18 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): HuYaEntity? 19 | 20 | suspend fun findByLive(live: Status): List 21 | 22 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 23 | 24 | } 25 | 26 | @Service 27 | class HuYaService( 28 | private val huYaRepository: HuYaRepository 29 | ) { 30 | 31 | suspend fun findByTgId(tgId: Long) = huYaRepository.findEnableEntityByTgId(tgId) as? HuYaEntity 32 | 33 | suspend fun findByLive(live: Status): List = huYaRepository.findByLive(live) 34 | 35 | suspend fun save(huYaEntity: HuYaEntity): HuYaEntity = huYaRepository.save(huYaEntity) 36 | 37 | suspend fun findAll(): List = huYaRepository.findAll().toList() 38 | 39 | @Transactional 40 | suspend fun deleteByTgId(tgId: Long) = huYaRepository.deleteEnableEntityByTgId(tgId) 41 | 42 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/KuGouEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import kotlinx.coroutines.flow.toList 4 | import org.springframework.data.annotation.Id 5 | import org.springframework.data.mongodb.core.mapping.Document 6 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 7 | import org.springframework.stereotype.Service 8 | import org.springframework.transaction.annotation.Transactional 9 | 10 | @Document("ku_gou") 11 | class KuGouEntity: BaseEntity() { 12 | @Id 13 | var id: String? = null 14 | var token: String = "" 15 | var userid: Long = 0 16 | var kuGoo: String = "" 17 | var mid: String = "" 18 | var sign: Status = Status.OFF 19 | } 20 | 21 | interface KuGouRepository: CoroutineCrudRepository { 22 | 23 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): KuGouEntity? 24 | 25 | suspend fun findBySign(sign: Status): List 26 | 27 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 28 | 29 | } 30 | 31 | @Service 32 | class KuGouService( 33 | private val kuGouRepository: KuGouRepository 34 | ) { 35 | 36 | suspend fun findByTgId(tgId: Long) = kuGouRepository.findEnableEntityByTgId(tgId) as? KuGouEntity 37 | 38 | suspend fun findBySign(sign: Status): List = kuGouRepository.findBySign(sign) 39 | 40 | suspend fun save(kuGouEntity: KuGouEntity): KuGouEntity = kuGouRepository.save(kuGouEntity) 41 | 42 | suspend fun findAll(): List = kuGouRepository.findAll().toList() 43 | 44 | @Transactional 45 | suspend fun deleteByTgId(tgId: Long) = kuGouRepository.deleteEnableEntityByTgId(tgId) 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/LeiShenEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import org.springframework.data.annotation.Id 4 | import org.springframework.data.mongodb.core.mapping.Document 5 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 6 | import org.springframework.stereotype.Service 7 | import org.springframework.transaction.annotation.Transactional 8 | 9 | @Document("lei_shen") 10 | class LeiShenEntity: BaseEntity() { 11 | @Id 12 | var id: String? = null 13 | var username: String = "" 14 | var password: String = "" 15 | var accountToken: String = "" 16 | var nnToken: String = "" 17 | var status: Status = Status.OFF 18 | var expiryTime: Long = 0 19 | } 20 | 21 | interface LeiShenRepository: CoroutineCrudRepository { 22 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): LeiShenEntity? 23 | 24 | suspend fun findByStatus(status: Status): List 25 | 26 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 27 | } 28 | 29 | @Service 30 | class LeiShenService( 31 | private val leiShenRepository: LeiShenRepository 32 | ) { 33 | 34 | suspend fun findByTgId(tgId: Long) = leiShenRepository.findEnableEntityByTgId(tgId) as? LeiShenEntity 35 | 36 | suspend fun findByStatus(status: Status) = leiShenRepository.findByStatus(status) 37 | 38 | @Transactional 39 | suspend fun deleteByTgId(tgId: Long) = leiShenRepository.deleteEnableEntityByTgId(tgId) 40 | 41 | suspend fun save(entity: LeiShenEntity) = leiShenRepository.save(entity) 42 | 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/LinuxDoEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import org.springframework.data.mongodb.core.mapping.Document 4 | import org.springframework.stereotype.Service 5 | import org.springframework.transaction.annotation.Transactional 6 | 7 | @Document("linux_do") 8 | class LinuxDoEntity: BaseEntity() { 9 | var id: String? = null 10 | var cookie: String = "" 11 | var sign: Status = Status.OFF 12 | } 13 | 14 | interface LinuxDoRepository: CoroutineCrudMultipleRepository { 15 | suspend fun findBySign(sign: Status): List 16 | } 17 | 18 | @Service 19 | class LinuxDoService( 20 | private val linuxDoRepository: LinuxDoRepository 21 | ) { 22 | 23 | suspend fun findByTgId(tgId: Long) = linuxDoRepository.findEnableEntityByTgId(tgId) as? LinuxDoEntity 24 | 25 | suspend fun save(linuxDoEntity: LinuxDoEntity) = linuxDoRepository.save(linuxDoEntity) 26 | 27 | @Transactional 28 | suspend fun deleteByTgId(tgId: Long) = linuxDoRepository.deleteEnableEntityByTgId(tgId) 29 | 30 | suspend fun findBySign(sign: Status) = linuxDoRepository.findBySign(sign) 31 | 32 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/LogEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.request.SendMessage 5 | import me.kuku.telegram.context.asyncExecute 6 | import me.kuku.telegram.utils.SpringUtils 7 | import org.springframework.data.mongodb.core.mapping.Document 8 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 9 | import org.springframework.stereotype.Service 10 | import java.time.LocalDateTime 11 | 12 | @Document("log") 13 | class LogEntity: BaseEntity() { 14 | var id: String? = null 15 | var type: LogType = LogType.None 16 | var text: String = "成功" 17 | var show: String = "" 18 | var errReason: String = "" 19 | var exceptionStack: String = "" 20 | 21 | suspend fun sendFailMessage(message: String? = null) { 22 | val telegramBot = SpringUtils.getBean() 23 | val sendMessage = SendMessage(tgId, "#自动签到失败提醒\n${type.value}执行失败,${message ?: "未知异常原因,请重新执行指令以查看原因"}") 24 | telegramBot.asyncExecute(sendMessage) 25 | } 26 | 27 | fun success() = text.contains("成功") 28 | } 29 | 30 | 31 | enum class LogType(val value: String) { 32 | None("无"), 33 | Baidu("百度"), 34 | BiliBili("哔哩哔哩"), 35 | HostLoc("HostLoc"), 36 | KuGou("酷狗"), 37 | GenShin("原神"), 38 | Mys("米游社"), 39 | NetEase("网易云"), 40 | NetEaseMusician("网易云音乐人"), 41 | NetEaseVip("网易云音乐vip"), 42 | Step("修改步数"), 43 | Weibo("微博"), 44 | DouYu("斗鱼"), 45 | SmZdm("什么值得买"), 46 | AliDrive("阿里云盘"), 47 | AliDriver("阿里云盘"), 48 | AliDriveTask("阿里云盘任务"), 49 | AliDriveReceiveTaskToday("阿里云盘领取当日任务奖励"), 50 | ALiDriveReceive("阿里云盘月末领取签到奖励"), 51 | ALiDriveReceiveTask("阿里云盘月末领取任务奖励"), 52 | AliDriveDeviceRoom("阿里云盘时光设备间"), 53 | AliDriveCard("阿里云盘领取补签卡"), 54 | NodeSeek("NodeSeek"), 55 | GlaDos("GlaDos"), 56 | Iqy("爱奇艺"), 57 | ECloud("天翼云盘") 58 | } 59 | 60 | interface LogRepository: CoroutineCrudRepository { 61 | suspend fun findByTgId(tgId: Long): List 62 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): List 63 | suspend fun findByType(logType: LogType): List 64 | suspend fun findByCreateTimeBetween(before: LocalDateTime, after: LocalDateTime): List 65 | 66 | suspend fun findByCreateTimeBetweenAndTgId(before: LocalDateTime, after: LocalDateTime, tgId: Long): List 67 | 68 | suspend fun findByCreateTimeBetweenAndTgIdAndTgName(before: LocalDateTime, after: LocalDateTime, tgId: Long, tgName: String?): List 69 | } 70 | 71 | @Service 72 | class LogService( 73 | private val logRepository: LogRepository 74 | ) { 75 | 76 | suspend fun findByTgId(tgId: Long): List = logRepository.findByTgId(tgId) 77 | 78 | suspend fun findByType(logType: LogType): List = logRepository.findByType(logType) 79 | 80 | suspend fun save(logEntity: LogEntity): LogEntity = logRepository.save(logEntity) 81 | 82 | suspend fun findByCreateTimeBetween(before: LocalDateTime, after: LocalDateTime): List = logRepository.findByCreateTimeBetween(before, after) 83 | 84 | suspend fun findByCreateTimeBetweenAndTgId(before: LocalDateTime, after: LocalDateTime, tgId: Long): List = 85 | logRepository.findByCreateTimeBetweenAndTgIdAndTgName(before, after, tgId, tgId.tgName()) 86 | 87 | suspend fun findById(id: String) = logRepository.findById(id) 88 | 89 | suspend fun log(entity: BaseEntity, type: LogType, block: suspend LogEntity.() -> Unit) { 90 | val logEntity = LogEntity().also { 91 | it.tgId = entity.tgId 92 | it.tgName = entity.tgName 93 | it.type = type 94 | } 95 | kotlin.runCatching { 96 | block(logEntity) 97 | }.onFailure { 98 | logEntity.text = "失败" 99 | logEntity.errReason = it.message ?: "未知异常原因" 100 | logEntity.exceptionStack = it.stackTraceToString() 101 | logEntity.sendFailMessage(it.message) 102 | } 103 | save(logEntity) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/MiHoYoEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import kotlinx.coroutines.flow.toList 4 | import me.kuku.telegram.logic.MiHoYoFix 5 | import org.springframework.data.annotation.Id 6 | import org.springframework.data.mongodb.core.mapping.Document 7 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 8 | import org.springframework.stereotype.Service 9 | import org.springframework.transaction.annotation.Transactional 10 | 11 | @Document("mi_ho_yo") 12 | class MiHoYoEntity: BaseEntity() { 13 | @Id 14 | var id: String? = null 15 | var fix: MiHoYoFix = MiHoYoFix() 16 | var aid: String = "" 17 | var mid: String = "" 18 | var cookie: String = "" 19 | var token: String = "" 20 | var sToken: String = "" 21 | var ticket: String = "" 22 | var sign: Status = Status.OFF 23 | var mysSign: Status = Status.OFF 24 | 25 | fun hubCookie(): String { 26 | if (token.isEmpty()) error("未设置token,请使用app账号密码重新登录") 27 | return "stuid=$aid; stoken=$token; mid=$mid; " 28 | } 29 | } 30 | 31 | interface MiHoYoRepository: CoroutineCrudRepository { 32 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): MiHoYoEntity? 33 | 34 | suspend fun findBySign(sign: Status): List 35 | 36 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 37 | 38 | suspend fun findByMysSign(sign: Status): List 39 | 40 | } 41 | 42 | @Service 43 | class MiHoYoService( 44 | private val miHoYoRepository: MiHoYoRepository 45 | ) { 46 | 47 | suspend fun findByTgId(tgId: Long) = miHoYoRepository.findEnableEntityByTgId(tgId) as? MiHoYoEntity 48 | 49 | suspend fun findBySign(sign: Status): List = miHoYoRepository.findBySign(sign) 50 | 51 | suspend fun findByMysSign(sign: Status): List = miHoYoRepository.findByMysSign(sign) 52 | 53 | suspend fun save(miHoYoEntity: MiHoYoEntity): MiHoYoEntity = miHoYoRepository.save(miHoYoEntity) 54 | 55 | suspend fun findAll(): List = miHoYoRepository.findAll().toList() 56 | 57 | @Transactional 58 | suspend fun deleteByTgId(tgId: Long) = miHoYoRepository.deleteEnableEntityByTgId(tgId) 59 | 60 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/NodeSeekEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import kotlinx.coroutines.flow.toList 4 | import org.springframework.data.annotation.Id 5 | import org.springframework.data.mongodb.core.mapping.Document 6 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 7 | import org.springframework.stereotype.Service 8 | 9 | @Document("node_seek") 10 | class NodeSeekEntity: BaseEntity() { 11 | @Id 12 | var id: String? = null 13 | var cookie: String = "" 14 | var sign: Sign = Sign.None 15 | 16 | enum class Sign(val value: String) { 17 | Random("随机"), Fix("固定"), None("关闭") 18 | } 19 | 20 | } 21 | 22 | interface NodeSeekRepository: CoroutineCrudRepository { 23 | 24 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): NodeSeekEntity? 25 | 26 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 27 | 28 | } 29 | 30 | @Service 31 | class NodeSeekService( 32 | private val nodeSeekRepository: NodeSeekRepository 33 | ) { 34 | 35 | suspend fun save(entity: NodeSeekEntity) = nodeSeekRepository.save(entity) 36 | 37 | suspend fun findByTgId(tgId: Long) = nodeSeekRepository.findEnableEntityByTgId(tgId) as? NodeSeekEntity 38 | 39 | suspend fun deleteByTgId(tgId: Long) = nodeSeekRepository.deleteEnableEntityByTgId(tgId) 40 | 41 | suspend fun findAll() = nodeSeekRepository.findAll().toList() 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/OciEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import kotlinx.coroutines.flow.toList 4 | import org.springframework.data.annotation.Id 5 | import org.springframework.data.mongodb.core.index.Indexed 6 | import org.springframework.data.mongodb.core.mapping.Document 7 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 8 | import org.springframework.stereotype.Service 9 | import java.io.Serializable 10 | 11 | @Suppress("ConstPropertyName") 12 | @Document("oci") 13 | class OciEntity: Serializable { 14 | companion object { 15 | private const val serialVersionUID = 1L 16 | } 17 | @Id 18 | var id: String? = null 19 | var tgId: Long = 0 20 | var tenantId: String = "" 21 | @Indexed(unique = true) 22 | var remark: String = "" 23 | var userid: String = "" 24 | var fingerprint: String = "" 25 | var privateKey: String = "" 26 | var region: String = "" 27 | } 28 | 29 | class OciTask 30 | 31 | interface OciRepository: CoroutineCrudRepository { 32 | 33 | suspend fun findByTgId(tgId: Long): List 34 | 35 | suspend fun findByRemarkAndTgId(remark: String, tgId: Long): OciEntity? 36 | 37 | } 38 | 39 | @Service 40 | class OciService( 41 | private val ociRepository: OciRepository 42 | ) { 43 | 44 | suspend fun findByTgId(tgId: Long) = ociRepository.findByTgId(tgId) 45 | 46 | suspend fun save(entity: OciEntity) = ociRepository.save(entity) 47 | 48 | suspend fun deleteById(id: String) = ociRepository.deleteById(id) 49 | 50 | suspend fun findById(id: String) = ociRepository.findById(id) 51 | 52 | suspend fun findAll() = ociRepository.findAll().toList() 53 | 54 | suspend fun checkRemark(remark: String, tgId: Long): Boolean { 55 | val entity = ociRepository.findByRemarkAndTgId(remark, tgId) 56 | return entity == null || entity.remark == remark 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/PushEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import org.springframework.data.annotation.Id 4 | import org.springframework.data.mongodb.core.index.Indexed 5 | import org.springframework.data.mongodb.core.mapping.Document 6 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 7 | import org.springframework.stereotype.Service 8 | 9 | @Document("push") 10 | class PushEntity { 11 | @Id 12 | var id: String? = null 13 | @Indexed(unique = true) 14 | var tgId: Long = 0 15 | @Indexed(unique = true) 16 | var key: String = "" 17 | } 18 | 19 | interface PushRepository: CoroutineCrudRepository { 20 | suspend fun findByTgId(tgId: Long): PushEntity? 21 | 22 | suspend fun findByKey(key: String): PushEntity? 23 | } 24 | 25 | @Service 26 | class PushService( 27 | private val pushRepository: PushRepository 28 | ) { 29 | 30 | suspend fun save(entity: PushEntity) = pushRepository.save(entity) 31 | 32 | suspend fun findByTgId(tgId: Long) = pushRepository.findByTgId(tgId) 33 | 34 | suspend fun findByKey(key: String) = pushRepository.findByKey(key) 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/SmZdmEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import org.springframework.data.annotation.Id 4 | import org.springframework.data.mongodb.core.mapping.Document 5 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 6 | import org.springframework.stereotype.Service 7 | 8 | @Document("sm_zdm") 9 | class SmZdmEntity: BaseEntity() { 10 | @Id 11 | var id: String? = null 12 | var cookie: String = "" 13 | var sign: Status = Status.OFF 14 | } 15 | 16 | interface SmZdmRepository: CoroutineCrudRepository { 17 | 18 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): SmZdmEntity? 19 | 20 | suspend fun findBySign(sign: Status): List 21 | 22 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 23 | 24 | } 25 | 26 | @Service 27 | class SmZdmService( 28 | private val smZdmRepository: SmZdmRepository 29 | ) { 30 | 31 | suspend fun findByTgId(tgId: Long) = smZdmRepository.findEnableEntityByTgId(tgId) as? SmZdmEntity 32 | 33 | suspend fun save(smZdmEntity: SmZdmEntity) = smZdmRepository.save(smZdmEntity) 34 | 35 | suspend fun delete(smZdmEntity: SmZdmEntity) = smZdmRepository.delete(smZdmEntity) 36 | 37 | suspend fun findBySign(sign: Status) = smZdmRepository.findBySign(sign) 38 | 39 | suspend fun deleteByTgId(tgId: Long) = smZdmRepository.deleteEnableEntityByTgId(tgId) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/StepEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import org.springframework.data.annotation.Id 4 | import org.springframework.data.mongodb.core.mapping.Document 5 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 6 | import org.springframework.stereotype.Service 7 | import org.springframework.transaction.annotation.Transactional 8 | 9 | @Document("step") 10 | class StepEntity: BaseEntity() { 11 | @Id 12 | var id: String? = null 13 | var leXinCookie: String = "" 14 | var leXinUserid: String = "" 15 | var leXinAccessToken: String = "" 16 | var miLoginToken: String = "" 17 | var step: Int = -1 18 | var offset: Status = Status.OFF 19 | } 20 | 21 | interface StepRepository: CoroutineCrudRepository { 22 | 23 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): StepEntity? 24 | 25 | suspend fun findByStepIsGreaterThan(step: Int): List 26 | 27 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 28 | 29 | } 30 | 31 | @Service 32 | class StepService( 33 | private val stepRepository: StepRepository 34 | ) { 35 | 36 | suspend fun findByTgId(tgId: Long) = stepRepository.findEnableEntityByTgId(tgId) as? StepEntity 37 | 38 | suspend fun findByAuto(): List = stepRepository.findByStepIsGreaterThan(0) 39 | 40 | suspend fun save(stepEntity: StepEntity): StepEntity = stepRepository.save(stepEntity) 41 | 42 | @Transactional 43 | suspend fun deleteByTgId(tgId: Long) = stepRepository.deleteEnableEntityByTgId(tgId) 44 | 45 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/SwitchEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import me.kuku.telegram.utils.SpringUtils 4 | import org.springframework.data.annotation.Id 5 | import org.springframework.data.mongodb.core.mapping.Document 6 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 7 | import org.springframework.stereotype.Service 8 | import kotlin.reflect.full.callSuspend 9 | import kotlin.reflect.full.declaredFunctions 10 | import kotlin.reflect.full.functions 11 | 12 | @Document("switch") 13 | class SwitchEntity { 14 | @Id 15 | var id: String? = null 16 | var name: String = "" 17 | var tgId: Long = 0 18 | var status: Status = Status.OFF 19 | } 20 | 21 | 22 | interface SwitchRepository: CoroutineCrudRepository { 23 | suspend fun findByTgId(tgId: Long): List 24 | 25 | suspend fun findByTgIdAndName(tgId: Long, name: String): List 26 | 27 | suspend fun findByTgIdAndStatus(tgId: Long, status: Status): List 28 | 29 | } 30 | 31 | @Service 32 | class SwitchService( 33 | private val switchRepository: SwitchRepository 34 | ) { 35 | 36 | suspend fun findByTgId(tgId: Long) = switchRepository.findByTgId(tgId) 37 | 38 | suspend fun findByTgIdAndName(tgId: Long, name: String) = switchRepository.findByTgIdAndName(tgId, name) 39 | 40 | suspend fun save(switchEntity: SwitchEntity) = switchRepository.save(switchEntity) 41 | 42 | suspend fun delete(switchEntity: SwitchEntity) = switchRepository.delete(switchEntity) 43 | 44 | suspend fun deleteById(id: String) = switchRepository.deleteById(id) 45 | 46 | suspend fun findById(id: String) = switchRepository.findById(id) 47 | 48 | suspend fun findByTgIdAndStatus(tgId: Long, status: Status) = switchRepository.findByTgIdAndStatus(tgId, status) 49 | 50 | suspend fun editName(tgId: Long, oldName: String, name: String) { 51 | for (clazz in clazzList) { 52 | val function = clazz.declaredFunctions.find { it.name == "findByTgIdAndTgName" } ?: continue 53 | val instance = SpringUtils.getBean(clazz) 54 | val any = function.callSuspend(instance, tgId, oldName) 55 | if (any != null) { 56 | if (any is List<*>) { 57 | any.forEach { single -> 58 | val method = single!!::class.java.getMethod("setTgName", String::class.java) 59 | method.invoke(single, name) 60 | val saveFunction = clazz.functions.find { it.name == "save" } ?: return@forEach 61 | saveFunction.callSuspend(instance, single) 62 | } 63 | } else { 64 | val method = any::class.java.getMethod("setTgName", String::class.java) 65 | method.invoke(any, name) 66 | val saveFunction = clazz.functions.find { it.name == "save" } ?: continue 67 | saveFunction.callSuspend(instance, any) 68 | } 69 | } 70 | } 71 | } 72 | 73 | suspend fun deleteName(tgId: Long, name: String) { 74 | for (clazz in clazzList) { 75 | val function = clazz.declaredFunctions.find { it.name == "deleteByTgIdAndTgName" } ?: continue 76 | val instance = SpringUtils.getBean(clazz) 77 | function.callSuspend(instance, tgId, name) 78 | } 79 | } 80 | 81 | } 82 | 83 | private val clazzList = mutableListOf( 84 | BaiduRepository::class, BiliBiliRepository::class, 85 | DouYuRepository::class, ECloudRepository::class, 86 | HostLocRepository::class, HuYaRepository::class, 87 | KuGouRepository::class, LeiShenRepository::class, 88 | LogRepository::class, MiHoYoRepository::class, 89 | NodeSeekRepository::class, SmZdmRepository::class, StepRepository::class, 90 | WeiboRepository::class) 91 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/entity/WeiboEntity.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.entity 2 | 3 | import org.springframework.data.annotation.Id 4 | import org.springframework.data.mongodb.core.mapping.Document 5 | import org.springframework.data.repository.kotlin.CoroutineCrudRepository 6 | import org.springframework.stereotype.Service 7 | import org.springframework.transaction.annotation.Transactional 8 | 9 | @Document("weibo") 10 | class WeiboEntity: BaseEntity() { 11 | @Id 12 | var id: String? = null 13 | var cookie: String = "" 14 | var push: Status = Status.OFF 15 | var sign: Status = Status.OFF 16 | } 17 | 18 | interface WeiboRepository: CoroutineCrudRepository { 19 | 20 | suspend fun findByTgIdAndTgName(tgId: Long, tgName: String?): WeiboEntity? 21 | 22 | suspend fun findByPush(push: Status): List 23 | 24 | suspend fun findBySign(sign: Status): List 25 | 26 | suspend fun deleteByTgIdAndTgName(tgId: Long, tgName: String?) 27 | 28 | } 29 | 30 | @Service 31 | class WeiboService( 32 | private val weiboRepository: WeiboRepository 33 | ) { 34 | 35 | suspend fun findByTgId(tgId: Long) = weiboRepository.findEnableEntityByTgId(tgId) as? WeiboEntity 36 | 37 | suspend fun findByPush(push: Status): List = weiboRepository.findByPush(push) 38 | 39 | suspend fun findBySign(sign: Status): List = weiboRepository.findBySign(sign) 40 | 41 | suspend fun save(weiboEntity: WeiboEntity): WeiboEntity = weiboRepository.save(weiboEntity) 42 | 43 | @Transactional 44 | suspend fun deleteByTgId(tgId: Long) = weiboRepository.deleteEnableEntityByTgId(tgId) 45 | 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/exception/QrcodeException.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.exception 2 | 3 | open class QrcodeException(message: String? = null): RuntimeException(message) 4 | 5 | open class QrcodeScanException(message: String? = null): QrcodeException(message) 6 | 7 | class QrcodeNotScannedException(message: String? = null): QrcodeScanException(message) 8 | 9 | fun qrcodeNotScanned(message: String? = null): Nothing = throw QrcodeNotScannedException(message) 10 | 11 | class QrcodeScannedException(message: String? = null): QrcodeScanException(message) 12 | 13 | fun qrcodeScanned(message: String? = null): Nothing = throw QrcodeScannedException(message) 14 | 15 | class QrcodeExpireException(message: String? = null): QrcodeException(message) 16 | 17 | fun qrcodeExpire(message: String = "二维码已过期"): Nothing = throw QrcodeExpireException(message) -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/extension/ConfigExtension.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.extension 2 | 3 | import com.pengrad.telegrambot.model.request.InlineKeyboardButton 4 | import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup 5 | import com.pengrad.telegrambot.model.request.ParseMode 6 | import me.kuku.telegram.context.AbilitySubscriber 7 | import me.kuku.telegram.context.TelegramSubscribe 8 | import me.kuku.telegram.context.inlineKeyboardButton 9 | import me.kuku.telegram.context.nextMessage 10 | import me.kuku.telegram.entity.ConfigEntity 11 | import me.kuku.telegram.entity.ConfigService 12 | import org.springframework.stereotype.Component 13 | 14 | @Component 15 | class ConfigExtension( 16 | private val configService: ConfigService 17 | ) { 18 | 19 | private fun keyboardMarkup(configEntity: ConfigEntity): InlineKeyboardMarkup { 20 | val positiveEnergySwitch = InlineKeyboardButton("${configEntity.positiveEnergy}新闻联播推送") 21 | .callbackData("positiveEnergySwitch") 22 | val settingTwoCaptcha = inlineKeyboardButton("配置2captcha的key", "settingTwoCaptcha") 23 | val v2exPushSwitch = inlineKeyboardButton("${configEntity.v2exPush}v2ex推送", "v2exPushSwitch") 24 | val xianBaoSwitch = inlineKeyboardButton("${configEntity.xianBaoPush}线报推送", "xianBaoSwitch") 25 | val epicFreeGameSwitch = inlineKeyboardButton("${configEntity.epicFreeGamePush}epic免费游戏推送", 26 | "epicFreeGameSwitch") 27 | return InlineKeyboardMarkup( 28 | arrayOf(positiveEnergySwitch), 29 | arrayOf(v2exPushSwitch), 30 | arrayOf(xianBaoSwitch), 31 | arrayOf(epicFreeGameSwitch), 32 | arrayOf(settingTwoCaptcha) 33 | ) 34 | } 35 | 36 | fun text(configEntity: ConfigEntity): String { 37 | return """ 38 | 谨慎充值打码网站,有跑路风险 39 | 配置管理,当前配置: 40 | [2captcha](https://2captcha.com)的key:`${configEntity.twoCaptchaKey}` 41 | """.trimIndent() 42 | } 43 | 44 | fun AbilitySubscriber.config() { 45 | sub("config") { 46 | val configEntity = configService.findByTgId(tgId)?: configService.save(ConfigEntity().init()) 47 | val markup = keyboardMarkup(configEntity) 48 | sendMessage(text(configEntity), replyKeyboard = markup, parseMode = ParseMode.MarkdownV2) 49 | } 50 | } 51 | 52 | fun TelegramSubscribe.positiveEnergyConfig() { 53 | before { set(configService.findByTgId(tgId)!!) } 54 | callback("positiveEnergySwitch") { firstArg().also { it.positiveEnergy = !it.positiveEnergy } } 55 | callback("v2exPushSwitch") { firstArg().also { it.v2exPush = !it.v2exPush } } 56 | callback("xianBaoSwitch") { firstArg().also { it.xianBaoPush = !it.xianBaoPush } } 57 | callback("epicFreeGameSwitch") { firstArg().also { it.epicFreeGamePush = !it.epicFreeGamePush } } 58 | callback("settingTwoCaptcha") { 59 | editMessageText("请发送2captcha的key") 60 | val key = nextMessage().text() 61 | firstArg().twoCaptchaKey = key 62 | } 63 | after { 64 | val configEntity = firstArg() 65 | configService.save(configEntity) 66 | editMessageText(text(configEntity), keyboardMarkup(configEntity), returnButton = false) 67 | } 68 | } 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/extension/DeleteExtension.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.extension 2 | 3 | import com.pengrad.telegrambot.model.request.InlineKeyboardButton 4 | import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup 5 | import me.kuku.telegram.context.AbilitySubscriber 6 | import me.kuku.telegram.context.TelegramSubscribe 7 | import me.kuku.telegram.context.inlineKeyboardButton 8 | import me.kuku.telegram.entity.* 9 | import org.springframework.stereotype.Component 10 | 11 | @Component 12 | class DeleteExtension( 13 | private val baiduService: BaiduService, 14 | private val biliBiliService: BiliBiliService, 15 | private val douYuService: DouYuService, 16 | private val hostLocService: HostLocService, 17 | private val huYaService: HuYaService, 18 | private val kuGouService: KuGouService, 19 | private val miHoYoService: MiHoYoService, 20 | private val stepService: StepService, 21 | private val weiboService: WeiboService, 22 | private val smZdmService: SmZdmService, 23 | private val leiShenService: LeiShenService, 24 | private val nodeSeekService: NodeSeekService, 25 | private val eCloudService: ECloudService 26 | ) { 27 | 28 | private fun markup(): InlineKeyboardMarkup { 29 | val baiduButton = InlineKeyboardButton("百度").callbackData("baiduDelete") 30 | val biliBiliButton = InlineKeyboardButton("哔哩哔哩").callbackData("biliBiliDelete") 31 | val douYuButton = InlineKeyboardButton("斗鱼").callbackData("douYuDelete") 32 | val hostLocButton = InlineKeyboardButton("HostLoc").callbackData("hostLocDelete") 33 | val huYaButton = InlineKeyboardButton("虎牙").callbackData("huYaDelete") 34 | val kuGouButton = InlineKeyboardButton("酷狗").callbackData("kuGouDelete") 35 | val miHoYoButton = InlineKeyboardButton("米忽悠").callbackData("miHoYoDelete") 36 | val stepButton = InlineKeyboardButton("修改步数").callbackData("stepDelete") 37 | val weiboStepButton = InlineKeyboardButton("微博").callbackData("weiboDelete") 38 | val smZdmButton = inlineKeyboardButton("什么值得买", "smZdmDelete") 39 | val leiShenDelete = inlineKeyboardButton("雷神加速器", "leiShenDelete") 40 | val nodeSeek = inlineKeyboardButton("NodeSeek", "nodeSeekDelete") 41 | val eCloud = inlineKeyboardButton("天翼云盘", "eCloudDelete") 42 | return InlineKeyboardMarkup( 43 | arrayOf(baiduButton, biliBiliButton), 44 | arrayOf(douYuButton, hostLocButton), 45 | arrayOf(huYaButton, kuGouButton), 46 | arrayOf(miHoYoButton), 47 | arrayOf(stepButton, weiboStepButton), 48 | arrayOf(smZdmButton), 49 | arrayOf(leiShenDelete, nodeSeek), 50 | arrayOf(eCloud) 51 | ) 52 | } 53 | 54 | fun AbilitySubscriber.delete() { 55 | sub("delete") { 56 | sendMessage("请点击按钮,以删除对应账号", markup()) 57 | } 58 | } 59 | 60 | fun TelegramSubscribe.deleteOperate() { 61 | callback("baiduDelete") { 62 | baiduService.deleteByTgId(tgId) 63 | editMessageText("删除百度成功") 64 | } 65 | callback("biliBiliDelete") { 66 | biliBiliService.deleteByTgId(tgId) 67 | editMessageText("删除哔哩哔哩成功") 68 | } 69 | callback("douYuDelete") { 70 | douYuService.deleteByTgId(tgId) 71 | editMessageText("删除斗鱼成功") 72 | } 73 | callback("hostLocDelete") { 74 | hostLocService.deleteByTgId(tgId) 75 | editMessageText("删除HostLoc成功") 76 | } 77 | callback("huYaDelete") { 78 | huYaService.deleteByTgId(tgId) 79 | editMessageText("删除虎牙成功") 80 | } 81 | callback("kuGouDelete") { 82 | kuGouService.deleteByTgId(tgId) 83 | editMessageText("删除酷狗成功") 84 | } 85 | callback("miHoYoDelete") { 86 | miHoYoService.deleteByTgId(tgId) 87 | editMessageText("删除米哈游成功") 88 | } 89 | callback("stepDelete") { 90 | stepService.deleteByTgId(tgId) 91 | editMessageText("删除修改步数成功") 92 | } 93 | callback("weiboDelete") { 94 | weiboService.deleteByTgId(tgId) 95 | editMessageText("删除微博成功") 96 | } 97 | callback("smZdmDelete") { 98 | smZdmService.deleteByTgId(tgId) 99 | editMessageText("删除什么值得买成功") 100 | } 101 | callback("leiShenDelete") { 102 | leiShenService.deleteByTgId(tgId) 103 | editMessageText("删除雷神加速器成功") 104 | } 105 | callback("nodeSeekDelete") { 106 | nodeSeekService.deleteByTgId(tgId) 107 | editMessageText("删除NodeSeek成功") 108 | } 109 | callback("eCloudDelete") { 110 | eCloudService.deleteByTgId(tgId) 111 | editMessageText("删除天翼云盘成功") 112 | } 113 | } 114 | 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/extension/ErrorHandler.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.extension 2 | 3 | import me.kuku.telegram.context.AnswerCallbackQueryException 4 | import me.kuku.telegram.context.MessageExpiredException 5 | import me.kuku.telegram.context.TelegramExceptionHandler 6 | import org.springframework.stereotype.Component 7 | import java.net.SocketException 8 | import kotlin.IllegalStateException 9 | 10 | @Component 11 | class ErrorHandler { 12 | 13 | fun TelegramExceptionHandler.ss() { 14 | abilityHandler { 15 | abilityContext.sendMessage(throwable.toString()) 16 | } 17 | } 18 | 19 | fun TelegramExceptionHandler.re() { 20 | 21 | handler { 22 | telegramContext.editMessageText(throwable.toString()) 23 | } 24 | 25 | handler { 26 | telegramContext.answerCallbackQuery(throwable.message?: "未知错误消息", throwable.showAlert) 27 | } 28 | 29 | handler { 30 | telegramContext.editMessageText(throwable.toString()) 31 | } 32 | 33 | handler { 34 | telegramContext.editMessageText(throwable.message ?: "未知错消息", returnButton = false) 35 | } 36 | 37 | handler { 38 | telegramContext.editMessageText("请求接口异常,请重试,异常信息:${throwable.message}") 39 | } 40 | 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/extension/IndexExtension.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.extension 2 | 3 | import com.pengrad.telegrambot.model.request.ParseMode 4 | import me.kuku.telegram.context.AbilitySubscriber 5 | import org.springframework.stereotype.Service 6 | 7 | @Service 8 | class IndexExtension { 9 | 10 | fun AbilitySubscriber.start() { 11 | sub("start") { 12 | sendMessage(""" 13 | *kuku*的自动签到机器人。 14 | 机器人开源地址 https://github.com/kukume/tgbot 15 | 求求点个star把 16 | 有问题可以发issues 17 | """.trimIndent(), parseMode = ParseMode.Markdown) 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/extension/LogExtension.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.extension 2 | 3 | import com.pengrad.telegrambot.model.request.InlineKeyboardButton 4 | import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup 5 | import me.kuku.telegram.context.AbilitySubscriber 6 | import me.kuku.telegram.context.TelegramSubscribe 7 | import me.kuku.telegram.context.inlineKeyboardButton 8 | import me.kuku.telegram.entity.LogService 9 | import org.springframework.stereotype.Component 10 | import java.time.LocalDate 11 | import java.time.LocalDateTime 12 | import java.time.format.DateTimeFormatter 13 | 14 | @Component 15 | class LogExtension( 16 | private val logService: LogService 17 | ) { 18 | 19 | private suspend fun replyMarkup(before: LocalDateTime, after: LocalDateTime, tgId: Long): InlineKeyboardMarkup { 20 | val logList = logService.findByCreateTimeBetweenAndTgId(before, after, tgId) 21 | val list = mutableListOf>() 22 | for (logEntity in logList) { 23 | val button = InlineKeyboardButton("${logEntity.type.value} - ${logEntity.text}") 24 | if (logEntity.success()) button.callbackData("logSuccess-${logEntity.id}") 25 | else button.callbackData("logErrReason-${logEntity.id}") 26 | val single = arrayOf(button) 27 | list.add(single) 28 | } 29 | if (list.isEmpty()) 30 | list.add(arrayOf(InlineKeyboardButton("无").callbackData("logNone"))) 31 | val up = before.minusDays(1).toLocalDate() 32 | val upStr = up.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) 33 | val down = before.plusDays(1).toLocalDate() 34 | val downStr = down.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) 35 | val deepButton = arrayOf(inlineKeyboardButton("上一天", "log-$upStr"), inlineKeyboardButton("下一天", "log-$downStr")) 36 | list.add(deepButton) 37 | return InlineKeyboardMarkup(*list.toTypedArray()) 38 | } 39 | 40 | 41 | fun AbilitySubscriber.logShow() { 42 | sub("log") { 43 | val before = LocalDate.now().atTime(0, 0) 44 | sendMessage("${before.format(DateTimeFormatter.ofPattern( "yyyy-MM-dd"))}的自动签到日志,点击可查看详情", 45 | replyMarkup(before, before.plusDays(1), tgId)) 46 | } 47 | } 48 | 49 | fun TelegramSubscribe.logSwitch() { 50 | callbackStartsWith("log-") { 51 | val data = query.data().substring(4) 52 | val before = LocalDate.parse(data, DateTimeFormatter.ofPattern("yyyy-MM-dd")) 53 | val beforeTime = before.atTime(0, 0) 54 | editMessageText("${before}的自动签到日志", replyMarkup(beforeTime, beforeTime.plusDays(1), tgId), 55 | returnButton = false) 56 | } 57 | callbackStartsWith("logSuccess-") { 58 | val id = query.data().substring(11) 59 | val logEntity = logService.findById(id)!! 60 | answerCallbackQuery(logEntity.show, showAlert = true) 61 | } 62 | callback("logNone") { 63 | answerCallbackQuery("这是给你看的,不是给你点的") 64 | } 65 | callbackStartsWith("logErrReason-") { 66 | val id = query.data().substring(13) 67 | val logEntity = logService.findById(id)!! 68 | val reason = logEntity.errReason 69 | val errReason = reason.ifEmpty { "没有记录异常原因,请手动执行重新获取" } 70 | sendMessage("#${logEntity.type.value}签到失败异常信息\n$errReason\n${logEntity.exceptionStack}") 71 | answerCallbackQuery("获取成功") 72 | } 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/extension/OpenaiExtension.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.extension 2 | 3 | import com.aallam.openai.api.chat.* 4 | import com.aallam.openai.api.model.ModelId 5 | import com.aallam.openai.client.OpenAI 6 | import com.aallam.openai.client.OpenAIHost 7 | import com.pengrad.telegrambot.model.PhotoSize 8 | import com.pengrad.telegrambot.model.request.ParseMode 9 | import com.pengrad.telegrambot.request.EditMessageText 10 | import com.pengrad.telegrambot.request.GetFile 11 | import io.ktor.util.* 12 | import kotlinx.coroutines.flow.launchIn 13 | import kotlinx.coroutines.flow.onCompletion 14 | import kotlinx.coroutines.flow.onEach 15 | import kotlinx.coroutines.runBlocking 16 | import me.kuku.telegram.context.AbilitySubscriber 17 | import me.kuku.telegram.context.Locality 18 | import me.kuku.telegram.context.asyncExecute 19 | import me.kuku.telegram.context.byteArray 20 | import me.kuku.telegram.entity.BotConfigService 21 | import me.kuku.telegram.utils.CacheManager 22 | import org.springframework.stereotype.Component 23 | import java.time.Duration 24 | 25 | @Component 26 | class OpenaiExtension( 27 | private val botConfigService: BotConfigService 28 | ) { 29 | 30 | private val cache = CacheManager.getCache>("gpt-chat-context", Duration.ofMinutes(2)) 31 | 32 | 33 | fun AbilitySubscriber.openai() { 34 | 35 | sub(name = "chat", locality = Locality.ALL) { 36 | val replyToMessage = message.replyToMessage() 37 | var text = "" 38 | val photoList = mutableListOf() 39 | val photoSizeList: Array? 40 | if (replyToMessage != null) { 41 | text = (replyToMessage.text() ?: "") + message.text() 42 | photoSizeList = replyToMessage.photo() 43 | } else { 44 | text = firstArg() 45 | photoSizeList = message.photo() 46 | } 47 | photoSizeList?.groupBy { it.fileUniqueId().dropLast(1) }?.mapNotNull { (_, group) -> group.maxByOrNull { it.fileSize() } } 48 | ?.forEach { photoSize -> 49 | val getFile = GetFile(photoSize.fileId()) 50 | val getFileResponse = bot.asyncExecute(getFile) 51 | val base64 = getFileResponse.file().byteArray().encodeBase64() 52 | photoList.add(base64) 53 | } 54 | 55 | val key = chatId.toString() + message.from().id() 56 | 57 | val cacheBody = cache[key] ?: mutableListOf() 58 | 59 | val botConfigEntity = botConfigService.find() 60 | if (botConfigEntity.openaiToken.ifEmpty { "" }.isEmpty()) error("not setting openai token") 61 | val openaiHost = if (botConfigEntity.openaiUrl.isEmpty()) OpenAIHost.OpenAI else OpenAIHost(botConfigEntity.openaiUrl) 62 | val openaiModel = botConfigEntity.openaiModel.ifEmpty { "gpt-4o-mini" } 63 | 64 | val openai = OpenAI(botConfigEntity.openaiToken, host = openaiHost) 65 | 66 | val chatMessage = ChatMessage( 67 | role = ChatRole.User, 68 | content = ContentPartBuilder().also { 69 | it.text(text) 70 | for (photo in photoList) { 71 | it.image("data:image/jpeg;base64,$photo") 72 | } 73 | }.build() 74 | ) 75 | 76 | cacheBody.add(chatMessage) 77 | 78 | val request = ChatCompletionRequest( 79 | model = ModelId(openaiModel), 80 | messages = cacheBody, 81 | streamOptions = streamOptions { 82 | includeUsage = true 83 | } 84 | ) 85 | 86 | runBlocking { 87 | val response = sendMessage("Processing\\.\\.\\.", parseMode = ParseMode.MarkdownV2, replyToMessageId = message.messageId()) 88 | val sendMessageObject = response.message() 89 | val sendMessageId = sendMessageObject.messageId() 90 | var openaiText = "" 91 | var prefix = ">model: ${openaiModel.replace("-", "\\-")}\n" 92 | var alreadySendText = "" 93 | var i = 5 94 | openai.chatCompletions(request).onEach { 95 | it.choices.getOrNull(0)?.delta?.content?.let { content -> 96 | openaiText += content 97 | } 98 | it.usage?.let { usage -> 99 | prefix += ">promptToken: ${usage.promptTokens}\n>completionToken: ${usage.completionTokens}\n" 100 | } 101 | if (i++ % 20 == 0) { 102 | val sendText = "$prefix\n```text\n$openaiText```" 103 | if (alreadySendText != sendText) { 104 | alreadySendText = sendText 105 | val editMessageText = EditMessageText(chatId, sendMessageId, sendText) 106 | .parseMode(ParseMode.MarkdownV2) 107 | bot.asyncExecute(editMessageText) 108 | } 109 | } 110 | }.onCompletion { 111 | val sendText = "$prefix\n```text\n$openaiText```" 112 | cacheBody.add(ChatMessage(ChatRole.Assistant, openaiText)) 113 | cache[key] = cacheBody 114 | if (alreadySendText != sendText) { 115 | alreadySendText = sendText 116 | val editMessageText = EditMessageText(chatId, sendMessageId, sendText) 117 | .parseMode(ParseMode.MarkdownV2) 118 | bot.asyncExecute(editMessageText) 119 | } 120 | }.launchIn(this).join() 121 | } 122 | } 123 | 124 | 125 | } 126 | 127 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/extension/OtherExtension.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.extension 2 | 3 | import me.kuku.telegram.context.TelegramSubscribe 4 | import org.springframework.stereotype.Component 5 | 6 | @Component 7 | class OtherExtension { 8 | 9 | fun TelegramSubscribe.other() { 10 | callback("notWrite") { 11 | answerCallbackQuery("没写") 12 | } 13 | callback("none") { 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/extension/PushExtension.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.extension 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup 5 | import com.pengrad.telegrambot.model.request.ParseMode 6 | import com.pengrad.telegrambot.request.SendDocument 7 | import com.pengrad.telegrambot.request.SendMessage 8 | import com.pengrad.telegrambot.request.SendVideo 9 | import io.ktor.client.call.* 10 | import io.ktor.client.request.* 11 | import io.ktor.server.application.* 12 | import io.ktor.server.request.* 13 | import io.ktor.server.response.* 14 | import io.ktor.server.routing.* 15 | import io.ktor.server.util.* 16 | import me.kuku.telegram.context.* 17 | import me.kuku.telegram.entity.PushEntity 18 | import me.kuku.telegram.entity.PushService 19 | import me.kuku.telegram.ktor.context.KtorRouter 20 | import me.kuku.telegram.utils.client 21 | import me.kuku.telegram.utils.toJsonNode 22 | import org.springframework.stereotype.Component 23 | import java.util.UUID 24 | 25 | @Component 26 | class PushExtension( 27 | private val pushService: PushService 28 | ) { 29 | 30 | fun AbilitySubscriber.push() { 31 | userSub("push") { 32 | val query = inlineKeyboardButton("查看key", "queryPush") 33 | val recreate = inlineKeyboardButton("重新生成key", "recreateKey") 34 | sendMessage("请选择", InlineKeyboardMarkup( 35 | arrayOf(query), 36 | arrayOf(recreate) 37 | )) 38 | } 39 | } 40 | 41 | fun TelegramSubscribe.push() { 42 | callback("queryPush") { 43 | val pushEntity = pushService.findByTgId(tgId) ?: run { 44 | val pushEntity = PushEntity() 45 | pushEntity.tgId = tgId 46 | pushEntity.key = UUID.randomUUID().toString().replace("-", "") 47 | pushService.save(pushEntity) 48 | pushEntity 49 | } 50 | editMessageText(""" 51 | TelegramBot推送 52 | 您的key为:`${pushEntity.key}` 53 | 接口地址为:管理员未设置接口url 54 | 参数:key、text 55 | """.trimIndent(), parseMode = ParseMode.Markdown) 56 | } 57 | callback("recreateKey") { 58 | val pushEntity = pushService.findByTgId(tgId) ?: PushEntity().also { it.tgId = tgId } 59 | pushEntity.key = UUID.randomUUID().toString().replace("-", "") 60 | pushService.save(pushEntity) 61 | editMessageText(""" 62 | TelegramBot推送 63 | 您的key为:`${pushEntity.key}` 64 | 接口地址为:管理员未设置接口url 65 | 参数:key、text、parseMode 66 | """.trimIndent(), parseMode = ParseMode.Markdown) 67 | } 68 | } 69 | 70 | 71 | } 72 | 73 | 74 | @Component 75 | class PushController( 76 | private val pushService: PushService, 77 | private val telegramBot: TelegramBot 78 | ): KtorRouter { 79 | 80 | override fun Routing.route() { 81 | 82 | route("push") { 83 | 84 | suspend fun ApplicationCall.push(key: String, text: String, parseMode: String?) { 85 | val pushEntity = pushService.findByKey(key) ?: error("key不存在") 86 | val tgId = pushEntity.tgId 87 | val sendMessage = SendMessage(tgId, "#自定义推送\n$text") 88 | parseMode?.let { 89 | sendMessage.parseMode(ParseMode.valueOf(parseMode)) 90 | } 91 | telegramBot.asyncExecute(sendMessage) 92 | respond("{}") 93 | } 94 | 95 | get { 96 | val key = call.request.queryParameters.getOrFail("key") 97 | val text = call.request.queryParameters.getOrFail("text") 98 | val parseMode = call.request.queryParameters["parseMode"] 99 | call.push(key, text, parseMode) 100 | } 101 | 102 | post { 103 | val jsonNode = call.receiveText().toJsonNode() 104 | val key = jsonNode["key"].asText() 105 | val text = jsonNode["text"].asText() 106 | val parseMode = jsonNode["parseMode"]?.asText() 107 | call.push(key, text, parseMode) 108 | } 109 | 110 | post("chat") { 111 | val pushBody = call.receive() 112 | val chatId = pushBody.chatId 113 | val messageThreadId = pushBody.messageThreadId 114 | val messages = pushBody.message 115 | val textMessages = messages.filter { it.type == PushBody.Type.TEXT } 116 | val sb = StringBuilder() 117 | textMessages.forEach { sb.append(it.content) } 118 | val imageList = messages.filter { it.type == PushBody.Type.IMAGE }.map { it.content } 119 | if (imageList.isNotEmpty()) { 120 | telegramBot.sendPic(chatId, sb.toString(), imageList, messageThreadId) 121 | call.respond("""{"message": "success"}""") 122 | return@post 123 | } 124 | val videoMessage = messages.find { it.type == PushBody.Type.VIDEO } 125 | if (videoMessage != null) { 126 | val sendVideo = SendVideo(chatId, client.get(videoMessage.content).body()) 127 | messageThreadId?.let { 128 | sendVideo.messageThreadId(it) 129 | } 130 | sendVideo.caption(sb.toString()) 131 | telegramBot.asyncExecute(sendVideo) 132 | call.respond("""{"message": "success"}""") 133 | return@post 134 | } 135 | val fileMessage = messages.find { it.type == PushBody.Type.FILE } 136 | if (fileMessage != null) { 137 | val sendDocument = SendDocument(chatId, client.get(fileMessage.content).body()) 138 | messageThreadId?.let { 139 | sendDocument.messageThreadId(it) 140 | } 141 | sendDocument.caption(sb.toString()) 142 | telegramBot.asyncExecute(sendDocument) 143 | call.respond("""{"message": "success"}""") 144 | return@post 145 | } 146 | telegramBot.sendTextMessage(chatId, sb.toString(), messageThreadId) 147 | call.respond("""{"message": "success"}""") 148 | } 149 | 150 | } 151 | 152 | 153 | } 154 | 155 | } 156 | 157 | class PushBody { 158 | var chatId: Long = 0 159 | var messageThreadId: Int? = 0 160 | var message: MutableList = mutableListOf() 161 | 162 | class Message { 163 | var type: Type = Type.TEXT 164 | var content: String = "" 165 | } 166 | 167 | enum class Type { 168 | TEXT, IMAGE, VIDEO, FILE } 169 | 170 | 171 | } 172 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/extension/SwitchExtension.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.extension 2 | 3 | import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup 4 | import me.kuku.telegram.context.AbilitySubscriber 5 | import me.kuku.telegram.context.TelegramSubscribe 6 | import me.kuku.telegram.context.inlineKeyboardButton 7 | import me.kuku.telegram.context.nextMessage 8 | import me.kuku.telegram.entity.Status 9 | import me.kuku.telegram.entity.SwitchEntity 10 | import me.kuku.telegram.entity.SwitchService 11 | import org.springframework.stereotype.Component 12 | 13 | @Component 14 | class SwitchExtension( 15 | private val switchService: SwitchService 16 | ) { 17 | 18 | fun AbilitySubscriber.switch() { 19 | sub("switch") { 20 | val inlineKeyboardMarkup = InlineKeyboardMarkup( 21 | arrayOf(inlineKeyboardButton("切换身份", "mainSwitch")), 22 | arrayOf(inlineKeyboardButton("添加身份", "addSwitch")), 23 | arrayOf(inlineKeyboardButton("编辑身份", "editSwitch")), 24 | arrayOf(inlineKeyboardButton("删除身份", "deleteSwitch")) 25 | ) 26 | sendMessage(""" 27 | 请选择身份操作 28 | """.trimIndent(), inlineKeyboardMarkup) 29 | } 30 | } 31 | 32 | fun TelegramSubscribe.switch() { 33 | callback("mainSwitch") { 34 | val list = switchService.findByTgId(tgId) 35 | val enable = list.find { it.status == Status.ON } 36 | val str = if (enable == null) "主身份" 37 | else list.first().name 38 | val inlineKeyboardMarkup = InlineKeyboardMarkup( 39 | arrayOf(inlineKeyboardButton("主身份", "mainSwitch-0")), 40 | list.map { inlineKeyboardButton(it.name, "mainSwitch-${it.id}") }.toTypedArray() 41 | ) 42 | editMessageText("请选择需要切换的身份\n当前激活身份:$str", inlineKeyboardMarkup) 43 | } 44 | callbackStartsWith("mainSwitch-") { 45 | val id = query.data().split("-")[1] 46 | val name = if (id == "0") { 47 | val findList = switchService.findByTgId(tgId) 48 | findList.forEach { 49 | it.status = Status.OFF 50 | switchService.save(it) 51 | } 52 | "主身份" 53 | } else { 54 | val switchEntity = switchService.findById(id) ?: error("未找到该身份") 55 | val list = switchService.findByTgIdAndStatus(tgId, Status.ON) 56 | list.forEach { 57 | it.status = Status.OFF 58 | switchService.save(it) 59 | } 60 | switchEntity.status = Status.ON 61 | switchService.save(switchEntity) 62 | switchEntity.name 63 | } 64 | editMessageText("切换身份${name}成功") 65 | } 66 | callback("addSwitch") { 67 | editMessageText("请发送您添加的身份名称") 68 | val remark = nextMessage().text() 69 | val findList = switchService.findByTgIdAndName(tgId, remark) 70 | if (findList.isNotEmpty()) error("该名称已存在") 71 | val switchEntity = SwitchEntity().also { 72 | it.name = remark 73 | it.tgId = tgId 74 | } 75 | switchService.save(switchEntity) 76 | editMessageText("添加身份${remark}成功") 77 | } 78 | callback("editSwitch") { 79 | val list = switchService.findByTgId(tgId) 80 | if (list.isEmpty()) error("您还没有添加身份") 81 | val inlineKeyboardMarkup = InlineKeyboardMarkup( 82 | list.map { inlineKeyboardButton(it.name, "editSwitch-${it.id}") }.toTypedArray() 83 | ) 84 | editMessageText("请选择要编辑的身份", inlineKeyboardMarkup) 85 | } 86 | callbackStartsWith("editSwitch-") { 87 | val id = query.data().split("-")[1] 88 | val switchEntity = switchService.findById(id) ?: error("未找到该身份") 89 | val oldRemark = switchEntity.name 90 | editMessageText("请发送您修改后的身份名称") 91 | val remark = nextMessage().text() 92 | val findList = switchService.findByTgIdAndName(tgId, remark) 93 | if (findList.isNotEmpty() && findList.first().id != id) error("该名称已存在") 94 | switchEntity.name = remark 95 | switchService.save(switchEntity) 96 | switchService.editName(tgId, oldRemark, remark) 97 | editMessageText("修改身份名成功,新身份名:${remark},旧身份名:${oldRemark}") 98 | } 99 | callback("deleteSwitch") { 100 | val list = switchService.findByTgId(tgId) 101 | if (list.isEmpty()) error("您还没有添加身份") 102 | val inlineKeyboardMarkup = InlineKeyboardMarkup( 103 | list.map { inlineKeyboardButton(it.name, "deleteSwitch-${it.id}") }.toTypedArray() 104 | ) 105 | editMessageText("请选择要删除的身份", inlineKeyboardMarkup) 106 | } 107 | callbackStartsWith("deleteSwitch-") { 108 | val id = query.data().split("-")[1] 109 | val switchEntity = switchService.findById(id) ?: error("未找到该身份") 110 | switchService.delete(switchEntity) 111 | switchService.deleteName(tgId, switchEntity.name) 112 | editMessageText("删除身份${switchEntity.name}成功") 113 | } 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/extension/UpdateExtension.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.extension 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import com.fasterxml.jackson.databind.JsonNode 5 | import com.pengrad.telegrambot.model.request.InlineKeyboardButton 6 | import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup 7 | import io.ktor.client.call.* 8 | import io.ktor.client.request.* 9 | import io.ktor.client.statement.* 10 | import kotlinx.coroutines.sync.Mutex 11 | import kotlinx.coroutines.sync.withLock 12 | import me.kuku.telegram.context.MixSubscribe 13 | import me.kuku.telegram.context.Privacy 14 | import me.kuku.telegram.utils.client 15 | import me.kuku.telegram.utils.convertValue 16 | import me.kuku.telegram.utils.setJsonBody 17 | import org.jsoup.Jsoup 18 | import org.springframework.stereotype.Component 19 | import java.io.FileOutputStream 20 | import java.io.InputStream 21 | 22 | @Component 23 | class UpdateExtension { 24 | 25 | val mutex = Mutex() 26 | 27 | fun MixSubscribe.update() { 28 | 29 | ability { 30 | sub(name = "update", privacy = Privacy.CREATOR) { 31 | val os = System.getProperty("os.name") 32 | if (os.lowercase().contains("windows")) error("不支持windows系统") 33 | val files = listFile("/tgbot") 34 | val list = mutableListOf>() 35 | files.forEach { 36 | list.add(arrayOf(InlineKeyboardButton(it.name).callbackData("update|${it.name}"))) 37 | } 38 | sendMessage("请选择需要更新的文件\n/updatelog可查询github提交日志", replyKeyboard = InlineKeyboardMarkup(*list.toTypedArray())) 39 | } 40 | } 41 | 42 | telegram { 43 | callbackStartsWith("update|") { 44 | mutex.withLock { 45 | editMessageText("下载指定jar中") 46 | val suffix = query.data().split("|")[1] 47 | val find = listFile("/tgbot/$suffix").find { it.name == "tgbot.jar" } ?: error("未找到该目录下的文件") 48 | val url = "https://pan.kuku.me/d/tgbot/$suffix/tgbot.jar?sign=${find.sign}" 49 | val str = client.get(url).bodyAsText() 50 | val newUrl = Jsoup.parse(str).getElementsByTag("a").first()?.attr("href") ?: error("未获取到文件链接") 51 | val iis = client.get(newUrl).body() 52 | iis.transferTo(FileOutputStream("tmp${java.io.File.separator}tgbot-new.jar")) 53 | "kuku".toByteArray().inputStream().transferTo(FileOutputStream("update.pid")) 54 | editMessageText(""" 55 | 下载完成,更新中... 56 | 更新完成不会提示,一般10秒以内即可更新完成 57 | """.trimIndent()) 58 | } 59 | } 60 | } 61 | 62 | } 63 | 64 | } 65 | 66 | 67 | private suspend fun listFile(path: String): List { 68 | val jsonNode = client.post("https://pan.kuku.me/api/fs/list") { 69 | setJsonBody("""{"path":"$path","password":"","page":1,"per_page":0,"refresh":false}""") 70 | }.body() 71 | return jsonNode["data"]["content"].convertValue>().stream().limit(5).toList() 72 | } 73 | 74 | 75 | private class File { 76 | var name: String = "" 77 | var size: Long = 0 78 | @JsonProperty("is_dir") 79 | var isDir: Boolean = false 80 | var modified: String = "" 81 | var created: String = "" 82 | var sign: String = "" 83 | var thumb: String = "" 84 | var type: Int = 0 85 | @JsonProperty("hashinfo") 86 | var hashInfo: String = "" 87 | 88 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/ktor/KtorConfiguration.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.ktor 2 | 3 | import io.ktor.server.engine.* 4 | import io.ktor.server.netty.* 5 | import io.ktor.server.routing.* 6 | import me.kuku.telegram.ktor.context.KtorModule 7 | import me.kuku.telegram.ktor.context.KtorRouter 8 | import org.springframework.boot.context.properties.ConfigurationProperties 9 | import org.springframework.boot.context.properties.EnableConfigurationProperties 10 | import org.springframework.context.ApplicationContext 11 | import org.springframework.context.annotation.Bean 12 | import org.springframework.context.annotation.Configuration 13 | 14 | @Configuration 15 | @EnableConfigurationProperties(KtorProperties::class) 16 | class KtorConfiguration( 17 | private val properties: KtorProperties 18 | ) { 19 | 20 | 21 | @Bean 22 | fun applicationEngine(context: ApplicationContext): EmbeddedServer { 23 | return embeddedServer(Netty, host = properties.host, port = properties.port) { 24 | val modules = context.getBeansOfType(KtorModule::class.java).values 25 | val routes = context.getBeansOfType(KtorRouter::class.java).values 26 | 27 | //注册模块 28 | modules.forEach { 29 | it.apply { register() } 30 | } 31 | 32 | //注册路由 33 | routing { 34 | routes.forEach { 35 | it.apply { route() } 36 | } 37 | } 38 | }.start() 39 | } 40 | 41 | 42 | } 43 | 44 | @ConfigurationProperties(prefix = "spring.ktor") 45 | open class KtorProperties( 46 | open var host: String = "0.0.0.0", 47 | open var port: Int = 8080 48 | ) -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/ktor/context/KtorModule.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.ktor.context 2 | 3 | import io.ktor.server.application.* 4 | 5 | interface KtorModule { 6 | 7 | fun Application.register() 8 | 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/ktor/context/KtorRouter.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.ktor.context 2 | 3 | import io.ktor.server.routing.* 4 | 5 | interface KtorRouter { 6 | 7 | fun Routing.route() 8 | 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/logic/CaptchaLogic.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.logic 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import com.fasterxml.jackson.databind.JsonNode 5 | import io.ktor.client.call.* 6 | import io.ktor.client.request.* 7 | import kotlinx.coroutines.delay 8 | import me.kuku.telegram.config.TelegramConfig 9 | import me.kuku.telegram.entity.BotConfigService 10 | import me.kuku.telegram.entity.ConfigService 11 | import me.kuku.telegram.utils.Jackson 12 | import me.kuku.telegram.utils.client 13 | import me.kuku.telegram.utils.setJsonBody 14 | import org.springframework.stereotype.Service 15 | 16 | @Service 17 | class TwoCaptchaLogic( 18 | private val botConfigService: BotConfigService, 19 | private val telegramConfig: TelegramConfig, 20 | private val configService: ConfigService 21 | ) { 22 | 23 | private suspend inline fun captcha(tgId: Long? = null, task: Map): T { 24 | val newKey = run { 25 | tgId?.let { 26 | val configEntity = configService.findByTgId(tgId) 27 | configEntity?.twoCaptchaKey() 28 | } 29 | } ?: botConfigService.findByToken(telegramConfig.token)?.twoCaptchaKey() ?: error("未设置2captcha的key") 30 | val paramNode = Jackson.createObjectNode() 31 | paramNode.put("clientKey", newKey) 32 | paramNode.putPOJO("task", task) 33 | val jsonNode = client.post("https://api.2captcha.com/createTask") { 34 | setJsonBody(paramNode) 35 | }.body() 36 | if (jsonNode["errorId"].asInt() != 0) error("识别验证码失败:" + jsonNode["errorDescription"].asText()) 37 | val code = jsonNode["taskId"].asLong() 38 | var i = 0 39 | while (true) { 40 | if (i++ > 35) error("无法识别验证码") 41 | delay(2000) 42 | val resultJsonNode = client.post("https://api.2captcha.com/getTaskResult") { 43 | setJsonBody(""" 44 | { 45 | "clientKey": "$newKey", 46 | "taskId": $code 47 | } 48 | """.trimIndent()) 49 | }.body() 50 | val resultCode = resultJsonNode["errorId"].asInt() 51 | if (resultCode == 0) { 52 | val status = resultJsonNode["status"].asText() 53 | if (status == "processing") continue 54 | else return Jackson.convertValue(resultJsonNode["solution"]) 55 | } else { 56 | error("识别验证码失败:" + resultJsonNode["errorDescription"].asText()) 57 | } 58 | } 59 | } 60 | 61 | suspend fun geeTest(gt: String, challenge: String, pageUrl: String, tgId: Long? = null): GeeTest { 62 | return captcha(tgId, 63 | mapOf("type" to "GeeTestTaskProxyless", "gt" to gt, "challenge" to challenge, "websiteURL" to pageUrl)) 64 | } 65 | 66 | suspend fun geeTestV4(captchaId: String, pageUrl: String, extraParams: Map = mapOf(), tgId: Long? = null): GeeTestV4 { 67 | return captcha(tgId, 68 | mapOf("type" to "GeeTestTaskProxyless", "captcha_id" to captchaId, "websiteURL" to pageUrl, "version" to 4, 69 | "initParameters" to mutableMapOf("captcha_id" to captchaId).also { it.putAll(extraParams) } 70 | )) 71 | } 72 | 73 | } 74 | 75 | data class GeeTest(@JsonProperty("challenge") val challenge: String, 76 | @JsonProperty("validate") val validate: String, 77 | @JsonProperty("seccode") val secCode: String) 78 | 79 | 80 | data class GeeTestV4(@JsonProperty("lot_number") val lotNumber: String, 81 | @JsonProperty("gen_time") val genTime: String, 82 | @JsonProperty("pass_token") val passToken: String, 83 | @JsonProperty("captcha_output") val captchaOutput: String 84 | ) -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/logic/ECloudLogic.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.logic 2 | 3 | import com.fasterxml.jackson.databind.JsonNode 4 | import io.ktor.client.call.* 5 | import io.ktor.client.request.* 6 | import io.ktor.client.request.forms.* 7 | import io.ktor.client.request.headers 8 | import io.ktor.client.statement.* 9 | import io.ktor.http.* 10 | import io.ktor.util.* 11 | import kotlinx.coroutines.delay 12 | import me.kuku.telegram.entity.ECloudEntity 13 | import me.kuku.telegram.entity.ECloudService 14 | import me.kuku.telegram.utils.* 15 | import org.springframework.stereotype.Service 16 | 17 | @Service 18 | class ECloudLogic( 19 | private val eCloudService: ECloudService 20 | ) { 21 | 22 | suspend fun login(username: String, password: String): ECloudEntity { 23 | val (cookie, lt, reqId, refererUrl) = client.get("https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fweb%2Fredirect.html&defaultSaveName=3&defaultSaveNameCheck=uncheck&browserId=c7044c4577d2d903bbb74a956c11274d") 24 | .let { 25 | val location = it.headers["location"] ?: error("未能成功跳转") 26 | client.get(location).let { response -> 27 | val ltUrl = response.headers["location"] ?: error("未能成功跳转") 28 | val lt = RegexUtils.extract(ltUrl, "lt=", "&") ?: error("未能成功获取lt") 29 | val reqId = RegexUtils.extract(ltUrl, "reqId=", "&") ?: error("未能成功获取reqId") 30 | listOf(response.setCookie().renderCookieHeader(), lt, reqId, ltUrl) 31 | } 32 | } 33 | val headers = StringValues.build { 34 | append("cookie", cookie) 35 | append("lt", lt) 36 | append("referer", refererUrl) 37 | append("reqId", reqId) 38 | } 39 | val configJsonNode = client.submitForm("https://open.e.189.cn/api/logbox/oauth2/appConf.do", 40 | parameters { 41 | append("version", "2.0") 42 | append("appKey", "cloud") 43 | }) { 44 | headers { 45 | appendAll(headers) 46 | } 47 | }.bodyAsText().toJsonNode() 48 | val encryptJsonNode = client.submitForm("https://open.e.189.cn/api/logbox/config/encryptConf.do", 49 | parameters { append("appId", "cloud") }).bodyAsText().toJsonNode() 50 | val paramId = configJsonNode["data"]?.get("paramId")?.asText() ?: error("not found paramId") 51 | val encryptData = encryptJsonNode["data"] 52 | val pre = encryptData["pre"].asText() 53 | val pubKey = encryptData["pubKey"].asText() 54 | client.submitForm("https://open.e.189.cn/api/logbox/oauth2/needcaptcha.do", 55 | parameters { 56 | append("accountType", "01") 57 | append("userName", pre + username.rsaEncrypt(pubKey)) 58 | append("appKey", "cloud") 59 | }).bodyAsText().takeIf { it == "0" } ?: error("需要验证码,请在任意设备成功登陆一次再试") 60 | val response = client.submitForm("https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do", 61 | parameters { 62 | append("version", "v2.0") 63 | append("apToken", "") 64 | append("appKey", "cloud") 65 | append("accountType", "01") 66 | append("userName", pre + username.rsaEncrypt(pubKey).decodeBase64Bytes().hex()) 67 | append("epd", pre + password.rsaEncrypt(pubKey).decodeBase64Bytes().hex()) 68 | append("captchaType", "") 69 | append("validateCode", "") 70 | append("smsValidateCode", "") 71 | append("captchaToken", "") 72 | append( 73 | "returnUrl", 74 | "https%3A%2F%2Fcloud.189.cn%2Fapi%2Fportal%2FcallbackUnify.action%3FredirectURL%3Dhttps%253A%252F%252Fcloud.189.cn%252Fweb%252Fredirect.html" 75 | ) 76 | append("mailSuffix", "@189.cn") 77 | append("dynamicCheck", "FALSE") 78 | append("clientType", "1") 79 | append("cb_SaveName", "3") 80 | append("isOauth2", "false") 81 | append("state", "") 82 | append("paramId", paramId) 83 | }) { 84 | headers { appendAll(headers) } 85 | } 86 | val jsonNode = response.bodyAsText().toJsonNode() 87 | if (jsonNode["result"].asText() != "0") error(jsonNode["msg"].asText()) 88 | val toUrl = jsonNode["toUrl"].asText() 89 | val eCookie = response.setCookie().renderCookieHeader() 90 | val loginResponse = client.get(toUrl) 91 | val resultCookie = loginResponse.setCookie().renderCookieHeader() 92 | return ECloudEntity().also { 93 | it.eCookie = eCookie 94 | it.cookie = resultCookie 95 | } 96 | } 97 | 98 | private fun JsonNode.check() { 99 | if (this.has("errorCode") && this["errorCode"].asText() != "User_Not_Chance") error(this["errorMsg"].asText()) 100 | } 101 | 102 | private suspend fun updateCookie(entity: ECloudEntity) { 103 | val jsonNode = client.get("https://cloud.189.cn/api/portal/listFiles.action?noCache=0.${RandomUtils.num(16)}&fileId=-11") 104 | .body() 105 | if (jsonNode.has("errorCode")) { 106 | val response1 = 107 | client.get("https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fweb%2Fredirect.html") { 108 | cookieString(entity.cookie) 109 | } 110 | val location1 = response1.headers["location"] ?: error("未能成功跳转") 111 | val response2 = client.get(location1) { 112 | cookieString(entity.eCookie) 113 | } 114 | val location2 = response2.headers["location"] ?: error("未能成功跳转") 115 | val response3 = client.get(location2) 116 | val cookie = response3.setCookie().renderCookieHeader() 117 | entity.cookie = cookie 118 | eCloudService.save(entity) 119 | } 120 | } 121 | 122 | suspend fun sign(entity: ECloudEntity) { 123 | updateCookie(entity) 124 | // prizeName 125 | val sv= StringValues.build { 126 | append("user-agent", "Mozilla/5.0 (Linux; Android 5.1.1; SM-G930K Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36 Ecloud/8.6.3 Android/22 clientId/355325117317828 clientModel/SM-G930K imsi/460071114317824 clientChannelId/qq proVersion/1.0.6") 127 | append("Referer", "https://m.cloud.189.cn/zhuanti/2016/sign/index.jsp?albumBackupOpened=1") 128 | append("cookie", entity.cookie) 129 | } 130 | val jsonNode1 = client.get("https://m.cloud.189.cn/v2/drawPrizeMarketDetails.action?taskId=TASK_SIGNIN&activityId=ACT_SIGNIN") { 131 | headers { appendAll(sv) } 132 | }.bodyAsText().toJsonNode() 133 | jsonNode1.check() 134 | delay(5000) 135 | val jsonNode2 = client.get("https://m.cloud.189.cn/v2/drawPrizeMarketDetails.action?taskId=TASK_SIGNIN_PHOTOS&activityId=ACT_SIGNIN") { 136 | headers { appendAll(sv) } 137 | }.bodyAsText().toJsonNode() 138 | jsonNode2.check() 139 | delay(5000) 140 | val jsonNode3 = client.get("https://m.cloud.189.cn/v2/drawPrizeMarketDetails.action?taskId=TASK_2022_FLDFS_KJ&activityId=ACT_SIGNIN") { 141 | headers { appendAll(sv) } 142 | }.bodyAsText().toJsonNode() 143 | jsonNode3.check() 144 | } 145 | 146 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/logic/HuYaLogic.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.logic 2 | 3 | import com.fasterxml.jackson.databind.JsonNode 4 | import io.ktor.client.call.* 5 | import io.ktor.client.request.* 6 | import io.ktor.http.* 7 | import me.kuku.telegram.entity.HuYaEntity 8 | import me.kuku.telegram.exception.qrcodeExpire 9 | import me.kuku.telegram.exception.qrcodeNotScanned 10 | import me.kuku.telegram.utils.* 11 | import org.springframework.stereotype.Service 12 | 13 | @Service 14 | class HuYaLogic { 15 | 16 | suspend fun getQrcode(): HuYaQrcode { 17 | val requestId = RandomUtils.num(8) 18 | val response = client.post("https://udblgn.huya.com/qrLgn/getQrId") { 19 | setJsonBody(""" 20 | {"uri":"70001","version":"2.4","context":"WB-b11031a6ccf245169759e35fc6adc5d9-C9D11B3412B00001BAEA164B1FD4176D-","requestId":"$requestId","appId":"5002","data":{"behavior":"%7B%22a%22%3A%22m%22%2C%22w%22%3A520%2C%22h%22%3A340%2C%22b%22%3A%5B%5D%7D","type":"","domainList":"","page":"https%3A%2F%2Fwww.huya.com%2F"}} 21 | """.trimIndent()) 22 | } 23 | val jsonNode = response.body() 24 | val qrId = jsonNode["data"]["qrId"].asText() 25 | return HuYaQrcode("https://udblgn.huya.com/qrLgn/getQrImg?k=$qrId&appId=5002", qrId, response.setCookie().renderCookieHeader(), requestId) 26 | } 27 | 28 | suspend fun checkQrcode(huYaQrcode: HuYaQrcode): HuYaEntity { 29 | val response = client.post("https://udblgn.huya.com/qrLgn/tryQrLogin") { 30 | setJsonBody(""" 31 | {"uri":"70003","version":"2.4","context":"WB-b11031a6ccf245169759e35fc6adc5d9-C9D11B3412B00001BAEA164B1FD4176D-","requestId":"${huYaQrcode.requestId}","appId":"5002","data":{"qrId":"${huYaQrcode.id}","remember":"1","domainList":"","behavior":"%7B%22a%22%3A%22m%22%2C%22w%22%3A520%2C%22h%22%3A340%2C%22b%22%3A%5B%5D%7D","page":"https%3A%2F%2Fwww.huya.com%2F"}} 32 | """.trimIndent()) 33 | cookieString(huYaQrcode.cookie) 34 | } 35 | val jsonNode = response.body() 36 | return when (val stage = jsonNode["data"]["stage"].asInt()) { 37 | 0, 1 -> qrcodeNotScanned() 38 | 2 -> { 39 | val cookie = response.setCookie().renderCookieHeader() 40 | HuYaEntity().also { 41 | it.cookie = cookie 42 | } 43 | } 44 | 5 -> qrcodeExpire() 45 | else -> error("错误代码为$stage") 46 | } 47 | } 48 | 49 | suspend fun live(huYaEntity: HuYaEntity): List { 50 | var i = 0 51 | val resultList = mutableListOf() 52 | while (true) { 53 | val response = client.get("https://live.huya.com/liveHttpUI/getUserSubscribeToInfoList?iPageIndex=${i++}&_=${System.currentTimeMillis()}") { 54 | cookieString(huYaEntity.cookie) 55 | } 56 | if (response.status == HttpStatusCode.OK) { 57 | val jsonNode = response.body() 58 | val list = jsonNode["vItems"] 59 | if (list.isEmpty) break 60 | for (ss in list) { 61 | val huYaLive = HuYaLive(ss["iRoomId"].asLong(), ss["sLiveDesc"].asText(), ss["sGameName"].asText(), 62 | ss["iIsLive"].asInt() == 1, ss["sNick"].asText(), ss["sVideoCaptureUrl"].asText(), "https://www.huya.com/${ss["iRoomId"].asLong()}") 63 | resultList.add(huYaLive) 64 | } 65 | } else error("查询失败,可能cookie已失效") 66 | } 67 | return resultList 68 | } 69 | 70 | } 71 | 72 | data class HuYaQrcode(val url: String, val id: String, val cookie: String, val requestId: String) 73 | 74 | data class HuYaLive(val roomId: Long, val liveDesc: String, val gameName: String, val isLive: Boolean, val nick: String, val videoCaptureUrl: String, val url: String) -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/logic/LeiShenLogic.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.logic 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import com.fasterxml.jackson.databind.JsonNode 5 | import com.fasterxml.jackson.databind.node.ObjectNode 6 | import io.ktor.client.call.* 7 | import io.ktor.client.request.* 8 | import io.ktor.client.statement.* 9 | import me.kuku.telegram.entity.LeiShenEntity 10 | import me.kuku.telegram.utils.* 11 | import java.net.URLEncoder 12 | import java.time.LocalDateTime 13 | import java.time.ZoneId 14 | import java.time.format.DateTimeFormatter 15 | 16 | object LeiShenLogic { 17 | 18 | suspend fun login(phone: String, password: String): LeiShenEntity { 19 | val params = """ 20 | {"os_type":4,"password":"$password","mobile_num":"$phone","src_channel":"guanwang","country_code":86,"username":"$phone","lang":"zh_CN","region_code":1,"ts":"${System.currentTimeMillis() / 1000}"} 21 | """.trimIndent().toJsonNode() as ObjectNode 22 | val sortedFields = params.fields().asSequence() 23 | .sortedBy { it.key } // 对字段进行排序 24 | .map { entry -> 25 | val key = URLEncoder.encode(entry.key, "UTF-8") 26 | val value = URLEncoder.encode(entry.value.asText(), "UTF-8") 27 | "$key=$value" 28 | } 29 | val convert = sortedFields.joinToString("&") + "&key=5C5A639C20665313622F51E93E3F2783" 30 | val md5 = convert.md5() 31 | params.put("sign", md5) 32 | val jsonNode = client.post("https://webapi.leigod.com/api/auth/login/v1") { 33 | setJsonBody(params) 34 | }.bodyAsText().toJsonNode() 35 | if (jsonNode["code"].asInt() != 0) error(jsonNode["msg"].asText()) 36 | val data = jsonNode["data"] 37 | val info = data["login_info"] 38 | val accountToken = info["account_token"].asText() 39 | val nnToken = info["nn_token"].asText() 40 | val expiryTime = LocalDateTime.parse(info["expiry_time"].asText(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) 41 | .atZone(ZoneId.systemDefault()).toEpochSecond() 42 | return LeiShenEntity().also { 43 | it.accountToken = accountToken 44 | it.nnToken = nnToken 45 | it.expiryTime = expiryTime 46 | it.username = phone 47 | it.password = password 48 | } 49 | } 50 | 51 | suspend fun userInfo(leiShenEntity: LeiShenEntity): LeiShenUserInfo { 52 | val jsonNode = client.post("https://webapi.leigod.com/api/user/info") { 53 | setJsonBody(""" 54 | {"account_token":"${leiShenEntity.accountToken}","lang":"zh_CN"} 55 | """.trimIndent()) 56 | }.body() 57 | if (jsonNode["code"].asInt() != 0) error(jsonNode["msg"].asText()) 58 | return jsonNode["data"].convertValue() 59 | } 60 | 61 | suspend fun recover(leiShenEntity: LeiShenEntity) { 62 | val jsonNode = client.post("https://webapi.leigod.com/api/user/recover") { 63 | setJsonBody(""" 64 | {"account_token":"${leiShenEntity.accountToken}","lang":"zh_CN"} 65 | """.trimIndent()) 66 | }.body() 67 | if (jsonNode["code"].asInt() != 0) error(jsonNode["msg"].asText()) 68 | } 69 | 70 | suspend fun pause(leiShenEntity: LeiShenEntity) { 71 | val jsonNode = client.post("https://webapi.leigod.com/api/user/pause") { 72 | setJsonBody(""" 73 | {"account_token":"${leiShenEntity.accountToken}","lang":"zh_CN"} 74 | """.trimIndent()) 75 | }.body() 76 | if (jsonNode["code"].asInt() != 0) error(jsonNode["msg"].asText()) 77 | } 78 | 79 | } 80 | 81 | 82 | class LeiShenUserInfo { 83 | @JsonProperty("pause_status") 84 | var pauseStatus: String = "" 85 | @JsonProperty("pause_status_id") 86 | var pauseStatusId: Int = 0 // 1 暂停 0 未暂停 87 | @JsonProperty("expiry_time") 88 | var expiryTime: String = "" 89 | } 90 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/logic/LinuxDoLogic.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.logic 2 | 3 | import com.fasterxml.jackson.databind.JsonNode 4 | import io.ktor.client.call.* 5 | import io.ktor.client.request.* 6 | import io.ktor.client.request.forms.* 7 | import io.ktor.client.statement.* 8 | import io.ktor.http.* 9 | import me.kuku.telegram.config.api 10 | import me.kuku.telegram.entity.LinuxDoEntity 11 | import me.kuku.telegram.entity.LinuxDoService 12 | import me.kuku.telegram.utils.* 13 | import org.jsoup.Jsoup 14 | import org.springframework.stereotype.Service 15 | 16 | @Service 17 | class LinuxDoLogic( 18 | private val linuxDoService: LinuxDoService 19 | ) { 20 | 21 | companion object { 22 | 23 | suspend fun check(cookie: String) { 24 | val html = client.get("https://linux.do/") { 25 | cookieString(cookie) 26 | }.bodyAsText() 27 | if (html.contains("""""")) error("未登陆,cookie不正确") 28 | } 29 | 30 | suspend fun latestTopic(): List { 31 | val html = client.get("https://linux.do/latest").bodyAsText() 32 | val document = Jsoup.parse(html) 33 | val elements = document.getElementsByClass("topic-list-item") 34 | val list = mutableListOf() 35 | for (element in elements) { 36 | val title = element.select(".title").text() 37 | val category = element.select(".category-name").text() 38 | val text = element.select(".excerpt").text() 39 | val url = element.select("a[itemprop=\"url\"]").attr("href") 40 | val suffix = url.replace("https://linux.do/t/topic/", "") 41 | list.add(LinuxDoTopic().also { 42 | it.title = title 43 | it.category = category 44 | it.text = text 45 | it.url = url 46 | it.suffix = suffix 47 | }) 48 | /** 49 | * td class="replies">819 50 | * 31644 51 | * 2024 年5 月 11 日 52 | */ 53 | 54 | } 55 | return list 56 | } 57 | 58 | suspend fun login(username: String, password: String): String { 59 | val csrfResponse = client.get("https://linux.do/session/csrf") { 60 | headers { 61 | append("X-Requested-With", "XMLHttpRequest") 62 | } 63 | } 64 | val jsonNode = csrfResponse.body() 65 | val csrf = jsonNode["csrf"].asText() 66 | val tempCookie = csrfResponse.setCookie().renderCookieHeader() 67 | val response = client.submitForm("https://linux.do/session", 68 | parameters { 69 | append("login", username) 70 | append("password", password) 71 | append("second_factor_method", "1") 72 | append("timezone", "Asia/Shanghai") 73 | }) { 74 | headers { 75 | append("X-CSRF-Token", csrf) 76 | append("X-Requested-With", "XMLHttpRequest") 77 | cookieString(tempCookie) 78 | origin("https://linux.do") 79 | referer("https://linux.do/") 80 | } 81 | } 82 | val loginNode = response.body() 83 | if (loginNode.has("error")) error(loginNode["error"].asText()) 84 | return response.setCookie().renderCookieHeader() 85 | } 86 | 87 | 88 | } 89 | 90 | suspend fun index(linuxDoEntity: LinuxDoEntity) { 91 | val jsonNode = client.get("$api/linuxdo/index") { 92 | cookieString(linuxDoEntity.cookie) 93 | }.body() 94 | val newCookie = jsonNode["cookie"].asText() 95 | linuxDoEntity.cookie = newCookie 96 | linuxDoService.save(linuxDoEntity) 97 | } 98 | 99 | suspend fun topic(linuxDoEntity: LinuxDoEntity, id: String) { 100 | val jsonNode = client.get("$api/linuxdo/topic/$id") { 101 | cookieString(linuxDoEntity.cookie) 102 | }.body() 103 | val newCookie = jsonNode["cookie"].asText() 104 | linuxDoEntity.cookie = newCookie 105 | linuxDoService.save(linuxDoEntity) 106 | } 107 | 108 | } 109 | 110 | 111 | class LinuxDoTopic { 112 | var title: String = "" 113 | var category: String = "" 114 | var text: String = "" 115 | var url: String = "" 116 | var suffix: String = "" 117 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/logic/NodeSeekLogic.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.logic 2 | 3 | import com.fasterxml.jackson.databind.JsonNode 4 | import io.ktor.client.call.* 5 | import io.ktor.client.request.* 6 | import io.ktor.client.request.forms.* 7 | import io.ktor.client.statement.* 8 | import io.ktor.http.* 9 | import me.kuku.telegram.config.api 10 | import me.kuku.telegram.entity.NodeSeekEntity 11 | import me.kuku.telegram.utils.client 12 | import me.kuku.telegram.utils.toJsonNode 13 | import me.kuku.telegram.utils.toUrlEncode 14 | 15 | object NodeSeekLogic { 16 | 17 | 18 | suspend fun sign(entity: NodeSeekEntity, random: Boolean = false) { 19 | client.get("$api/nodeseek/sign?cookie=${entity.cookie.toUrlEncode()}&random=$random") 20 | .bodyAsText() 21 | } 22 | 23 | suspend fun querySign(entity: NodeSeekEntity): Int { 24 | val jsonNode = client.get("$api/nodeseek/sign/query?cookie=${entity.cookie.toUrlEncode()}") 25 | .body() 26 | // gain current 27 | if (!(jsonNode["success"]?.asBoolean() ?: error("未获取到NodeSeek签到执行结果"))) error(jsonNode["message"].asText()) 28 | return jsonNode["gain"].asInt() 29 | } 30 | 31 | suspend fun login(username: String, password: String, token: String? = null): String { 32 | val jsonNode = client.submitForm("$api/nodeseek/login", 33 | parameters { 34 | append("username", username) 35 | append("password", password) 36 | token?.let { 37 | append("token", token) 38 | } 39 | }) { 40 | }.bodyAsText().toJsonNode() 41 | if (jsonNode.has("cookie")) return jsonNode["cookie"].asText() 42 | else error(jsonNode["message"].asText()) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/logic/ToolLogic.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.logic 2 | 3 | import io.ktor.client.request.* 4 | import io.ktor.client.statement.* 5 | import io.ktor.utils.io.jvm.javaio.* 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.withContext 8 | import me.kuku.telegram.utils.* 9 | import org.jsoup.Jsoup 10 | import org.springframework.stereotype.Service 11 | import java.io.File 12 | import java.io.FileOutputStream 13 | import java.nio.file.Files 14 | import java.nio.file.Path 15 | 16 | @Service 17 | class ToolLogic { 18 | 19 | suspend fun positiveEnergy(date: String): File { 20 | val html = client.get("http://tv.cctv.com/lm/xwlb/day/$date.shtml").bodyAsText() 21 | val url = 22 | Jsoup.parse(html).getElementsByTag("li").first()?.getElementsByTag("a")?.last()?.attr("href") ?: error("未找到新闻联播链接") 23 | val nextHtml = client.get(url).bodyAsText() 24 | val guid = RegexUtils.extract("guid = \"", "\";", nextHtml) ?: error("没有找到guid") 25 | val tsp = System.currentTimeMillis().toString().substring(0, 10) 26 | val vc = "${tsp}204947899B86370B879139C08EA3B5E88267BF11E55294143CAE692F250517A4C10C".md5().uppercase() 27 | val jsonNode = 28 | client.get("https://vdn.apps.cntv.cn/api/getHttpVideoInfo.do?pid=$guid&client=flash&im=0&tsp=$tsp&vn=2049&vc=$vc&uid=BF11E55294143CAE692F250517A4C10C&wlan=") 29 | .bodyAsText().toJsonNode() 30 | val urlList = jsonNode["video"]["chapters4"].map { it["url"].asText() } 31 | val list = mutableListOf() 32 | for (i in urlList.indices) { 33 | client.get(urlList[i]).bodyAsChannel().toInputStream().use { iis -> 34 | val path = Path.of("tmp", "$date-$i.mp4") 35 | Files.copy(iis, path) 36 | list.add(path.toFile()) 37 | } 38 | } 39 | val sb = StringBuilder() 40 | for (file in list) { 41 | sb.appendLine("file ${file.absolutePath.replace("\\", "/")}") 42 | } 43 | sb.removeSuffix("\n") 44 | val txtFile = File("$date.txt") 45 | val txtFos = withContext(Dispatchers.IO) { 46 | FileOutputStream(txtFile) 47 | } 48 | sb.toString().byteInputStream().copyTo(txtFos) 49 | val newPath = list[0].absolutePath.replace("$date-0.mp4", "$date-output.mp4") 50 | ffmpeg("ffmpeg -f concat -safe 0 -i $date.txt -c copy $newPath") 51 | list.forEach { it.delete() } 52 | txtFile.delete() 53 | return File(newPath) 54 | } 55 | 56 | } 57 | 58 | data class SaucenaoResult( 59 | val similarity: String, val thumbnail: String, val indexName: String, val extUrls: List, val title: String, val author: String, val authUrl: String, 60 | var daId: Long? = null, var pixivId: Long? = null, var faId: Long? = null, var tweetId: Long? = null 61 | ) 62 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/logic/V2exLogic.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.logic 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import com.fasterxml.jackson.databind.JsonNode 5 | import io.ktor.client.call.* 6 | import io.ktor.client.request.* 7 | import me.kuku.telegram.utils.client 8 | import me.kuku.telegram.utils.convertValue 9 | 10 | object V2exLogic { 11 | 12 | suspend fun latestTopic(): List { 13 | val jsonNode = client.get("https://www.v2ex.com/api/topics/latest.json").body() 14 | return jsonNode.convertValue() 15 | } 16 | 17 | } 18 | 19 | 20 | class V2exTopic { 21 | var node: Node = Node() 22 | var member: Member = Member() 23 | @JsonProperty("last_reply_by") 24 | var lastReplyBy: String = "" 25 | @JsonProperty("last_touched") 26 | var lastTouched: Long = 0 27 | var title: String = "" 28 | var url: String = "" 29 | var created: Long = 0 30 | var deleted: Int = 0 31 | var content: String = "" 32 | @JsonProperty("content_rendered") 33 | var contentRendered: String = "" 34 | @JsonProperty("last_modified") 35 | var lastModified: Long = 0 36 | var replies: Int = 0 37 | var id: Int = 0 38 | 39 | class Node { 40 | @JsonProperty("avatar_large") 41 | var avatarLarge: String = "" 42 | var name: String = "" 43 | @JsonProperty("avatar_normal") 44 | var avatarNormal: String = "" 45 | var title: String = "" 46 | var url: String = "" 47 | var topics: Int = 0 48 | var footer: String? = "" 49 | var header: String? = "" 50 | @JsonProperty("title_alternative") 51 | var titleAlternative: String = "" 52 | @JsonProperty("avatar_mini") 53 | var avatarMini: String = "" 54 | var stars: Int = 0 55 | var aliases: List = listOf() 56 | var root: Boolean = false 57 | var id: Int = 0 58 | @JsonProperty("parent_node_name") 59 | var parentNodeName: String? = "" 60 | } 61 | 62 | class Member { 63 | var id: Int = 0 64 | var username: String = "" 65 | var url: String = "" 66 | var website: String? = "" 67 | var twitter: String? = "" 68 | var psn: String? = "" 69 | var github: String? = "" 70 | var btc: String? = "" 71 | var location: String? = "" 72 | var tagline: String? = "" 73 | var bio: String? = "" 74 | @JsonProperty("avatar_mini") 75 | var avatarMini: String = "" 76 | @JsonProperty("avatar_normal") 77 | var avatarNormal: String = "" 78 | @JsonProperty("avatar_large") 79 | var avatarLarge: String = "" 80 | var created: Long = 0 81 | @JsonProperty("last_modified") 82 | var lastModified: String = "" 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/logic/YgoLogic.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.logic 2 | 3 | import io.ktor.client.request.* 4 | import io.ktor.client.statement.* 5 | import me.kuku.telegram.utils.client 6 | import me.kuku.telegram.utils.toUrlEncode 7 | import org.jsoup.Jsoup 8 | import org.jsoup.nodes.Element 9 | import org.jsoup.nodes.TextNode 10 | import org.springframework.stereotype.Service 11 | 12 | @Suppress("DuplicatedCode") 13 | @Service 14 | class YgoLogic { 15 | 16 | suspend fun search(name: String): List { 17 | val str = client.get("https://ygocdb.com/?search=${name.toUrlEncode()}").bodyAsText() 18 | val elements = Jsoup.parse(str).select(".card") 19 | val list = mutableListOf() 20 | for (element in elements) { 21 | val spans = element.select("span") 22 | val chineseName = spans[0].text() 23 | val japaneseName = spans[1].text() 24 | val englishName = if (spans.size == 6) spans[2].text() else "" 25 | val cardPassword = if (spans.size == 6) spans[3].text() else spans[2].text() 26 | val a = element.select(".cardimg a").first()!! 27 | val href = a.attr("href") 28 | val url = "https://ygocdb.com$href" 29 | val imgUrl = a.select("img").first()!!.attr("data-original").replace("!half", "") 30 | val desc = element.select(".desc").first()!! 31 | val nameHtml = desc.select(".name").toString() 32 | val effect = desc.removeClass("name").html().replace(nameHtml, "").replace("
", "\n").replace("
", "\n").replace("\n\n", "\n") 33 | list.add(Card(chineseName, japaneseName, englishName, cardPassword, effect, url, imgUrl)) 34 | } 35 | return list 36 | } 37 | 38 | suspend fun searchDetail(id: Long): Card { 39 | val url = "https://ygocdb.com/card/$id" 40 | val str = client.get(url).bodyAsText() 41 | val document = Jsoup.parse(str) 42 | val imageUrl = document.select(".cardimg img").first()!!.attr("src") 43 | val spans = document.select(".detail .names span").filter { it -> it.attributes().isEmpty } 44 | val chineseName = spans[0].text() 45 | val japaneseName = spans[1].text() 46 | val englishName = spans[2].text() 47 | val cardPassword = spans.getOrNull(3)?.text() ?: "" 48 | val desc = document.select(".desc").first()!! 49 | val childNodes = desc.childNodes() 50 | val sb = StringBuilder() 51 | for (childNode in childNodes) { 52 | val toString = childNode.toString() 53 | if (toString == "
" || toString == "
") sb.append("\n") 54 | else { 55 | when (childNode) { 56 | is Element -> { 57 | sb.append(childNode.text()) 58 | } 59 | is TextNode -> { 60 | sb.append(childNode.text()) 61 | } 62 | else -> sb.append(childNode.toString()) 63 | } 64 | } 65 | } 66 | val effect = sb.toString() 67 | return Card(chineseName, japaneseName, englishName, cardPassword, effect, url, imageUrl) 68 | } 69 | 70 | 71 | } 72 | 73 | data class Card( 74 | val chineseName: String, 75 | val japaneseName: String, 76 | val englishName: String, 77 | val cardPassword: String, 78 | val effect: String, 79 | val url: String, 80 | val imageUrl: String 81 | ) 82 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/BaiduScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import kotlinx.coroutines.delay 4 | import me.kuku.telegram.entity.* 5 | import me.kuku.telegram.logic.BaiduLogic 6 | import org.springframework.scheduling.annotation.Scheduled 7 | import org.springframework.stereotype.Component 8 | 9 | @Component 10 | class BaiduScheduled( 11 | private val baiduService: BaiduService, 12 | private val baiduLogic: BaiduLogic, 13 | private val logService: LogService 14 | ) { 15 | 16 | @Scheduled(cron = "0 41 2 * * ?") 17 | suspend fun sign() { 18 | val list = baiduService.findBySign(Status.ON) 19 | for (baiduEntity in list) { 20 | logService.log(baiduEntity, LogType.Baidu) { 21 | for (i in 0 until 12) { 22 | delay(1000 * 15) 23 | baiduLogic.ybbWatchAd(baiduEntity) 24 | } 25 | for (i in 0 until 4) { 26 | delay(1000 * 30) 27 | baiduLogic.ybbWatchAd(baiduEntity, "v3") 28 | } 29 | baiduLogic.ybbSign(baiduEntity) 30 | delay(2000) 31 | baiduLogic.ybbExchangeVip(baiduEntity) 32 | baiduLogic.tieBaSign(baiduEntity) 33 | } 34 | } 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/BiliBilliScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.request.SendMessage 5 | import com.pengrad.telegrambot.request.SendPhoto 6 | import com.pengrad.telegrambot.request.SendVideo 7 | import io.ktor.client.call.* 8 | import io.ktor.client.request.* 9 | import io.ktor.util.logging.* 10 | import kotlinx.coroutines.delay 11 | import me.kuku.telegram.config.TelegramConfig 12 | import me.kuku.telegram.context.asyncExecute 13 | import me.kuku.telegram.entity.* 14 | import me.kuku.telegram.logic.BiliBiliLogic 15 | import me.kuku.telegram.logic.BiliBiliPojo 16 | import me.kuku.telegram.context.sendPic 17 | import me.kuku.telegram.utils.client 18 | import org.slf4j.LoggerFactory 19 | import org.springframework.scheduling.annotation.Scheduled 20 | import org.springframework.stereotype.Component 21 | import java.io.File 22 | import java.util.concurrent.TimeUnit 23 | 24 | @Component 25 | class BiliBilliScheduled( 26 | private val biliBiliService: BiliBiliService, 27 | private val telegramBot: TelegramBot, 28 | private val telegramConfig: TelegramConfig, 29 | private val logService: LogService 30 | ) { 31 | 32 | private val liveMap = mutableMapOf>() 33 | private val userMap = mutableMapOf() 34 | private val logger = LoggerFactory.getLogger(BiliBilliScheduled::class.java) 35 | 36 | 37 | @Scheduled(cron = "0 23 3 * * ?") 38 | suspend fun sign() { 39 | val list = biliBiliService.findBySign(Status.ON) 40 | for (biliBiliEntity in list) { 41 | logService.log(biliBiliEntity, LogType.BiliBili) { 42 | val firstRank = BiliBiliLogic.ranking(biliBiliEntity)[0] 43 | delay(5000) 44 | BiliBiliLogic.watchVideo(biliBiliEntity, firstRank) 45 | delay(5000) 46 | BiliBiliLogic.share(biliBiliEntity, firstRank.aid) 47 | delay(5000) 48 | BiliBiliLogic.liveSign(biliBiliEntity) 49 | } 50 | delay(3000) 51 | } 52 | } 53 | 54 | @Scheduled(fixedDelay = 2, initialDelay = 2, timeUnit = TimeUnit.MINUTES) 55 | suspend fun liveMonitor() { 56 | val list = biliBiliService.findByLive(Status.ON) 57 | for (biliBiliEntity in list) { 58 | kotlin.runCatching { 59 | delay(3000) 60 | val tgId = biliBiliEntity.tgId 61 | if (!liveMap.containsKey(tgId)) liveMap[tgId] = mutableMapOf() 62 | val map = liveMap[tgId]!! 63 | val liveList = BiliBiliLogic.live(biliBiliEntity) 64 | for (live in liveList) { 65 | val userid = live.id.toLong() 66 | val b = live.status 67 | val name = live.uname 68 | if (map.containsKey(userid)) { 69 | if (map[userid] != b) { 70 | map[userid] = b 71 | val msg = if (b) "直播啦!!" else "下播了!!" 72 | val text = "#哔哩哔哩开播提醒\n#$name $msg\n标题:${live.title}\n链接:${live.url}" 73 | val imageUrl = live.imageUrl 74 | if (imageUrl.isEmpty()) 75 | telegramBot.asyncExecute(SendMessage(tgId, text)) 76 | else { 77 | client.get(imageUrl).body().let { 78 | val sendPhoto = SendPhoto(tgId, it).caption(text).fileName("live.jpg") 79 | telegramBot.asyncExecute(sendPhoto) 80 | } 81 | } 82 | } 83 | } else map[userid] = live.status 84 | } 85 | }.onFailure { 86 | logger.error(it) 87 | } 88 | } 89 | } 90 | 91 | @Scheduled(fixedDelay = 2, timeUnit = TimeUnit.MINUTES) 92 | suspend fun userMonitor() { 93 | val biliBiliList = biliBiliService.findByPush(Status.ON) 94 | for (biliBiliEntity in biliBiliList) { 95 | kotlin.runCatching { 96 | val tgId = biliBiliEntity.tgId 97 | delay(3000) 98 | val list = BiliBiliLogic.friendDynamic(biliBiliEntity) 99 | val newList = mutableListOf() 100 | if (userMap.containsKey(tgId)) { 101 | val oldId = userMap[tgId]!! 102 | for (biliBiliPojo in list) { 103 | if (biliBiliPojo.id.toLong() <= oldId) break 104 | newList.add(biliBiliPojo) 105 | } 106 | for (biliBiliPojo in newList) { 107 | val text = "#哔哩哔哩动态推送\n${BiliBiliLogic.convertStr(biliBiliPojo)}" 108 | val bvId = if (biliBiliPojo.bvId.isNotEmpty()) biliBiliPojo.bvId 109 | else if (biliBiliPojo.forwardBvId.isNotEmpty()) biliBiliPojo.forwardBvId 110 | else "" 111 | try { 112 | if (bvId.isNotEmpty() && telegramConfig.url.isNotEmpty()) { 113 | var file: File? = null 114 | try { 115 | delay(3000) 116 | file = BiliBiliLogic.videoByBvId(biliBiliEntity, biliBiliPojo.bvId) 117 | val sendVideo = 118 | SendVideo(tgId, file).caption(text) 119 | telegramBot.asyncExecute(sendVideo) 120 | } finally { 121 | file?.delete() 122 | } 123 | } else if (biliBiliPojo.picList.isNotEmpty() || biliBiliPojo.forwardPicList.isNotEmpty()) { 124 | val picList = biliBiliPojo.picList 125 | picList.addAll(biliBiliPojo.forwardPicList) 126 | telegramBot.sendPic(tgId, text, picList) 127 | } else telegramBot.asyncExecute(SendMessage(tgId, text)) 128 | } catch (e: Exception) { 129 | telegramBot.asyncExecute(SendMessage(tgId, text)) 130 | } 131 | } 132 | } 133 | userMap[tgId] = list[0].id.toLong() 134 | }.onFailure { 135 | logger.error(it) 136 | } 137 | } 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/BotConfigScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.model.request.InlineKeyboardButton 5 | import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup 6 | import com.pengrad.telegrambot.request.SendMessage 7 | import me.kuku.telegram.config.TelegramConfig 8 | import me.kuku.telegram.context.asyncExecute 9 | import me.kuku.telegram.entity.BotConfigService 10 | import me.kuku.telegram.entity.Status 11 | import me.kuku.telegram.utils.githubCommit 12 | import org.springframework.scheduling.annotation.Scheduled 13 | import org.springframework.stereotype.Component 14 | import java.time.LocalDateTime 15 | 16 | @Component 17 | class BotConfigScheduled( 18 | private val botConfigService: BotConfigService, 19 | private val telegramConfig: TelegramConfig, 20 | private val telegramBot: TelegramBot 21 | ) { 22 | 23 | @Scheduled(cron = "0 0 0 * * ?") 24 | suspend fun commitPush() { 25 | if (telegramConfig.creatorId == 0L) return 26 | val entity = botConfigService.init() 27 | if (entity.updatePush == Status.ON) { 28 | val yesterday = LocalDateTime.now().minusDays(1).toLocalDate() 29 | val commitList = githubCommit().filter { 30 | val localDateTime = it.localDateTime 31 | val head = yesterday.atStartOfDay() 32 | val tail = yesterday.atTime(23, 59, 59) 33 | localDateTime.isAfter(head) && localDateTime.isBefore(tail) 34 | } 35 | val list = mutableListOf>() 36 | for (githubCommit in commitList) { 37 | list.add(arrayOf(InlineKeyboardButton("${githubCommit.date} - ${githubCommit.message}").callbackData("none"))) 38 | } 39 | if (list.isNotEmpty()) { 40 | val sendMessage = SendMessage(telegramConfig.creatorId, """ 41 | #github提交推送 42 | 昨日共有${commitList.size}次提交 43 | """.trimIndent()).replyMarkup(InlineKeyboardMarkup(*list.toTypedArray())) 44 | telegramBot.asyncExecute(sendMessage) 45 | } 46 | } 47 | } 48 | 49 | 50 | 51 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/ConfigScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import com.pengrad.telegrambot.TelegramBot 5 | import com.pengrad.telegrambot.request.SendMessage 6 | import com.pengrad.telegrambot.request.SendVideo 7 | import io.ktor.client.call.* 8 | import io.ktor.client.request.* 9 | import io.ktor.client.statement.* 10 | import kotlinx.coroutines.delay 11 | import me.kuku.telegram.context.asyncExecute 12 | import me.kuku.telegram.entity.ConfigService 13 | import me.kuku.telegram.entity.Status 14 | import me.kuku.telegram.logic.ToolLogic 15 | import me.kuku.telegram.utils.client 16 | import org.springframework.scheduling.annotation.Scheduled 17 | import org.springframework.stereotype.Component 18 | import java.time.LocalDateTime 19 | import java.time.format.DateTimeFormatter 20 | import java.util.concurrent.TimeUnit 21 | 22 | @Component 23 | class ConfigScheduled( 24 | private val toolLogic: ToolLogic, 25 | private val configService: ConfigService, 26 | private val telegramBot: TelegramBot 27 | ) { 28 | 29 | @Scheduled(cron = "0 0 20 * * ?") 30 | suspend fun positiveEnergyPush() { 31 | val entityList = configService.findByPositiveEnergy(Status.ON) 32 | if (entityList.isEmpty()) return 33 | val time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) 34 | val file = toolLogic.positiveEnergy(time) 35 | try { 36 | for (configEntity in entityList) { 37 | val sendVideo = 38 | SendVideo(configEntity.tgId.toString(), file).caption("#新闻联播") 39 | telegramBot.asyncExecute(sendVideo) 40 | } 41 | } finally { 42 | file.delete() 43 | } 44 | } 45 | 46 | private var xianBaoId = 0 47 | 48 | @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.MINUTES) 49 | suspend fun xianBaoPush() { 50 | val list = try { 51 | client.get("http://new.xianbao.fun/plus/json/push.json?230406").body>() 52 | } catch (e: Exception) { 53 | return 54 | } 55 | if (list.isEmpty()) return 56 | val newList = mutableListOf() 57 | if (xianBaoId != 0) { 58 | for (xianBao in list) { 59 | if (xianBao.id <= xianBaoId) break 60 | newList.add(xianBao) 61 | } 62 | } 63 | xianBaoId = list[0].id 64 | val pushList = configService.findByXianBaoPush(Status.ON) 65 | for (xianBao in newList) { 66 | delay(3000) 67 | for (configEntity in pushList) { 68 | val str = """ 69 | #线报酷推送 70 | 标题:${xianBao.title} 71 | 时间:${xianBao.datetime} 72 | 源链接:${xianBao.yuanUrl} 73 | 线报酷链接:${xianBao.urlIncludeDomain()} 74 | """.trimIndent() 75 | val sendMessage = SendMessage(configEntity.tgId, str) 76 | telegramBot.asyncExecute(sendMessage) 77 | } 78 | } 79 | } 80 | 81 | } 82 | 83 | 84 | class XianBao { 85 | var id: Int = 0 86 | var title: String = "" 87 | var content: String = "" 88 | var datetime: String = "" 89 | @JsonProperty("shorttime") 90 | var shortTime: String = "" 91 | @JsonProperty("shijianchuo") 92 | var time: String = "" 93 | @JsonProperty("cateid") 94 | var cateId: String = "" 95 | @JsonProperty("catename") 96 | var cateName: String = "" 97 | var comments: String = "" 98 | @JsonProperty("louzhu") 99 | var louZhu: String = "" 100 | @JsonProperty("louzhuregtime") 101 | var regTime: String? = null 102 | var url: String = "" 103 | @JsonProperty("yuanurl") 104 | var yuanUrl: String = "" 105 | 106 | fun urlIncludeDomain() = "http://new.xianbao.fun/$url" 107 | 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/DouYuScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.request.SendMessage 5 | import com.pengrad.telegrambot.request.SendPhoto 6 | import io.ktor.client.call.* 7 | import io.ktor.client.request.* 8 | import io.ktor.util.logging.* 9 | import kotlinx.coroutines.delay 10 | import me.kuku.telegram.context.asyncExecute 11 | import me.kuku.telegram.entity.* 12 | import me.kuku.telegram.logic.DouYuFish 13 | import me.kuku.telegram.logic.DouYuLogic 14 | import me.kuku.telegram.context.sendPic 15 | import me.kuku.telegram.context.sendTextMessage 16 | import me.kuku.telegram.utils.client 17 | import org.slf4j.LoggerFactory 18 | import org.springframework.scheduling.annotation.Scheduled 19 | import org.springframework.stereotype.Component 20 | import java.util.concurrent.TimeUnit 21 | 22 | @Component 23 | class DouYuScheduled( 24 | private val douYuService: DouYuService, 25 | private val douYuLogic: DouYuLogic, 26 | private val logService: LogService, 27 | private val telegramBot: TelegramBot 28 | ) { 29 | 30 | private val douYuLiveMap = mutableMapOf>() 31 | 32 | private val douYuPushMap = mutableMapOf() 33 | 34 | private val douYuTitleMap = mutableMapOf>() 35 | 36 | private val logger = LoggerFactory.getLogger(DouYuScheduled::class.java) 37 | 38 | @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.MINUTES) 39 | suspend fun douYu() { 40 | val list = douYuService.findByLive(Status.ON) 41 | for (douYuEntity in list) { 42 | kotlin.runCatching { 43 | delay(3000) 44 | val rooms = douYuLogic.room(douYuEntity) 45 | val tgId = douYuEntity.tgId 46 | if (!douYuLiveMap.containsKey(tgId)) douYuLiveMap[tgId] = mutableMapOf() 47 | val map = douYuLiveMap[tgId]!! 48 | for (room in rooms) { 49 | val id = room.roomId 50 | val b = room.showStatus 51 | if (map.containsKey(id)) { 52 | if (map[id] != b) { 53 | map[id] = b 54 | val msg = if (b) "直播啦!!" else "下播啦" 55 | val text = "#斗鱼开播提醒\n#${room.nickName} $msg\n标题:${room.name}\n分类:${room.gameName}\n在线:${room.online}\n链接:${room.url}" 56 | val imageUrl = room.imageUrl 57 | if (imageUrl.isNotEmpty()) { 58 | client.get(imageUrl).body().let { 59 | val sendPhoto = SendPhoto(tgId, it) 60 | .caption(text).fileName("douYuRoom.jpg") 61 | telegramBot.asyncExecute(sendPhoto) 62 | } 63 | } else telegramBot.asyncExecute(SendMessage(tgId, text)) 64 | } 65 | } else map[id] = b 66 | } 67 | }.onFailure { 68 | logger.error(it) 69 | } 70 | } 71 | } 72 | 73 | @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.MINUTES) 74 | suspend fun titleChange() { 75 | val list = douYuService.findByTitleChange(Status.ON) 76 | for (douYuEntity in list) { 77 | kotlin.runCatching { 78 | val data = douYuLogic.room(douYuEntity) 79 | delay(3000) 80 | val tgId = douYuEntity.tgId 81 | if (!douYuTitleMap.containsKey(tgId)) douYuTitleMap[tgId] = mutableMapOf() 82 | val map = douYuTitleMap[tgId]!! 83 | for (room in data) { 84 | val name = room.name 85 | val roomId = room.roomId 86 | val value = map[roomId] 87 | if (value != null && value != name) { 88 | val text = "#斗鱼标题更新提醒\n${room.nickName}\n旧标题:${value}\n新标题:${name}\n链接:${room.url}" 89 | val imageUrl = room.imageUrl 90 | if (imageUrl.isNotEmpty()) { 91 | client.get(imageUrl).body().let { 92 | val sendPhoto = SendPhoto(tgId.toString(), it).fileName("douYuRoom.jpg") 93 | .caption(text) 94 | telegramBot.asyncExecute(sendPhoto) 95 | } 96 | } else telegramBot.sendTextMessage(tgId, text) 97 | } 98 | map[roomId] = name 99 | } 100 | }.onFailure { 101 | logger.error(it) 102 | } 103 | } 104 | } 105 | 106 | @Scheduled(cron = "0 3 6 * * ?") 107 | suspend fun douYuSign() { 108 | val list = douYuService.findByFishGroup(Status.ON) 109 | for (douYuEntity in list) { 110 | logService.log(douYuEntity, LogType.DouYu) { 111 | delay(3000) 112 | val cookie = douYuEntity.cookie 113 | if (cookie.isNotEmpty()) douYuLogic.fishGroup(douYuEntity) 114 | } 115 | } 116 | } 117 | 118 | @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.MINUTES) 119 | suspend fun douYuPush() { 120 | val entityList = douYuService.findByPush(Status.ON) 121 | for (douYuEntity in entityList) { 122 | kotlin.runCatching { 123 | val tgId = douYuEntity.tgId 124 | val list = douYuLogic.focusFishGroup(douYuEntity) 125 | val newList = mutableListOf() 126 | if (douYuPushMap.containsKey(tgId)) { 127 | val oldId = douYuPushMap[tgId]!! 128 | for (biliBiliPojo in list) { 129 | if (biliBiliPojo.id <= oldId) break 130 | newList.add(biliBiliPojo) 131 | } 132 | for (douYuFish in newList) { 133 | val text = "#斗鱼鱼吧动态推送\n#${douYuFish.nickname}\n标题:${douYuFish.title}\n内容:${douYuFish.content}\n链接:${douYuFish.url}" 134 | if (douYuFish.image.isNotEmpty()) { 135 | // JobManager.delay(1000 * 60) { 136 | telegramBot.sendPic(tgId, text, douYuFish.image) 137 | // } 138 | } else { 139 | telegramBot.asyncExecute(SendMessage(tgId, text)) 140 | } 141 | } 142 | } 143 | douYuPushMap[tgId] = list[0].id 144 | }.onFailure { 145 | logger.error(it) 146 | } 147 | } 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/ECloudScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import me.kuku.telegram.entity.ECloudService 4 | import me.kuku.telegram.entity.LogService 5 | import me.kuku.telegram.entity.LogType 6 | import me.kuku.telegram.entity.Status 7 | import me.kuku.telegram.logic.ECloudLogic 8 | import org.springframework.scheduling.annotation.Scheduled 9 | import org.springframework.stereotype.Component 10 | 11 | @Component 12 | class ECloudScheduled( 13 | private val eCloudService: ECloudService, 14 | private val logService: LogService, 15 | private val eCloudLogic: ECloudLogic 16 | ) { 17 | 18 | @Scheduled(cron = "23 14 2 * * ?") 19 | suspend fun sign() { 20 | val list = eCloudService.findBySign(Status.ON) 21 | for (eCloudEntity in list) { 22 | logService.log(eCloudEntity, LogType.ECloud) { 23 | eCloudLogic.sign(eCloudEntity) 24 | } 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/EpicScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import com.fasterxml.jackson.databind.JsonNode 4 | import com.fasterxml.jackson.databind.node.NullNode 5 | import com.pengrad.telegrambot.TelegramBot 6 | import io.ktor.client.call.* 7 | import io.ktor.client.request.* 8 | import io.ktor.client.statement.* 9 | import io.ktor.util.logging.* 10 | import me.kuku.telegram.context.sendPic 11 | import me.kuku.telegram.entity.ConfigService 12 | import me.kuku.telegram.entity.Status 13 | import me.kuku.telegram.utils.client 14 | import me.kuku.telegram.utils.toJsonNode 15 | import org.slf4j.LoggerFactory 16 | import org.springframework.scheduling.annotation.Scheduled 17 | import org.springframework.stereotype.Component 18 | import java.time.LocalDateTime 19 | import java.time.ZoneOffset 20 | import java.time.format.DateTimeFormatter 21 | import java.util.concurrent.TimeUnit 22 | 23 | @Component 24 | class EpicScheduled( 25 | private val telegramBot: TelegramBot, 26 | private val configService: ConfigService 27 | ) { 28 | 29 | private val logger = LoggerFactory.getLogger(EpicScheduled::class.java) 30 | 31 | @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.HOURS) 32 | suspend fun pushFreeGame() { 33 | val list = configService.findByEpicFreeGamePush(Status.ON) 34 | if (list.isEmpty()) return 35 | val jsonNode = client.get("https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions?locale=zh-CN&country=US&allowCountries=US") 36 | .body() 37 | val elements = jsonNode["data"]["Catalog"]["searchStore"]["elements"].filter { it["offerType"].asText() in listOf("OTHERS", "BASE_GAME") } 38 | for (element in elements) { 39 | val promotion = element["promotions"]?.get("promotionalOffers")?.get(0)?.get("promotionalOffers")?.get(0) 40 | ?: element["promotions"]?.get("upcomingPromotionalOffers")?.get(0)?.get("promotionalOffers")?.get(0) ?: continue 41 | val startDate = promotion["startDate"].asText().replace(".000Z", "") 42 | val startTimeStamp = LocalDateTime.parse(startDate, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")).toInstant(ZoneOffset.ofHours(0)).toEpochMilli() 43 | val nowTimeStamp = System.currentTimeMillis() 44 | val diff = nowTimeStamp - startTimeStamp 45 | if (diff < 1000 * 60 * 60 && diff > 0) { 46 | val title = element["title"].asText() 47 | val imageUrl = element["keyImages"][0]["url"].asText() 48 | val slug = element["productSlug"].takeIf { it !is NullNode }?.asText() ?: element["catalogNs"]["mappings"][0]["pageSlug"].asText() 49 | val html = 50 | client.get("https://store.epicgames.com/zh-CN/p/$slug").bodyAsText() 51 | val queryJsonNode = 52 | "window\\.__REACT_QUERY_INITIAL_QUERIES__\\s*=\\s*(\\{.*});".toRegex().find(html)?.value?.substring(41)?.dropLast(1)?.toJsonNode() ?: continue 53 | val queries = queryJsonNode["queries"] 54 | val mappings = queries.filter { it["queryKey"]?.get(0)?.asText() == "getCatalogOffer" } 55 | for (mapping in mappings) { 56 | val catalogOffer = mapping["state"]["data"]["Catalog"]["catalogOffer"] 57 | val offerType = catalogOffer["offerType"].asText() 58 | if (offerType != "BASE_GAME") continue 59 | val namespace = catalogOffer["namespace"].asText() 60 | val id = catalogOffer["id"].asText() 61 | val innerTitle = catalogOffer["title"].asText() 62 | val description = catalogOffer["description"].asText() 63 | val longDescription = catalogOffer["longDescription"].asText() 64 | val fmtPrice = catalogOffer["price"]["totalPrice"]["fmtPrice"] 65 | val originalPrice = fmtPrice["originalPrice"].asText() 66 | val discountPrice = fmtPrice["discountPrice"].asText() 67 | val url = "https://store.epicgames.com/purchase?highlightColor=0078f2&offers=1-$namespace-$id&showNavigation=true#/purchase/payment-methods" 68 | for (configEntity in list) { 69 | kotlin.runCatching { 70 | telegramBot.sendPic(configEntity.tgId, 71 | "#Epic免费游戏推送\n游戏名称: $title\n游戏内部名称: $innerTitle\n游戏描述: $description\n游戏长描述: $longDescription\n原价: $originalPrice\n折扣价: $discountPrice\n订单地址:$url", 72 | listOf(imageUrl)) 73 | }.onFailure { 74 | logger.error(it) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/HostLocScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.request.SendMessage 5 | import io.ktor.util.logging.* 6 | import kotlinx.coroutines.delay 7 | import me.kuku.telegram.context.asyncExecute 8 | import me.kuku.telegram.entity.* 9 | import me.kuku.telegram.logic.HostLocLogic 10 | import me.kuku.telegram.logic.HostLocPost 11 | import org.slf4j.LoggerFactory 12 | import org.springframework.scheduling.annotation.Scheduled 13 | import org.springframework.stereotype.Component 14 | import java.net.ConnectException 15 | import java.util.concurrent.TimeUnit 16 | 17 | @Component 18 | class HostLocScheduled( 19 | private val hostLocService: HostLocService, 20 | private val telegramBot: TelegramBot, 21 | private val logService: LogService 22 | ) { 23 | private var locId = 0 24 | 25 | private val logger = LoggerFactory.getLogger(HostLocScheduled::class.java) 26 | 27 | // @Scheduled(fixedDelay = 2, timeUnit = TimeUnit.MINUTES) 28 | suspend fun locPush() { 29 | val list = try { 30 | HostLocLogic.post() 31 | } catch (e: ConnectException) { 32 | return 33 | } 34 | if (list.isEmpty()) return 35 | val newList = mutableListOf() 36 | if (locId != 0) { 37 | for (hostLocPost in list) { 38 | if (hostLocPost.id <= locId) break 39 | newList.add(hostLocPost) 40 | } 41 | } 42 | locId = list[0].id 43 | val hostLocList = hostLocService.findByPush(Status.ON) 44 | for (hostLocPost in newList) { 45 | delay(3000) 46 | for (hostLocEntity in hostLocList) { 47 | val str = """ 48 | #HostLoc新帖推送 49 | 标题:${hostLocPost.title} 50 | 昵称:#${hostLocPost.name} 51 | 链接:${hostLocPost.url} 52 | 内容:${HostLocLogic.postContent(hostLocPost.url, hostLocEntity.cookie)} 53 | """.trimIndent() 54 | kotlin.runCatching { 55 | val sendMessage = SendMessage(hostLocEntity.tgId, str) 56 | telegramBot.asyncExecute(sendMessage) 57 | }.onFailure { 58 | logger.error(it) 59 | } 60 | } 61 | } 62 | } 63 | 64 | @Scheduled(cron = "0 12 4 * * ?") 65 | suspend fun sign() { 66 | val list = hostLocService.findBySign(Status.ON) 67 | for (hostLocEntity in list) { 68 | logService.log(hostLocEntity, LogType.HostLoc) { 69 | delay(3000) 70 | HostLocLogic.sign(hostLocEntity.cookie) 71 | } 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/HuYaScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.request.SendPhoto 5 | import io.ktor.client.call.* 6 | import io.ktor.client.request.* 7 | import io.ktor.util.logging.* 8 | import kotlinx.coroutines.delay 9 | import me.kuku.telegram.context.asyncExecute 10 | import me.kuku.telegram.entity.* 11 | import me.kuku.telegram.logic.HuYaLogic 12 | import me.kuku.telegram.context.sendTextMessage 13 | import me.kuku.telegram.utils.client 14 | import org.slf4j.LoggerFactory 15 | import org.springframework.scheduling.annotation.Scheduled 16 | import org.springframework.stereotype.Component 17 | import java.util.concurrent.TimeUnit 18 | 19 | @Component 20 | class HuYaScheduled( 21 | private val huYaService: HuYaService, 22 | private val huYaLogic: HuYaLogic, 23 | private val telegramBot: TelegramBot 24 | ) { 25 | 26 | private val logger = LoggerFactory.getLogger(HuYaScheduled::class.java) 27 | 28 | private val huYaLiveMap = mutableMapOf>() 29 | 30 | @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.MINUTES) 31 | suspend fun huYa() { 32 | val list = huYaService.findByLive(Status.ON) 33 | for (huYaEntity in list) { 34 | kotlin.runCatching { 35 | delay(3000) 36 | val lives = huYaLogic.live(huYaEntity) 37 | val tgId = huYaEntity.tgId 38 | if (!huYaLiveMap.containsKey(tgId)) huYaLiveMap[tgId] = mutableMapOf() 39 | val map = huYaLiveMap[tgId]!! 40 | for (room in lives) { 41 | val id = room.roomId 42 | val b = room.isLive 43 | if (map.containsKey(id)) { 44 | if (map[id] != b) { 45 | map[id] = b 46 | val msg = if (b) "直播啦!!" else "下播啦" 47 | val text = "#虎牙开播提醒\n#${room.nick} $msg\n标题:${room.liveDesc}\n分类:${room.gameName}\n链接:${room.url}" 48 | val videoCaptureUrl = room.videoCaptureUrl 49 | if (videoCaptureUrl.isEmpty()) telegramBot.sendTextMessage(tgId, text) 50 | else { 51 | client.get(videoCaptureUrl).body().let { 52 | val sendPhoto = 53 | SendPhoto(tgId, text).caption(text).fileName("huYa.jpg") 54 | telegramBot.asyncExecute(sendPhoto) 55 | } 56 | } 57 | } 58 | } else map[id] = b 59 | } 60 | }.onFailure { 61 | logger.error(it) 62 | } 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/KuGouScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import kotlinx.coroutines.delay 4 | import me.kuku.telegram.entity.* 5 | import me.kuku.telegram.logic.KuGouLogic 6 | import org.springframework.scheduling.annotation.Scheduled 7 | import org.springframework.stereotype.Component 8 | 9 | @Component 10 | class KuGouScheduled( 11 | private val kuGouService: KuGouService, 12 | private val kuGouLogic: KuGouLogic, 13 | private val logService: LogService 14 | ) { 15 | 16 | @Scheduled(cron = "0 41 3 * * ?") 17 | suspend fun sign() { 18 | val list = kuGouService.findBySign(Status.ON) 19 | for (kuGouEntity in list) { 20 | logService.log(kuGouEntity, LogType.KuGou) { 21 | // kuGouLogic.musicianSign(kuGouEntity) 22 | kuGouLogic.listenMusic(kuGouEntity) 23 | repeat(8) { 24 | delay(1000 * 25) 25 | kuGouLogic.watchAd(kuGouEntity) 26 | } 27 | } 28 | delay(3000) 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/LeiShenScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup 5 | import com.pengrad.telegrambot.request.SendMessage 6 | import io.ktor.util.logging.* 7 | import me.kuku.telegram.context.asyncExecute 8 | import me.kuku.telegram.entity.LeiShenService 9 | import me.kuku.telegram.entity.Status 10 | import me.kuku.telegram.logic.LeiShenLogic 11 | import me.kuku.telegram.context.inlineKeyboardButton 12 | import me.kuku.telegram.context.sendTextMessage 13 | import org.slf4j.LoggerFactory 14 | import org.springframework.scheduling.annotation.Scheduled 15 | import org.springframework.stereotype.Component 16 | import java.util.concurrent.TimeUnit 17 | 18 | @Component 19 | class LeiShenScheduled( 20 | private val leiShenService: LeiShenService, 21 | private val telegramBot: TelegramBot 22 | ) { 23 | 24 | private val logger = LoggerFactory.getLogger(LeiShenScheduled::class.java) 25 | 26 | 27 | @Scheduled(fixedDelay = 2, timeUnit = TimeUnit.HOURS) 28 | suspend fun leiShenRemind() { 29 | val entities = leiShenService.findByStatus(Status.ON) 30 | for (entity in entities) { 31 | kotlin.runCatching { 32 | val expiryTime = entity.expiryTime 33 | try { 34 | if (System.currentTimeMillis() > expiryTime) { 35 | val newEntity = LeiShenLogic.login(entity.username, entity.password) 36 | entity.accountToken = newEntity.accountToken 37 | entity.nnToken = newEntity.nnToken 38 | entity.expiryTime = newEntity.expiryTime 39 | leiShenService.save(entity) 40 | } 41 | } catch (e: Exception) { 42 | entity.status = Status.OFF 43 | leiShenService.save(entity) 44 | telegramBot.sendTextMessage(entity.tgId, """ 45 | #雷神加速器登录失败提醒 46 | 您的雷神加速器cookie已失效,重新登录失败,原因:${e.message} 47 | """.trimIndent()) 48 | return@runCatching 49 | } 50 | val userInfo = try { 51 | LeiShenLogic.userInfo(entity) 52 | } catch (e: Exception) { 53 | entity.expiryTime = 0 54 | leiShenService.save(entity) 55 | return@runCatching 56 | } 57 | if (userInfo.pauseStatusId == 0) { 58 | val sendMessage = SendMessage(entity.tgId, """ 59 | #雷神加速器未暂停时间提醒 2小时提醒一次 60 | 您的雷神加速器未暂停时间,如果您未在玩游戏,请尽快暂停 61 | """.trimIndent()).replyMarkup(InlineKeyboardMarkup(inlineKeyboardButton("暂停时间", "leiShenPause"))) 62 | telegramBot.asyncExecute(sendMessage) 63 | } 64 | }.onFailure { 65 | logger.error(it) 66 | } 67 | } 68 | } 69 | 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/LinuxDoScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import kotlinx.coroutines.delay 4 | import me.kuku.telegram.entity.LinuxDoService 5 | import me.kuku.telegram.entity.Status 6 | import me.kuku.telegram.logic.LinuxDoLogic 7 | import org.springframework.scheduling.annotation.Scheduled 8 | import org.springframework.stereotype.Component 9 | import java.util.concurrent.TimeUnit 10 | 11 | @Component 12 | class LinuxDoScheduled( 13 | private val linuxDoService: LinuxDoService, 14 | private val linuxDoLogic: LinuxDoLogic 15 | ) { 16 | 17 | 18 | @Scheduled(cron = "45 41 6 * * ?") 19 | suspend fun index() { 20 | val list = linuxDoService.findBySign(Status.ON) 21 | for (linuxDoEntity in list) { 22 | linuxDoLogic.index(linuxDoEntity) 23 | delay(1000 * 10) 24 | } 25 | } 26 | 27 | @Scheduled(fixedDelay = 3, initialDelay = 1, timeUnit = TimeUnit.HOURS) 28 | suspend fun post() { 29 | val list = linuxDoService.findBySign(Status.ON) 30 | val topic = LinuxDoLogic.latestTopic().random() 31 | for (linuxDoEntity in list) { 32 | linuxDoLogic.topic(linuxDoEntity, topic.suffix) 33 | delay(1000 * 10) 34 | } 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/MiHoYoScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import kotlinx.coroutines.delay 4 | import me.kuku.telegram.entity.* 5 | import me.kuku.telegram.logic.MiHoYoLogic 6 | import org.springframework.scheduling.annotation.Scheduled 7 | import org.springframework.stereotype.Component 8 | 9 | @Component 10 | class MiHoYoScheduled( 11 | private val miHoYoService: MiHoYoService, private val miHoYoLogic: MiHoYoLogic, 12 | private val logService: LogService 13 | ) { 14 | 15 | 16 | @Scheduled(cron = "0 13 8 * * ?") 17 | suspend fun genShinSign() { 18 | val list = miHoYoService.findBySign(Status.ON) 19 | for (miHoYoEntity in list) { 20 | logService.log(miHoYoEntity, LogType.GenShin) { 21 | miHoYoLogic.sign(miHoYoEntity, miHoYoEntity.tgId) 22 | } 23 | delay(3000) 24 | } 25 | } 26 | 27 | @Scheduled(cron = "0 23 8 * * ?") 28 | suspend fun mysSign() { 29 | val list = miHoYoService.findByMysSign(Status.ON) 30 | for (miHoYoEntity in list) { 31 | logService.log(miHoYoEntity, LogType.Mys) { 32 | miHoYoLogic.mysSign(miHoYoEntity) 33 | } 34 | delay(3000) 35 | } 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/NodeSeekScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import kotlinx.coroutines.delay 4 | import me.kuku.telegram.entity.* 5 | import me.kuku.telegram.logic.NodeSeekLogic 6 | import org.springframework.scheduling.annotation.Scheduled 7 | import org.springframework.stereotype.Component 8 | 9 | @Component 10 | class NodeSeekScheduled( 11 | private val nodeSeekService: NodeSeekService, 12 | private val logService: LogService 13 | ) { 14 | 15 | @Scheduled(cron = "0 25 2 * * ?") 16 | suspend fun sign() { 17 | val entityList = nodeSeekService.findAll().filter { it.sign != NodeSeekEntity.Sign.None } 18 | for (entity in entityList) { 19 | delay(3000) 20 | NodeSeekLogic.sign(entity, entity.sign == NodeSeekEntity.Sign.Random) 21 | } 22 | } 23 | 24 | @Scheduled(cron = "0 25 5 * * ?") 25 | suspend fun querySign() { 26 | val entityList = nodeSeekService.findAll().filter { it.sign != NodeSeekEntity.Sign.None } 27 | for (entity in entityList) { 28 | logService.log(entity, LogType.NodeSeek) { 29 | delay(3000) 30 | val num = NodeSeekLogic.querySign(entity) 31 | text = "成功,获得鸡腿${num}个" 32 | } 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/SmZdmScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import kotlinx.coroutines.delay 4 | import me.kuku.telegram.entity.* 5 | import me.kuku.telegram.logic.SmZdmLogic 6 | import org.springframework.scheduling.annotation.Scheduled 7 | import org.springframework.stereotype.Component 8 | 9 | @Component 10 | class SmZdmScheduled( 11 | private val smZdmService: SmZdmService, 12 | private val smZdmLogic: SmZdmLogic, 13 | private val logService: LogService 14 | ) { 15 | 16 | @Scheduled(cron = "42 32 6 * * ?") 17 | suspend fun sign() { 18 | val entityList = smZdmService.findBySign(Status.ON) 19 | for (smZdmEntity in entityList) { 20 | logService.log(smZdmEntity, LogType.SmZdm) { 21 | delay(3000) 22 | smZdmLogic.webSign(smZdmEntity, smZdmEntity.tgId) 23 | smZdmLogic.appSign(smZdmEntity) 24 | } 25 | } 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/StepScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import kotlinx.coroutines.delay 4 | import me.kuku.telegram.entity.* 5 | import me.kuku.telegram.logic.LeXinStepLogic 6 | import me.kuku.telegram.logic.XiaomiStepLogic 7 | import org.springframework.scheduling.annotation.Scheduled 8 | import org.springframework.stereotype.Component 9 | import kotlin.random.Random 10 | 11 | @Component 12 | class StepScheduled( 13 | private val stepService: StepService, 14 | private val logService: LogService 15 | ) { 16 | 17 | @Scheduled(cron = "0 12 5 * * ?") 18 | suspend fun ss() { 19 | val list = stepService.findByAuto() 20 | for (stepEntity in list) { 21 | var step = stepEntity.step 22 | if (stepEntity.offset == Status.ON) { 23 | step = Random.nextInt(step - 1000, step + 1000) 24 | } 25 | logService.log(stepEntity, LogType.Step) { 26 | XiaomiStepLogic.modifyStepCount(stepEntity, step) 27 | LeXinStepLogic.modifyStepCount(stepEntity, step) 28 | } 29 | delay(3000) 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/V2exScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.request.SendMessage 5 | import io.ktor.util.logging.* 6 | import me.kuku.telegram.context.asyncExecute 7 | import me.kuku.telegram.entity.ConfigService 8 | import me.kuku.telegram.entity.Status 9 | import me.kuku.telegram.logic.V2exLogic 10 | import me.kuku.telegram.logic.V2exTopic 11 | import org.slf4j.LoggerFactory 12 | import org.springframework.scheduling.annotation.Scheduled 13 | import org.springframework.stereotype.Component 14 | import java.net.ConnectException 15 | import java.util.concurrent.TimeUnit 16 | 17 | @Component 18 | class V2exScheduled( 19 | private val configService: ConfigService, 20 | private val telegramBot: TelegramBot 21 | ) { 22 | 23 | private val logger = LoggerFactory.getLogger(V2exScheduled::class.java) 24 | 25 | private var v2exId = 0 26 | 27 | @Scheduled(fixedDelay = 1, timeUnit = TimeUnit.MINUTES) 28 | suspend fun push() { 29 | val list = try { 30 | V2exLogic.latestTopic() 31 | } catch (e: ConnectException) { 32 | return 33 | } 34 | if (list.isEmpty()) return 35 | val newList = mutableListOf() 36 | if (v2exId != 0) { 37 | for (topic in list) { 38 | if (topic.id <= v2exId) break 39 | newList.add(topic) 40 | } 41 | } 42 | v2exId = list[0].id 43 | val pushList = configService.findByV2exPush(Status.ON) 44 | for (v2exTopic in newList) { 45 | for (configEntity in pushList) { 46 | val str = """ 47 | #v2ex新帖推送 48 | 标题:${v2exTopic.title} 49 | 链接:${v2exTopic.url} 50 | """.trimIndent() 51 | kotlin.runCatching { 52 | val sendMessage = SendMessage(configEntity.tgId, str) 53 | telegramBot.asyncExecute(sendMessage) 54 | }.onFailure { 55 | logger.error(it) 56 | } 57 | } 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/scheduled/WeiboScheduled.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.scheduled 2 | 3 | import com.pengrad.telegrambot.TelegramBot 4 | import com.pengrad.telegrambot.request.SendVideo 5 | import io.ktor.client.call.* 6 | import io.ktor.client.request.* 7 | import io.ktor.util.logging.* 8 | import kotlinx.coroutines.delay 9 | import me.kuku.telegram.context.asyncExecute 10 | import me.kuku.telegram.entity.* 11 | import me.kuku.telegram.logic.WeiboLogic 12 | import me.kuku.telegram.logic.WeiboPojo 13 | import me.kuku.telegram.context.sendPic 14 | import me.kuku.telegram.context.sendTextMessage 15 | import me.kuku.telegram.utils.client 16 | import org.slf4j.LoggerFactory 17 | import org.springframework.scheduling.annotation.Scheduled 18 | import org.springframework.stereotype.Component 19 | import java.util.concurrent.TimeUnit 20 | 21 | @Component 22 | class WeiboScheduled( 23 | private val weiboService: WeiboService, 24 | private val telegramBot: TelegramBot, 25 | private val logService: LogService 26 | ) { 27 | 28 | private val logger = LoggerFactory.getLogger(WeiboScheduled::class.java) 29 | 30 | private val userMap = mutableMapOf() 31 | 32 | @Scheduled(cron = "0 51 4 * * ?") 33 | suspend fun sign() { 34 | val list = weiboService.findBySign(Status.ON) 35 | for (weiboEntity in list) { 36 | logService.log(weiboEntity, LogType.Weibo) { 37 | WeiboLogic.superTalkSign(weiboEntity) 38 | } 39 | delay(3000) 40 | } 41 | } 42 | 43 | @Scheduled(fixedDelay = 2, timeUnit = TimeUnit.MINUTES) 44 | suspend fun userMonitor() { 45 | val weiboList = weiboService.findByPush(Status.ON) 46 | for (weiboEntity in weiboList) { 47 | kotlin.runCatching { 48 | val tgId = weiboEntity.tgId 49 | delay(3000) 50 | val list = WeiboLogic.followWeibo(weiboEntity) 51 | val newList = mutableListOf() 52 | if (userMap.containsKey(tgId)) { 53 | for (weiboPojo in list) { 54 | if (weiboPojo.id <= userMap[tgId]!!) break 55 | newList.add(weiboPojo) 56 | } 57 | for (weiboPojo in newList) { 58 | val ownText = if (weiboPojo.longText) WeiboLogic.longText(weiboEntity, weiboPojo.bid) else weiboPojo.text 59 | val forwardText = if (weiboPojo.forwardLongText) WeiboLogic.longText(weiboEntity, weiboPojo.forwardBid) else weiboPojo.forwardText 60 | val text = "#微博动态推送\n${WeiboLogic.convert(weiboPojo, ownText, forwardText)}" 61 | val videoUrl = if (weiboPojo.videoUrl.isNotEmpty()) weiboPojo.videoUrl 62 | else if (weiboPojo.forwardVideoUrl.isNotEmpty()) weiboPojo.forwardVideoUrl 63 | else "" 64 | try { 65 | if (videoUrl.isNotEmpty()) { 66 | client.get(videoUrl).body().let { 67 | val sendVideo = SendVideo(tgId, it).caption(text) 68 | .fileName("${weiboPojo.bid}.mp4") 69 | telegramBot.asyncExecute(sendVideo) 70 | } 71 | } else if (weiboPojo.imageUrl.isNotEmpty() || weiboPojo.forwardImageUrl.isNotEmpty()) { 72 | val imageList = weiboPojo.imageUrl 73 | imageList.addAll(weiboPojo.forwardImageUrl) 74 | telegramBot.sendPic(tgId, text, imageList) 75 | } else telegramBot.sendTextMessage(tgId, text) 76 | } catch (e: Exception) { 77 | telegramBot.sendTextMessage(tgId, text) 78 | } 79 | } 80 | } 81 | userMap[tgId] = list[0].id 82 | }.onFailure { 83 | logger.error(it) 84 | } 85 | } 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/utils/CacheManager.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.utils 2 | 3 | import org.slf4j.LoggerFactory 4 | import java.io.File 5 | import java.io.FileInputStream 6 | import java.io.FileOutputStream 7 | import java.io.ObjectInputStream 8 | import java.io.ObjectOutputStream 9 | import java.io.Serializable 10 | import java.time.Duration 11 | import java.util.concurrent.ConcurrentHashMap 12 | import java.util.concurrent.ConcurrentMap 13 | import java.util.concurrent.TimeUnit 14 | 15 | @Suppress("UNCHECKED_CAST") 16 | object CacheManager { 17 | 18 | private val folder = File("cache") 19 | 20 | val cacheMap: ConcurrentMap> = ConcurrentHashMap() 21 | 22 | // private val logger = LoggerFactory.getLogger(CacheManager::class.java) 23 | 24 | init { 25 | if (!folder.exists()) folder.mkdir() 26 | // folder.listFiles()?.filter { it.name.endsWith(".ser") }?.let { files -> 27 | // for (file in files) { 28 | // val cache = FileInputStream(file).use { 29 | // ObjectInputStream(it).use { ois -> 30 | // try { 31 | // ois.readObject() as Cache<*, *> 32 | // } catch (e: Exception) { 33 | // logger.warn("读取缓存文件${file.path}失败") 34 | // file.deleteOnExit() 35 | // null 36 | // } 37 | // } 38 | // } ?: continue 39 | // cacheMap[file.nameWithoutExtension] = cache 40 | // } 41 | // } 42 | Thread.startVirtualThread { 43 | while (true) { 44 | Thread.sleep(1000) 45 | cacheMap.forEach { (_, v) -> 46 | v.check() 47 | } 48 | } 49 | } 50 | // Runtime.getRuntime().addShutdownHook(Thread { 51 | // write() 52 | // }) 53 | } 54 | 55 | inline fun getCache(key: String): Cache { 56 | return cacheMap[key] as? Cache ?: Cache().also { 57 | cacheMap[key] = it 58 | } 59 | } 60 | 61 | inline fun getCache(key: String, duration: Duration): Cache { 62 | return cacheMap[key] as? Cache ?: Cache().also { 63 | it.expire = duration.toMillis() 64 | cacheMap[key] = it 65 | } 66 | } 67 | 68 | inline fun getCache(key: String, expire: Long): Cache { 69 | return cacheMap[key] as? Cache ?: Cache().also { 70 | cacheMap[key] = it 71 | it.expire = expire 72 | } 73 | } 74 | 75 | inline fun getCache(key: String, expire: Long, timeUnit: TimeUnit): Cache { 76 | return cacheMap[key] as? Cache ?: Cache().also { 77 | it.expire = timeUnit.toMillis(expire) 78 | cacheMap[key] = it 79 | } 80 | } 81 | 82 | @Synchronized 83 | fun write() { 84 | cacheMap.forEach { (k, v) -> 85 | FileOutputStream(File(folder, "$k.ser")).use { 86 | ObjectOutputStream(it).use { oos -> 87 | oos.writeObject(v) 88 | } 89 | } 90 | } 91 | } 92 | 93 | 94 | 95 | } 96 | 97 | @Suppress("ConstPropertyName") 98 | open class Cache: Serializable { 99 | companion object { 100 | private const val serialVersionUID = 1L 101 | } 102 | 103 | protected open val map = ConcurrentHashMap>() 104 | var expire: Long? = null 105 | 106 | class Body: Serializable { 107 | companion object { 108 | private const val serialVersionUID = 1L 109 | } 110 | 111 | var value: V? = null 112 | var time: Long = System.currentTimeMillis() 113 | var expire: Long = -1 114 | 115 | fun expire() = expire > 0 && System.currentTimeMillis() > time + expire 116 | } 117 | 118 | operator fun get(key: K): V? { 119 | renew(key) 120 | return map[key]?.value 121 | } 122 | 123 | private fun renew(key: K) { 124 | val body = map[key] 125 | if (body != null) { 126 | body.time = System.currentTimeMillis() 127 | } 128 | } 129 | 130 | operator fun set(key: K, value: V) = put(key, value) 131 | 132 | fun put(key: K, value: V) { 133 | if (expire != null && expire!! > 0) { 134 | put(key, value, expire!!) 135 | } else { 136 | renew(key) 137 | map[key] = Body().also { 138 | it.value = value 139 | } 140 | } 141 | } 142 | 143 | fun put(key: K, value: V, expire: Long, timeUnit: TimeUnit) { 144 | put(key, value, timeUnit.toMillis(expire)) 145 | } 146 | 147 | fun put(key: K, value: V, expire: Long) { 148 | renew(key) 149 | map[key] = Body().also { 150 | it.expire = expire 151 | it.value = value 152 | } 153 | } 154 | 155 | fun put(key: K, value: V, duration: Duration) { 156 | put(key, value, duration.toMillis()) 157 | } 158 | 159 | 160 | fun remove(key: K) { 161 | map.remove(key) 162 | } 163 | 164 | fun clear() { 165 | map.clear() 166 | } 167 | 168 | fun containsKey(key: K): Boolean { 169 | return map.containsKey(key) 170 | } 171 | 172 | fun check() { 173 | val expireKey = mutableListOf() 174 | map.forEach { (k, v) -> 175 | if (v.expire()) { 176 | expireKey.add(k) 177 | } 178 | } 179 | expireKey.forEach { map.remove(it) } 180 | } 181 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/utils/EncryptUtils.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.utils 2 | 3 | import java.net.URLDecoder 4 | import java.net.URLEncoder 5 | import java.security.KeyFactory 6 | import java.security.MessageDigest 7 | import java.security.spec.X509EncodedKeySpec 8 | import java.util.* 9 | import javax.crypto.Cipher 10 | 11 | fun String.md5(): String { 12 | val bytes = MessageDigest.getInstance("MD5").digest(this.toByteArray()) 13 | return bytes.joinToString("") { "%02x".format(it) } 14 | } 15 | 16 | fun String.toUrlDecode(): String { 17 | return URLDecoder.decode(this, "UTF-8") 18 | } 19 | 20 | fun String.toUrlEncode(): String { 21 | return URLEncoder.encode(this, "UTF-8") 22 | } 23 | 24 | fun String.rsaEncrypt(publicKeyStr: String): String { 25 | val keyFactory = KeyFactory.getInstance("RSA") 26 | val decodedKey = Base64.getDecoder().decode(publicKeyStr.toByteArray()) 27 | val keySpec = X509EncodedKeySpec(decodedKey) 28 | val publicKey = keyFactory.generatePublic(keySpec) 29 | val cipher = Cipher.getInstance("RSA") 30 | cipher.init(Cipher.ENCRYPT_MODE, publicKey) 31 | val encryptedBytes = cipher.doFinal(this.toByteArray(Charsets.UTF_8)) 32 | return Base64.getEncoder().encodeToString(encryptedBytes) 33 | } 34 | 35 | fun ByteArray.hex(): String { 36 | return this.joinToString("") { "%02x".format(it) } 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/utils/FuntionUtils.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.utils 2 | 3 | import com.fasterxml.jackson.databind.JsonNode 4 | import com.google.zxing.BarcodeFormat 5 | import com.google.zxing.EncodeHintType 6 | import com.google.zxing.MultiFormatWriter 7 | import com.google.zxing.client.j2se.MatrixToImageWriter 8 | import io.ktor.client.call.* 9 | import io.ktor.client.request.* 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.withContext 12 | import java.io.BufferedReader 13 | import java.io.ByteArrayOutputStream 14 | import java.io.InputStreamReader 15 | import java.time.LocalDateTime 16 | import java.time.format.DateTimeFormatter 17 | 18 | suspend fun ffmpeg(command: String) { 19 | val runtime = Runtime.getRuntime() 20 | val process = withContext(Dispatchers.IO) { 21 | runtime.exec("${if (System.getProperty("os.name").contains("Windows")) "cmd /C " else ""}$command".split(" ").toTypedArray()) 22 | } 23 | Thread.startVirtualThread { 24 | BufferedReader(InputStreamReader(process.inputStream)).use { br -> 25 | while (true) { 26 | br.readLine() ?: break 27 | } 28 | } 29 | } 30 | Thread.startVirtualThread { 31 | BufferedReader(InputStreamReader(process.errorStream)).use { br -> 32 | while (true) { 33 | br.readLine() ?: break 34 | } 35 | } 36 | } 37 | withContext(Dispatchers.IO) { 38 | process.waitFor() 39 | } 40 | } 41 | 42 | fun qrcode(text: String): ByteArray { 43 | val side = 200 44 | val hints = mapOf(EncodeHintType.CHARACTER_SET to "UTF8", EncodeHintType.MARGIN to 0) 45 | val bitMatrix = MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, side, side, hints) 46 | val bos = ByteArrayOutputStream() 47 | MatrixToImageWriter.writeToStream(bitMatrix, "JPEG", bos) 48 | return bos.use { 49 | it.toByteArray() 50 | } 51 | } 52 | 53 | suspend fun githubCommit(): List { 54 | val jsonNode = client.get("https://api.github.com/repos/kukume/tgbot/commits").body() 55 | val list = mutableListOf() 56 | for (node in jsonNode) { 57 | val commit = node["commit"] 58 | val message = commit["message"].asText() 59 | val dateStr = commit["committer"]["date"].asText() 60 | .replace("T", " ").replace("Z", "") 61 | val zero = LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) 62 | val right = zero.plusHours(8) 63 | val date = right.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) 64 | list.add(GithubCommit(date, message, right)) 65 | } 66 | return list 67 | } 68 | 69 | class GithubCommit(val date: String, val message: String, val localDateTime: LocalDateTime) -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/utils/Jackson.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.utils 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude 4 | import com.fasterxml.jackson.core.JsonParser 5 | import com.fasterxml.jackson.core.type.TypeReference 6 | import com.fasterxml.jackson.databind.DeserializationFeature 7 | import com.fasterxml.jackson.databind.JsonNode 8 | import com.fasterxml.jackson.databind.ObjectMapper 9 | import com.fasterxml.jackson.databind.SerializationFeature 10 | import com.fasterxml.jackson.databind.node.ArrayNode 11 | import com.fasterxml.jackson.databind.node.ObjectNode 12 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule 13 | import com.fasterxml.jackson.module.kotlin.kotlinModule 14 | 15 | object Jackson { 16 | 17 | var objectMapper: ObjectMapper = ObjectMapper() 18 | .setSerializationInclusion(JsonInclude.Include.NON_NULL) 19 | .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) 20 | .configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) 21 | .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 22 | .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) 23 | .registerModules(JavaTimeModule(), kotlinModule()) 24 | 25 | 26 | fun readTree(json: String): JsonNode { 27 | return objectMapper.readTree(json) 28 | } 29 | 30 | fun createObjectNode(): ObjectNode { 31 | return objectMapper.createObjectNode() 32 | } 33 | 34 | fun createArrayNode(): ArrayNode { 35 | return objectMapper.createArrayNode() 36 | } 37 | 38 | inline fun convertValue(jsonNode: JsonNode): T { 39 | return objectMapper.convertValue(jsonNode, object: TypeReference() {}) 40 | } 41 | 42 | fun writeValueAsString(any: Any): String { 43 | return objectMapper.writeValueAsString(any) 44 | } 45 | 46 | } 47 | 48 | fun String.toJsonNode(): JsonNode { 49 | return Jackson.readTree(this) 50 | } 51 | 52 | fun String.jsonpToJsonNode(): JsonNode { 53 | return """\{(?:[^{}]|\{[^{}]*})*}""".toRegex().find(this)?.value?.toJsonNode() ?: error("json not found") 54 | } 55 | 56 | inline fun JsonNode.convertValue(): T { 57 | return Jackson.convertValue(this) 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/utils/KtorClient.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.utils 2 | 3 | import io.ktor.client.* 4 | import io.ktor.client.engine.okhttp.* 5 | import io.ktor.client.plugins.contentnegotiation.* 6 | import io.ktor.client.plugins.logging.* 7 | import io.ktor.client.request.* 8 | import io.ktor.http.* 9 | import io.ktor.serialization.jackson.* 10 | 11 | val client by lazy { 12 | HttpClient(OkHttp) { 13 | engine { 14 | config { 15 | followRedirects(false) 16 | } 17 | } 18 | 19 | followRedirects = false 20 | 21 | install(ContentNegotiation) { 22 | jackson() 23 | } 24 | 25 | install(Logging) 26 | 27 | } 28 | } 29 | 30 | fun HttpMessageBuilder.cookieString(content: String): Unit = headers.set(HttpHeaders.Cookie, content) 31 | 32 | fun HttpMessageBuilder.origin(content: String): Unit = headers.set(HttpHeaders.Origin, content) 33 | 34 | fun HttpMessageBuilder.referer(content: String): Unit = headers.set(HttpHeaders.Referrer, content) 35 | 36 | fun List.renderCookieHeader(): String { 37 | return this.filterNot { it.value == "deleted" }.joinToString("") { "${it.name}=${it.value}; " } 38 | } 39 | 40 | fun HttpRequestBuilder.setJsonBody(content: Any) { 41 | contentType(ContentType.Application.Json) 42 | setBody(content) 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/utils/RandomUtils.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.utils 2 | 3 | import kotlin.random.Random 4 | 5 | object RandomUtils { 6 | 7 | fun num(num: Int): String { 8 | return (1..num) 9 | .map { Random.nextInt(0, 10) } // 生成单个数字 10 | .joinToString("") 11 | } 12 | 13 | fun letter(length: Int): String { 14 | val chars = ('a'..'z') + ('A'..'Z') // 包括大小写字母 15 | return (1..length) 16 | .map { chars.random() } 17 | .joinToString("") 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/utils/RegexUtils.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.utils 2 | 3 | object RegexUtils { 4 | 5 | fun extract(text: String, start: String, end: String): String? { 6 | val pattern = "$start(.*?)$end".toRegex() 7 | return pattern.find(text)?.groupValues?.get(1) 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/me/kuku/telegram/utils/SpringUtils.kt: -------------------------------------------------------------------------------- 1 | package me.kuku.telegram.utils 2 | 3 | import org.springframework.context.ApplicationContext 4 | import org.springframework.context.ApplicationContextAware 5 | import org.springframework.context.annotation.Lazy 6 | import org.springframework.stereotype.Component 7 | import kotlin.reflect.KClass 8 | 9 | @Component 10 | @Lazy(false) 11 | class SpringUtils: ApplicationContextAware { 12 | 13 | companion object { 14 | 15 | lateinit var applicationContext: ApplicationContext 16 | 17 | inline fun getBean(name: String): T { 18 | return applicationContext.getBean(name) as T 19 | } 20 | 21 | inline fun getBean(): T = applicationContext.getBean(T::class.java) 22 | 23 | fun getBean(clazz: KClass): T = applicationContext.getBean(clazz.java) 24 | 25 | } 26 | 27 | override fun setApplicationContext(applicationContext: ApplicationContext) { 28 | SpringUtils.applicationContext = applicationContext 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: dev 4 | ktor: 5 | port: 8080 6 | data: 7 | mongodb: 8 | uri: mongodb://localhost/tg 9 | auto-index-creation: true 10 | task: 11 | scheduling: 12 | pool: 13 | size: 10 14 | threads: 15 | virtual: 16 | enabled: true 17 | mail: 18 | host: smtp.office365.com 19 | username: x@x.com 20 | password: 0 21 | properties: 22 | mail: 23 | from: x@x.com 24 | smtp: 25 | starttls: 26 | enable: true 27 | logging: 28 | file: 29 | path: ./tmp 30 | 31 | kuku: 32 | telegram: 33 | token: 34 | creatorId: 0 35 | proxyHost: 36 | proxyPort: 0 37 | proxyType: direct 38 | url: 39 | localPath: 40 | api: 41 | -------------------------------------------------------------------------------- /src/main/resources/image/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/1.jpg -------------------------------------------------------------------------------- /src/main/resources/image/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/10.jpg -------------------------------------------------------------------------------- /src/main/resources/image/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/11.jpg -------------------------------------------------------------------------------- /src/main/resources/image/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/12.jpg -------------------------------------------------------------------------------- /src/main/resources/image/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/13.jpg -------------------------------------------------------------------------------- /src/main/resources/image/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/14.jpg -------------------------------------------------------------------------------- /src/main/resources/image/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/15.jpg -------------------------------------------------------------------------------- /src/main/resources/image/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/16.jpg -------------------------------------------------------------------------------- /src/main/resources/image/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/17.jpg -------------------------------------------------------------------------------- /src/main/resources/image/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/18.jpg -------------------------------------------------------------------------------- /src/main/resources/image/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/19.jpg -------------------------------------------------------------------------------- /src/main/resources/image/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/2.jpg -------------------------------------------------------------------------------- /src/main/resources/image/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/20.jpg -------------------------------------------------------------------------------- /src/main/resources/image/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/21.jpg -------------------------------------------------------------------------------- /src/main/resources/image/22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/22.jpg -------------------------------------------------------------------------------- /src/main/resources/image/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/3.jpg -------------------------------------------------------------------------------- /src/main/resources/image/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/4.jpg -------------------------------------------------------------------------------- /src/main/resources/image/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/5.jpg -------------------------------------------------------------------------------- /src/main/resources/image/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/6.jpg -------------------------------------------------------------------------------- /src/main/resources/image/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/7.jpg -------------------------------------------------------------------------------- /src/main/resources/image/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/8.jpg -------------------------------------------------------------------------------- /src/main/resources/image/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukume/tgbot/dda0f37a4757ce06caf6997f0885580394d4dd69/src/main/resources/image/9.jpg --------------------------------------------------------------------------------