Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
376ea5a1ac
build(deps): bump org.jetbrains.kotlinx:kotlinx-coroutines-core
Bumps [org.jetbrains.kotlinx:kotlinx-coroutines-core](https://github.com/Kotlin/kotlinx.coroutines) from 1.7.3 to 1.9.0.
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.7.3...1.9.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 07:39:09 +00:00
32 changed files with 236 additions and 521 deletions

View File

@ -20,10 +20,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: '11'
distribution: 'adopt-hotspot' distribution: 'adopt-hotspot'
- name: Set up Gradle - name: Set up Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4

View File

@ -19,10 +19,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: '11'
distribution: 'adopt-hotspot' distribution: 'adopt-hotspot'
- name: Set up Gradle - name: Set up Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4

View File

@ -39,10 +39,10 @@ jobs:
run: cz ch --start-rev ${{ steps.previous-tag.outputs.previous-tag }} --file-name ${{ github.workspace }}/CURRENT_CHANGELOG.md run: cz ch --start-rev ${{ steps.previous-tag.outputs.previous-tag }} --file-name ${{ github.workspace }}/CURRENT_CHANGELOG.md
# 开始构建项目. # 开始构建项目.
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: '11'
distribution: 'adopt-hotspot' distribution: 'adopt-hotspot'
- name: Set up Gradle - name: Set up Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4

View File

@ -15,10 +15,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: '11'
distribution: 'adopt-hotspot' distribution: 'adopt-hotspot'
- name: Set up Gradle - name: Set up Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4

View File

@ -15,10 +15,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: '11'
distribution: 'adopt-hotspot' distribution: 'adopt-hotspot'
- name: Set up Gradle - name: Set up Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4

View File

@ -1,7 +1,7 @@
plugins { plugins {
kotlin("jvm") version "2.1.0" apply false kotlin("jvm") version "1.9.23" apply false
id("org.jetbrains.kotlinx.kover") version "0.8.3" apply false id("org.jetbrains.kotlinx.kover") version "0.7.5" apply false
id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.16.3" apply false id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.14.0" apply false
} }
allprojects { allprojects {

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

10
gradlew vendored
View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright ? 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -32,10 +32,10 @@
# Busybox and similar reduced shells will NOT work, because this script # Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features: # requires all of these POSIX shell features:
# * functions; # * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # * expansions ?$var?, ?${var}?, ?${var:-default}?, ?${var+SET}?,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»; # ?${var#prefix}?, ?${var%suffix}?, and ?$( cmd )?;
# * compound commands having a testable exit status, especially «case»; # * compound commands having a testable exit status, especially ?case?;
# * various built-in commands including «command», «set», and «ulimit». # * various built-in commands including ?command?, ?set?, and ?ulimit?.
# #
# Important for patching: # Important for patching:
# #

View File

@ -1,4 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
@ -13,7 +12,7 @@ dependencies {
implementation("org.slf4j:slf4j-api:2.0.11") implementation("org.slf4j:slf4j-api:2.0.11")
implementation("io.github.microutils:kotlin-logging:3.0.5") implementation("io.github.microutils:kotlin-logging:3.0.5")
implementation("ch.qos.logback:logback-classic:1.5.12") implementation("ch.qos.logback:logback-classic:1.4.14")
val aetherVersion = "1.1.0" val aetherVersion = "1.1.0"
implementation("org.eclipse.aether:aether-api:$aetherVersion") implementation("org.eclipse.aether:aether-api:$aetherVersion")
@ -23,17 +22,15 @@ dependencies {
implementation("org.eclipse.aether:aether-transport-http:$aetherVersion") implementation("org.eclipse.aether:aether-transport-http:$aetherVersion")
implementation("org.eclipse.aether:aether-connector-basic:$aetherVersion") implementation("org.eclipse.aether:aether-connector-basic:$aetherVersion")
implementation("org.apache.maven:maven-aether-provider:3.3.9") implementation("org.apache.maven:maven-aether-provider:3.3.9")
implementation("org.codehaus.plexus:plexus-utils:3.5.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
implementation("org.jetbrains.kotlin:kotlin-reflect:2.1.0") implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.23")
implementation("com.google.code.gson:gson:2.10.1") implementation("com.google.code.gson:gson:2.10.1")
implementation("org.jdom:jdom2:2.0.6.1") implementation("org.jdom:jdom2:2.0.6.1")
implementation("org.telegram:telegrambots-abilities:8.0.0") implementation("org.telegram:telegrambots-abilities:6.9.7.1")
implementation("org.telegram:telegrambots-longpolling:8.0.0") implementation("org.telegram:telegrambots:6.9.7.1")
implementation("org.telegram:telegrambots-client:8.0.0")
implementation("io.prometheus:simpleclient:0.16.0") implementation("io.prometheus:simpleclient:0.16.0")
implementation("io.prometheus:simpleclient_httpserver:0.16.0") implementation("io.prometheus:simpleclient_httpserver:0.16.0")
@ -45,13 +42,11 @@ dependencies {
tasks.test { tasks.test {
useJUnitPlatform() useJUnitPlatform()
jvmArgs("--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.lang=ALL-UNNAMED")
} }
tasks.withType<KotlinCompile> { tasks.withType<KotlinCompile> {
compilerOptions { kotlinOptions.jvmTarget = "11"
jvmTarget = JvmTarget.JVM_17
}
} }
application { application {

View File

@ -13,6 +13,7 @@ import org.eclipse.aether.repository.Proxy
import org.eclipse.aether.repository.RemoteRepository import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.repository.RepositoryPolicy import org.eclipse.aether.repository.RepositoryPolicy
import org.slf4j.event.Level import org.slf4j.event.Level
import org.telegram.telegrambots.bots.DefaultBotOptions
import java.io.File import java.io.File
import java.net.URL import java.net.URL
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@ -23,13 +24,13 @@ import kotlin.reflect.KProperty
private val log = KotlinLogging.logger { } private val log = KotlinLogging.logger { }
internal fun ProxyType.toJavaProxyType(): java.net.Proxy.Type? { internal fun ProxyType.toTelegramBotsType(): DefaultBotOptions.ProxyType {
return when (this) { return when (this) {
ProxyType.NO_PROXY -> null ProxyType.NO_PROXY -> DefaultBotOptions.ProxyType.NO_PROXY
ProxyType.HTTP -> java.net.Proxy.Type.HTTP ProxyType.HTTP -> DefaultBotOptions.ProxyType.HTTP
ProxyType.HTTPS -> java.net.Proxy.Type.HTTP ProxyType.HTTPS -> DefaultBotOptions.ProxyType.HTTP
ProxyType.SOCKS4 -> java.net.Proxy.Type.SOCKS ProxyType.SOCKS4 -> DefaultBotOptions.ProxyType.SOCKS4
ProxyType.SOCKS5 -> java.net.Proxy.Type.SOCKS ProxyType.SOCKS5 -> DefaultBotOptions.ProxyType.SOCKS5
} }
} }
@ -309,7 +310,7 @@ internal fun initialFiles(): Boolean {
val configFilesNotInitialized = !AppPaths.CONFIG_APPLICATION.file.exists() val configFilesNotInitialized = !AppPaths.CONFIG_APPLICATION.file.exists()
&& !AppPaths.CONFIG_BOT.file.exists() && !AppPaths.CONFIG_BOT.file.exists()
for (path in AppPaths.entries) { for (path in AppPaths.values()) {
path.initial() path.initial()
} }

View File

@ -6,17 +6,15 @@ import kotlinx.coroutines.runBlocking
import mu.KotlinLogging import mu.KotlinLogging
import net.lamgc.scalabot.config.* import net.lamgc.scalabot.config.*
import net.lamgc.scalabot.util.registerShutdownHook import net.lamgc.scalabot.util.registerShutdownHook
import okhttp3.OkHttpClient
import org.eclipse.aether.repository.LocalRepository import org.eclipse.aether.repository.LocalRepository
import org.telegram.telegrambots.client.okhttp.OkHttpTelegramClient import org.telegram.telegrambots.bots.DefaultBotOptions
import org.telegram.telegrambots.longpolling.BotSession import org.telegram.telegrambots.meta.TelegramBotsApi
import org.telegram.telegrambots.longpolling.TelegramBotsLongPollingApplication
import org.telegram.telegrambots.meta.api.methods.GetMe import org.telegram.telegrambots.meta.api.methods.GetMe
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException
import org.telegram.telegrambots.meta.generics.BotSession
import org.telegram.telegrambots.updatesreceivers.DefaultBotSession
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.net.InetSocketAddress
import java.net.Proxy
import java.nio.file.attribute.PosixFilePermission import java.nio.file.attribute.PosixFilePermission
import java.nio.file.attribute.PosixFilePermissions import java.nio.file.attribute.PosixFilePermissions
import kotlin.io.path.createDirectories import kotlin.io.path.createDirectories
@ -76,7 +74,7 @@ internal class Launcher(
private val log = KotlinLogging.logger { } private val log = KotlinLogging.logger { }
} }
private val botApi = TelegramBotsLongPollingApplication() private val botApi = TelegramBotsApi(DefaultBotSession::class.java)
private val botSessionMap = mutableMapOf<ScalaBot, BotSession>() private val botSessionMap = mutableMapOf<ScalaBot, BotSession>()
private val mavenLocalRepository = getMavenLocalRepository() private val mavenLocalRepository = getMavenLocalRepository()
@ -146,10 +144,6 @@ internal class Launcher(
} }
} }
} }
botApi.start()
botApi.registerShutdownHook()
return if (launchedCounts != 0) { return if (launchedCounts != 0) {
log.info { "已启动 $launchedCounts 个机器人." } log.info { "已启动 $launchedCounts 个机器人." }
true true
@ -177,17 +171,17 @@ internal class Launcher(
ProxyConfig(type = ProxyType.NO_PROXY) ProxyConfig(type = ProxyType.NO_PROXY)
} }
val okhttpClientBuilder = OkHttpClient.Builder() val botOption = DefaultBotOptions().apply {
if (proxyConfig.type != ProxyType.NO_PROXY) {
proxyType = proxyConfig.type.toTelegramBotsType()
proxyHost = config.proxy.host
proxyPort = config.proxy.port
log.debug { "机器人 `${botConfig.account.name}` 已启用代理配置: $proxyConfig" }
}
if (proxyConfig.type != ProxyType.NO_PROXY) { baseUrl = botConfig.baseApiUrl
val proxyType = proxyConfig.type.toJavaProxyType()
val proxyAddress = InetSocketAddress.createUnresolved(proxyConfig.host, proxyConfig.port)
okhttpClientBuilder.proxy(Proxy(proxyType, proxyAddress))
} }
val account = botConfig.account val account = botConfig.account
val telegramClient =
OkHttpTelegramClient(okhttpClientBuilder.build(), account.token, botConfig.getBaseApiTelegramUrl())
val remoteRepositories = config.mavenRepositories val remoteRepositories = config.mavenRepositories
.map { it.toRemoteRepository(proxyConfig) } .map { it.toRemoteRepository(proxyConfig) }
@ -209,15 +203,15 @@ internal class Launcher(
val bot = ScalaBot( val bot = ScalaBot(
BotDBMaker.getBotDbInstance(account), BotDBMaker.getBotDbInstance(account),
telegramClient, botOption,
extensionPackageFinders, extensionPackageFinders,
botConfig botConfig
) )
val botUser = bot.telegramClient.execute(GetMe()) val botUser = bot.execute(GetMe())
log.debug { "已验证 Bot Token 有效性, Bot Username: ${botUser.userName}" } log.debug { "已验证 Bot Token 有效性, Bot Username: ${botUser.userName}" }
botSessionMap[bot] = botApi.registerBot(botConfig.account.token, bot) botSessionMap[bot] = botApi.registerBot(bot)
log.info { "机器人 `${bot.botUsername}` 已启动." } log.info { "机器人 `${bot.botUsername}` 已启动." }
if (botConfig.autoUpdateCommandList) { if (botConfig.autoUpdateCommandList) {

View File

@ -7,8 +7,8 @@ import net.lamgc.scalabot.util.toHexString
import org.mapdb.DB import org.mapdb.DB
import org.mapdb.DBException import org.mapdb.DBException
import org.mapdb.DBMaker import org.mapdb.DBMaker
import org.telegram.telegrambots.abilitybots.api.db.DBContext import org.telegram.abilitybots.api.db.DBContext
import org.telegram.telegrambots.abilitybots.api.db.MapDBContext import org.telegram.abilitybots.api.db.MapDBContext
import java.io.File import java.io.File
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.security.MessageDigest import java.security.MessageDigest
@ -157,6 +157,7 @@ private abstract class FileDbAdapter(
val oldFile = oldDbAdapter.getBotDbFile(botAccount) val oldFile = oldDbAdapter.getBotDbFile(botAccount)
val newFile = getBotDbFile(botAccount) val newFile = getBotDbFile(botAccount)
try { try {
@Suppress("UnstableApiUsage")
Files.copy(oldFile, newFile) Files.copy(oldFile, newFile)
} catch (e: Exception) { } catch (e: Exception) {
if (newFile.exists()) { if (newFile.exists()) {

View File

@ -5,7 +5,7 @@ import net.lamgc.scalabot.extension.BotExtensionCreateOptions
import net.lamgc.scalabot.extension.BotExtensionFactory import net.lamgc.scalabot.extension.BotExtensionFactory
import net.lamgc.scalabot.util.getPriority import net.lamgc.scalabot.util.getPriority
import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.Artifact
import org.telegram.telegrambots.abilitybots.api.util.AbilityExtension import org.telegram.abilitybots.api.util.AbilityExtension
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.net.URL import java.net.URL
@ -126,7 +126,6 @@ internal class ExtensionLoader(
factory.createExtensionInstance( factory.createExtensionInstance(
bot, getExtensionDataFolder(extensionArtifact), bot, getExtensionDataFolder(extensionArtifact),
BotExtensionCreateOptions( BotExtensionCreateOptions(
bot.accountId,
bot.botConfig.proxy.copy() bot.botConfig.proxy.copy()
) )
) )
@ -159,7 +158,7 @@ internal class ExtensionLoader(
* 搜索指定构件坐标的依赖包. * 搜索指定构件坐标的依赖包.
* *
* 搜索扩展包将根据搜索器优先级从高到低依次搜索, 当某一个优先级的搜索器搜到扩展包后将停止搜索. * 搜索扩展包将根据搜索器优先级从高到低依次搜索, 当某一个优先级的搜索器搜到扩展包后将停止搜索.
* 可以根据不同优先级的搜索器, 配置扩展包的主用备用文件. * 可以根据不同优先级的搜索器, 配置扩展包的主用备用文件.
* *
* @return 返回各个搜索器返回的搜索结果. * @return 返回各个搜索器返回的搜索结果.
*/ */

View File

@ -26,7 +26,6 @@ import org.jdom2.input.SAXBuilder
import org.jdom2.xpath.XPathFactory import org.jdom2.xpath.XPathFactory
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.net.URI
import java.net.URL import java.net.URL
import java.net.URLClassLoader import java.net.URLClassLoader
import java.util.* import java.util.*
@ -397,6 +396,7 @@ internal class MavenRepositoryExtensionFinder(
/** /**
* Maven 中央仓库 Url. * Maven 中央仓库 Url.
*/ */
@Suppress("MemberVisibilityCanBePrivate")
const val MAVEN_CENTRAL_URL = "https://repo1.maven.org/maven2/" const val MAVEN_CENTRAL_URL = "https://repo1.maven.org/maven2/"
/** /**
@ -466,19 +466,17 @@ internal class MavenRepositoryExtensionFinder(
throw IllegalArgumentException("Unsupported FoundExtensionPackage type: $foundExtensionPackage") throw IllegalArgumentException("Unsupported FoundExtensionPackage type: $foundExtensionPackage")
} }
val urls = mutableSetOf<URI>() val urls = mutableSetOf<URL>()
for (dependency in foundExtensionPackage.dependencies) { for (dependency in foundExtensionPackage.dependencies) {
val dependencyFile = dependency.file ?: continue val dependencyFile = dependency.file ?: continue
urls.add(dependencyFile.toURI()) urls.add(dependencyFile.toURI().toURL())
} }
// 将依赖的 ClassLoader 与 ExtensionPackage 的 ClassLoader 分开 // 将依赖的 ClassLoader 与 ExtensionPackage 的 ClassLoader 分开
// 这么做可以防范依赖中隐藏的 SPI 注册, 避免安全隐患. // 这么做可以防范依赖中隐藏的 SPI 注册, 避免安全隐患.
val dependenciesUrlArray = urls.toTypedArray() val dependenciesUrlArray = urls.toTypedArray()
val dependenciesClassLoader = URLClassLoader( val dependenciesClassLoader = URLClassLoader(dependenciesUrlArray)
dependenciesUrlArray.map { it.toURL() }.toTypedArray()
)
return ExtensionClassLoader( return ExtensionClassLoader(
arrayOf(foundExtensionPackage.getPackageFile().toURI().toURL()), arrayOf(foundExtensionPackage.getPackageFile().toURI().toURL()),

View File

@ -6,42 +6,43 @@ import io.prometheus.client.Summary
import mu.KotlinLogging import mu.KotlinLogging
import net.lamgc.scalabot.config.BotConfig import net.lamgc.scalabot.config.BotConfig
import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.Artifact
import org.telegram.telegrambots.abilitybots.api.bot.AbilityBot import org.telegram.abilitybots.api.bot.AbilityBot
import org.telegram.telegrambots.abilitybots.api.db.DBContext import org.telegram.abilitybots.api.db.DBContext
import org.telegram.telegrambots.abilitybots.api.objects.Ability import org.telegram.abilitybots.api.objects.Ability
import org.telegram.telegrambots.abilitybots.api.toggle.BareboneToggle import org.telegram.abilitybots.api.toggle.BareboneToggle
import org.telegram.telegrambots.abilitybots.api.toggle.DefaultToggle import org.telegram.abilitybots.api.toggle.DefaultToggle
import org.telegram.telegrambots.bots.DefaultBotOptions
import org.telegram.telegrambots.meta.api.methods.commands.DeleteMyCommands import org.telegram.telegrambots.meta.api.methods.commands.DeleteMyCommands
import org.telegram.telegrambots.meta.api.methods.commands.SetMyCommands import org.telegram.telegrambots.meta.api.methods.commands.SetMyCommands
import org.telegram.telegrambots.meta.api.objects.Update import org.telegram.telegrambots.meta.api.objects.Update
import org.telegram.telegrambots.meta.api.objects.commands.BotCommand import org.telegram.telegrambots.meta.api.objects.commands.BotCommand
import org.telegram.telegrambots.meta.generics.TelegramClient
/** /**
* 可扩展 Bot. * 可扩展 Bot.
* @property creatorId 机器人所有人的 Telegram 用户 ID. 可通过联系部分机器人来获取该信息. * @property creatorId 机器人所有人的 Telegram 用户 Id. 可通过联系部分机器人来获取该信息.
* (e.g. [@userinfobot](http://t.me/userinfobot)) * (e.g. [@userinfobot](http://t.me/userinfobot))
* @param db 机器人数据库对象. 用于状态机等用途. * @param db 机器人数据库对象. 用于状态机等用途.
* @param options AbilityBot 设置对象.
* @property extensions 扩展坐标集合. * @property extensions 扩展坐标集合.
*/ */
@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
internal class ScalaBot( internal class ScalaBot(
db: DBContext, db: DBContext,
client: TelegramClient, options: DefaultBotOptions,
extensionFinders: Set<ExtensionPackageFinder>, extensionFinders: Set<ExtensionPackageFinder>,
val botConfig: BotConfig, val botConfig: BotConfig,
val accountId: Long = botConfig.account.id,
private val creatorId: Long = botConfig.account.creatorId, private val creatorId: Long = botConfig.account.creatorId,
val accountId: Long = botConfig.account.id,
val extensions: Set<Artifact> = botConfig.extensions val extensions: Set<Artifact> = botConfig.extensions
) : ) :
AbilityBot( AbilityBot(
client, botConfig.account.token,
botConfig.account.name, botConfig.account.name,
db, db,
if (botConfig.disableBuiltInAbility) if (botConfig.disableBuiltInAbility)
BareboneToggle() BareboneToggle()
else else
DefaultToggle() DefaultToggle(),
options
) { ) {
private val extensionLoader = ExtensionLoader( private val extensionLoader = ExtensionLoader(
@ -66,13 +67,13 @@ internal class ScalaBot(
override fun creatorId(): Long = creatorId override fun creatorId(): Long = creatorId
override fun consume(update: Update?) { override fun onUpdateReceived(update: Update?) {
botUpdateCounter.labels(botUsername, accountIdString).inc() botUpdateCounter.labels(botUsername, accountIdString).inc()
botUpdateGauge.labels(botUsername, accountIdString).inc() botUpdateGauge.labels(botUsername, accountIdString).inc()
val timer = updateProcessTime.labels(botUsername, accountIdString).startTimer() val timer = updateProcessTime.labels(botUsername, accountIdString).startTimer()
try { try {
super.consume(update) super.onUpdateReceived(update)
} catch (e: Exception) { } catch (e: Exception) {
exceptionHandlingCounter.labels(botUsername, accountIdString).inc() exceptionHandlingCounter.labels(botUsername, accountIdString).inc()
throw e throw e
@ -91,11 +92,11 @@ internal class ScalaBot(
* @return 更新成功返回 `true`. * @return 更新成功返回 `true`.
*/ */
fun updateCommandList(): Boolean { fun updateCommandList(): Boolean {
if (abilities == null) { if (abilities() == null) {
throw IllegalStateException("Abilities has not been initialized.") throw IllegalStateException("Abilities has not been initialized.")
} }
val botCommands = abilities.values.map { val botCommands = abilities().values.map {
val abilityInfo = if (it.info() == null || it.info().trim().isEmpty()) { val abilityInfo = if (it.info() == null || it.info().trim().isEmpty()) {
log.warn { "[Bot $botUsername] Ability `${it.name()}` 没有说明信息." } log.warn { "[Bot $botUsername] Ability `${it.name()}` 没有说明信息." }
"(The command has no description)" "(The command has no description)"
@ -111,10 +112,9 @@ internal class ScalaBot(
return true return true
} }
val setMyCommands = SetMyCommands.builder() val setMyCommands = SetMyCommands()
.commands(botCommands) setMyCommands.commands = botCommands
.build() return execute(DeleteMyCommands()) && execute(setMyCommands)
return telegramClient.execute(DeleteMyCommands()) && telegramClient.execute(setMyCommands)
} }
override fun onRegister() { override fun onRegister() {
@ -122,6 +122,10 @@ internal class ScalaBot(
onlineBotGauge.inc() onlineBotGauge.inc()
} }
override fun onClosing() {
onlineBotGauge.dec()
}
companion object { companion object {
@JvmStatic @JvmStatic
private val log = KotlinLogging.logger { } private val log = KotlinLogging.logger { }

View File

@ -1,29 +0,0 @@
package net.lamgc.scalabot.util
import java.util.regex.Matcher
import java.util.regex.Pattern
object TelegramBotAccounts {
private val botTokenPattern: Pattern = Pattern.compile("([1-9]\\d+):([A-Za-z\\d_-]{35,})")
/**
* 获取 AbilityBot 的账户 Id.
*
*
* 账户 Id 来自于 botToken , token 的格式为 "{AccountId}:{Secret}".
*
* 账户 Id 的真实性与 botToken 的有效性有关, 本方法并不会确保 botToken 的有效性, 一般情况下也无需考虑 Id 的有效性,
* 如果有需要, 可尝试通过调用 [org.telegram.telegrambots.meta.api.methods.GetMe] 来确保 botToken 的有效性.
*
* @param botToken 要获取账户 Id botToken 字符串.
* @return 返回 AbilityBot 的账户 Id.
* @throws IllegalArgumentException AbilityBot botToken 格式错误时抛出该异常.
*/
fun getBotAccountId(botToken: String): Long {
val matcher: Matcher = botTokenPattern.matcher(botToken)
require(matcher.matches()) { "Invalid token format." }
return matcher.group(1).toLong()
}
}

View File

@ -10,9 +10,9 @@ import net.lamgc.scalabot.config.ProxyConfig
import net.lamgc.scalabot.config.ProxyType import net.lamgc.scalabot.config.ProxyType
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.api.io.TempDir
import org.telegram.telegrambots.bots.DefaultBotOptions
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.net.Proxy
import java.net.URL import java.net.URL
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
@ -23,7 +23,7 @@ internal class AppPathsTest {
@Test @Test
fun `Consistency check`() { fun `Consistency check`() {
for (path in AppPaths.entries) { for (path in AppPaths.values()) {
assertEquals( assertEquals(
File(path.path).canonicalPath, File(path.path).canonicalPath,
path.file.canonicalPath, path.file.canonicalPath,
@ -226,17 +226,17 @@ internal class AppPathsTest {
@Test @Test
fun `ProxyType_toTelegramBotsType test`() { fun `ProxyType_toTelegramBotsType test`() {
val expectTypeMapping = mapOf( val expectTypeMapping = mapOf(
ProxyType.NO_PROXY to null, ProxyType.NO_PROXY to DefaultBotOptions.ProxyType.NO_PROXY,
ProxyType.SOCKS5 to Proxy.Type.SOCKS, ProxyType.SOCKS5 to DefaultBotOptions.ProxyType.SOCKS5,
ProxyType.SOCKS4 to Proxy.Type.SOCKS, ProxyType.SOCKS4 to DefaultBotOptions.ProxyType.SOCKS4,
ProxyType.HTTP to Proxy.Type.HTTP, ProxyType.HTTP to DefaultBotOptions.ProxyType.HTTP,
ProxyType.HTTPS to Proxy.Type.HTTP ProxyType.HTTPS to DefaultBotOptions.ProxyType.HTTP
) )
for (proxyType in ProxyType.entries) { for (proxyType in ProxyType.values()) {
assertEquals( assertEquals(
expectTypeMapping[proxyType], expectTypeMapping[proxyType],
proxyType.toJavaProxyType(), proxyType.toTelegramBotsType(),
"ProxyType 转换失败." "ProxyType 转换失败."
) )
} }
@ -251,7 +251,7 @@ internal class AppPathsTest {
ProxyType.HTTP, ProxyType.HTTP,
ProxyType.HTTPS ProxyType.HTTPS
) )
for (proxyType in ProxyType.entries) { for (proxyType in ProxyType.values()) {
val proxyConfig = ProxyConfig(proxyType, host, port) val proxyConfig = ProxyConfig(proxyType, host, port)
val aetherProxy = proxyConfig.toAetherProxy() val aetherProxy = proxyConfig.toAetherProxy()
if (expectNotNullProxyType.contains(proxyType)) { if (expectNotNullProxyType.contains(proxyType)) {
@ -337,7 +337,7 @@ internal class AppPathsTest {
assertTrue(initialFiles(), "方法未能提醒用户编辑初始配置文件.") assertTrue(initialFiles(), "方法未能提醒用户编辑初始配置文件.")
for (path in AppPaths.entries) { for (path in AppPaths.values()) {
assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}") assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}")
if (path.file.isFile) { if (path.file.isFile) {
assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}") assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}")
@ -347,7 +347,7 @@ internal class AppPathsTest {
assertFalse(initialFiles(), "方法试图在配置已初始化的情况下提醒用户编辑初始配置文件.") assertFalse(initialFiles(), "方法试图在配置已初始化的情况下提醒用户编辑初始配置文件.")
for (path in AppPaths.entries) { for (path in AppPaths.values()) {
assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}") assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}")
if (path.file.isFile) { if (path.file.isFile) {
assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}") assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}")
@ -358,7 +358,7 @@ internal class AppPathsTest {
assertTrue(AppPaths.CONFIG_APPLICATION.file.delete(), "config.json 删除失败.") assertTrue(AppPaths.CONFIG_APPLICATION.file.delete(), "config.json 删除失败.")
assertFalse(initialFiles(), "方法试图在部分配置已初始化的情况下提醒用户编辑初始配置文件.") assertFalse(initialFiles(), "方法试图在部分配置已初始化的情况下提醒用户编辑初始配置文件.")
for (path in AppPaths.entries) { for (path in AppPaths.values()) {
assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}") assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}")
if (path.file.isFile) { if (path.file.isFile) {
assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}") assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}")
@ -369,7 +369,7 @@ internal class AppPathsTest {
assertTrue(AppPaths.CONFIG_BOT.file.delete(), "bot.json 删除失败.") assertTrue(AppPaths.CONFIG_BOT.file.delete(), "bot.json 删除失败.")
assertFalse(initialFiles(), "方法试图在部分配置已初始化的情况下提醒用户编辑初始配置文件.") assertFalse(initialFiles(), "方法试图在部分配置已初始化的情况下提醒用户编辑初始配置文件.")
for (path in AppPaths.entries) { for (path in AppPaths.values()) {
assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}") assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}")
if (path.file.isFile) { if (path.file.isFile) {
assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}") assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}")
@ -384,7 +384,7 @@ internal class AppPathsTest {
"在主要配置文件(config.json 和 bot.json)不存在的情况下初始化文件后, 方法未能提醒用户编辑初始配置文件." "在主要配置文件(config.json 和 bot.json)不存在的情况下初始化文件后, 方法未能提醒用户编辑初始配置文件."
) )
for (path in AppPaths.entries) { for (path in AppPaths.values()) {
assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}") assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}")
if (path.file.isFile) { if (path.file.isFile) {
assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}") assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}")

View File

@ -1,27 +0,0 @@
package util
import net.lamgc.scalabot.util.TelegramBotAccounts
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test
class TelegramBotAccountsTest {
@Test
fun getBotAccountIdTest() {
val expectToken = "1234567890:AAHXcNDBRZTKfyPED5Gi3PZDIKPOM6xhxwo"
val actual: Long = TelegramBotAccounts.getBotAccountId(expectToken)
assertEquals(1234567890, actual)
val badTokenA = "12c34d56a7890:AAHXcNDBRZTKfyPED5Gi3PZDIKPOM6xhxwo"
assertThrows(
IllegalArgumentException::class.java
) { TelegramBotAccounts.getBotAccountId(badTokenA) }
val badTokenB = "12c34d56a7890AAHXcNDBRZTKfyPED5Gi3PZDIKPOM6xhxwo"
assertThrows(
IllegalArgumentException::class.java
) { TelegramBotAccounts.getBotAccountId(badTokenB) }
}
}

View File

@ -1,8 +1,8 @@
package net.lamgc.scalabot.simple; package net.lamgc.scalabot.simple;
import org.telegram.telegrambots.abilitybots.api.bot.BaseAbilityBot; import org.telegram.abilitybots.api.bot.BaseAbilityBot;
import org.telegram.telegrambots.abilitybots.api.objects.*; import org.telegram.abilitybots.api.objects.*;
import org.telegram.telegrambots.abilitybots.api.util.AbilityExtension; import org.telegram.abilitybots.api.util.AbilityExtension;
public class SayHelloExtension implements AbilityExtension { public class SayHelloExtension implements AbilityExtension {
@ -27,7 +27,7 @@ public class SayHelloExtension implements AbilityExtension {
String msg = "Hello! " + ctx.user().getUserName() + String msg = "Hello! " + ctx.user().getUserName() +
" ( " + ctx.user().getId() + " ) [ " + ctx.user().getLanguageCode() + " ]" + "\n" + " ( " + ctx.user().getId() + " ) [ " + ctx.user().getLanguageCode() + " ]" + "\n" +
"Current Chat ID: " + ctx.chatId(); "Current Chat ID: " + ctx.chatId();
ctx.bot().getSilent().send(msg, ctx.chatId()); ctx.bot().silent().send(msg, ctx.chatId());
}) })
.build(); .build();
} }
@ -36,13 +36,13 @@ public class SayHelloExtension implements AbilityExtension {
* 更具特色的 `Say hello`. * 更具特色的 `Say hello`.
*/ */
public Ability test() { public Ability test() {
ReplyFlow botHello = ReplyFlow.builder(bot.getDb()) ReplyFlow botHello = ReplyFlow.builder(bot.db())
.enableStats("say_hello") .enableStats("say_hello")
.action((bot, upd) -> bot.getSilent().send("What is u name?", upd.getMessage().getChatId())) .action((bot, upd) -> bot.silent().send("What is u name?", upd.getMessage().getChatId()))
.onlyIf(update -> update.hasMessage() .onlyIf(update -> update.hasMessage()
&& update.getMessage().hasText() && update.getMessage().hasText()
&& "hello".equalsIgnoreCase(update.getMessage().getText())) && "hello".equalsIgnoreCase(update.getMessage().getText()))
.next(Reply.of((bot, upd) -> bot.getSilent() .next(Reply.of((bot, upd) -> bot.silent()
.send("OK! You name is " + upd.getMessage().getText().substring("my name is ".length()), upd.getMessage().getChatId()), .send("OK! You name is " + upd.getMessage().getText().substring("my name is ".length()), upd.getMessage().getChatId()),
upd -> upd.hasMessage() upd -> upd.hasMessage()
&& upd.getMessage().hasText() && upd.getMessage().hasText()
@ -55,7 +55,7 @@ public class SayHelloExtension implements AbilityExtension {
.locality(Locality.ALL) .locality(Locality.ALL)
.privacy(Privacy.PUBLIC) .privacy(Privacy.PUBLIC)
.enableStats() .enableStats()
.action(ctx -> ctx.bot().getSilent().send("Hello!", ctx.chatId())) .action(ctx -> ctx.bot().silent().send("Hello!", ctx.chatId()))
.reply(botHello) .reply(botHello)
.build(); .build();
} }

View File

@ -2,8 +2,8 @@ package net.lamgc.scalabot.simple;
import net.lamgc.scalabot.extension.BotExtensionCreateOptions; import net.lamgc.scalabot.extension.BotExtensionCreateOptions;
import net.lamgc.scalabot.extension.BotExtensionFactory; import net.lamgc.scalabot.extension.BotExtensionFactory;
import org.telegram.telegrambots.abilitybots.api.bot.BaseAbilityBot; import org.telegram.abilitybots.api.bot.BaseAbilityBot;
import org.telegram.telegrambots.abilitybots.api.util.AbilityExtension; import org.telegram.abilitybots.api.util.AbilityExtension;
import java.io.File; import java.io.File;

View File

@ -0,0 +1,9 @@
public abstract interface class net/lamgc/scalabot/extension/BotExtensionFactory {
public abstract fun createExtensionInstance (Lorg/telegram/abilitybots/api/bot/BaseAbilityBot;Ljava/io/File;)Lorg/telegram/abilitybots/api/util/AbilityExtension;
}
public class net/lamgc/scalabot/extension/util/AbilityBots {
public static fun cancelReplyState (Lorg/telegram/abilitybots/api/bot/BaseAbilityBot;J)Z
public static fun getBotAccountId (Lorg/telegram/abilitybots/api/bot/BaseAbilityBot;)J
}

View File

@ -3,16 +3,16 @@ plugins {
jacoco jacoco
`maven-publish` `maven-publish`
signing signing
id("org.jetbrains.kotlinx.binary-compatibility-validator")
} }
dependencies { dependencies {
implementation("commons-codec:commons-codec:1.16.1") implementation("commons-codec:commons-codec:1.16.1")
api("org.telegram:telegrambots-abilities:8.0.0") api("org.telegram:telegrambots-abilities:6.9.7.1")
api(project(":scalabot-meta")) api(project(":scalabot-meta"))
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testImplementation("org.mockito:mockito-core:5.11.0") testImplementation("org.mockito:mockito-core:5.11.0")
testImplementation("org.telegram:telegrambots-client:8.0.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
} }
@ -25,8 +25,8 @@ tasks.withType<Javadoc> {
java { java {
withJavadocJar() withJavadocJar()
withSourcesJar() withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_11
} }
tasks.test { tasks.test {

View File

@ -10,29 +10,13 @@ import net.lamgc.scalabot.config.ProxyConfig;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class BotExtensionCreateOptions { public class BotExtensionCreateOptions {
private final long botAccountId;
private final ProxyConfig proxy; private final ProxyConfig proxy;
public BotExtensionCreateOptions(long botAccountId, ProxyConfig proxy) { public BotExtensionCreateOptions(ProxyConfig proxy) {
this.botAccountId = botAccountId;
this.proxy = proxy; this.proxy = proxy;
} }
/**
* 获取 Bot 使用的代理信息.
*
* @return 返回 Bot TelegramClient 所使用的代理配置.
*/
public ProxyConfig getProxy() { public ProxyConfig getProxy() {
return proxy; return proxy;
} }
/**
* 获取 Bot 的账户 Id.
*
* @return 返回 Bot 的账户 Id.
*/
public long getBotAccountId() {
return botAccountId;
}
} }

View File

@ -1,8 +1,7 @@
package net.lamgc.scalabot.extension; package net.lamgc.scalabot.extension;
import org.telegram.telegrambots.abilitybots.api.bot.BaseAbilityBot; import org.telegram.abilitybots.api.bot.BaseAbilityBot;
import org.telegram.telegrambots.abilitybots.api.db.DBContext; import org.telegram.abilitybots.api.util.AbilityExtension;
import org.telegram.telegrambots.abilitybots.api.util.AbilityExtension;
import java.io.File; import java.io.File;
@ -10,7 +9,7 @@ import java.io.File;
* 该接口用于为指定的 {@link BaseAbilityBot} 创建扩展. * 该接口用于为指定的 {@link BaseAbilityBot} 创建扩展.
* *
* <p> 由于 AbilityExtension 无法直接获取 {@link BaseAbilityBot} * <p> 由于 AbilityExtension 无法直接获取 {@link BaseAbilityBot}
* 数据库对象 {@link DBContext}, * 数据库对象 {@link org.telegram.abilitybots.api.db.DBContext},
* 所以将通过该接口工厂来创建扩展对象. * 所以将通过该接口工厂来创建扩展对象.
* *
* @author LamGC * @author LamGC
@ -21,7 +20,30 @@ public interface BotExtensionFactory {
/** /**
* 为给定的 {@link BaseAbilityBot} 对象创建扩展. * 为给定的 {@link BaseAbilityBot} 对象创建扩展.
* *
* <p> 如扩展无使用 {@link DBContext} 的话, * <p> 如扩展无使用 {@link org.telegram.abilitybots.api.db.DBContext} 的话,
* 也可以返回扩展单例, 因为 AbilityBot 本身并不禁止多个机器人共用一个扩展对象
* (AbilityBot 只是调用了扩展中的方法来创建 Ability 对象).
*
* @param bot 机器人对象.
* @param shareDataFolder ScalaBot App 为扩展提供的共享数据目录.
* <p>路径格式为:
* <pre> $DATA_ROOT/data/extensions/{GroupId}/{ArtifactId}</pre>
* <b>同一个扩展包的 Factory</b> 接收到的共享数据目录<b>都是一样的</b>,
* 建议将数据存储在数据目录中, 便于数据的存储管理.
* @return 返回为该 Bot 对象创建的扩展对象, 如果不希望为该机器人提供扩展, 可返回 {@code null}.
* @deprecated 请使用 {@link #createExtensionInstance(BaseAbilityBot, File, BotExtensionCreateOptions)},
* 该方法最迟在 1.0.0 正式版中移除.
* @since 0.0.1
*/
@Deprecated(since = "0.7.0", forRemoval = true)
default AbilityExtension createExtensionInstance(BaseAbilityBot bot, File shareDataFolder) {
throw new UnsupportedOperationException("The method has not been implemented.");
}
/**
* 为给定的 {@link BaseAbilityBot} 对象创建扩展.
*
* <p> 如扩展无使用 {@link org.telegram.abilitybots.api.db.DBContext} 的话,
* 也可以返回扩展单例, 因为 AbilityBot 本身并不禁止多个机器人共用一个扩展对象 * 也可以返回扩展单例, 因为 AbilityBot 本身并不禁止多个机器人共用一个扩展对象
* (AbilityBot 只是调用了扩展中的方法来创建 Ability 对象). * (AbilityBot 只是调用了扩展中的方法来创建 Ability 对象).
* *
@ -35,6 +57,8 @@ public interface BotExtensionFactory {
* @return 返回为该 Bot 对象创建的扩展对象, 如果不希望为该机器人提供扩展, 可返回 {@code null}. * @return 返回为该 Bot 对象创建的扩展对象, 如果不希望为该机器人提供扩展, 可返回 {@code null}.
* @since 0.7.0 * @since 0.7.0
*/ */
AbilityExtension createExtensionInstance(BaseAbilityBot bot, File shareDataFolder, BotExtensionCreateOptions options); default AbilityExtension createExtensionInstance(BaseAbilityBot bot, File shareDataFolder, BotExtensionCreateOptions options) {
return createExtensionInstance(bot, shareDataFolder);
}
} }

View File

@ -1,14 +1,40 @@
package net.lamgc.scalabot.extension.util; package net.lamgc.scalabot.extension.util;
import org.telegram.telegrambots.abilitybots.api.bot.BaseAbilityBot; import org.telegram.abilitybots.api.bot.BaseAbilityBot;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class AbilityBots { public class AbilityBots {
private final static Pattern botTokenPattern = Pattern.compile("([1-9]\\d+):([A-Za-z\\d_-]{35,})");
private AbilityBots() { private AbilityBots() {
} }
/**
* 获取 AbilityBot 的账户 Id.
*
* <p> 账户 Id 来自于 botToken , token 的格式为 "[AccountId]:[Secret]".
* <p> 账户 Id 的真实性与 botToken 的有效性有关, 本方法并不会确保 botToken 的有效性, 一般情况下也无需考虑 Id 的有效性,
* 如果有需要, 可尝试通过调用 {@link org.telegram.telegrambots.meta.api.methods.GetMe} 来确保 botToken 的有效性.
*
* @param bot 要获取账户 Id AbilityBot 对象.
* @return 返回 AbilityBot 的账户 Id.
* @throws IllegalArgumentException AbilityBot botToken 格式错误时抛出该异常.
*/
@SuppressWarnings("deprecation")
public static long getBotAccountId(BaseAbilityBot bot) {
// 根据文档说明, 弃用仅针对重写方法, 使用该方法并无大碍.
String botToken = bot.getBotToken();
Matcher matcher = botTokenPattern.matcher(botToken);
if (!matcher.matches()) {
throw new IllegalArgumentException("Invalid token format.");
}
return Long.parseLong(matcher.group(1));
}
/** /**
* 取消某一对话的状态机. * 取消某一对话的状态机.
* *
@ -17,7 +43,7 @@ public final class AbilityBots {
* @return 如果状态机存在, 则删除后返回 true, 不存在(未开启任何状态机, 即没有触发任何 Reply)则返回 false. * @return 如果状态机存在, 则删除后返回 true, 不存在(未开启任何状态机, 即没有触发任何 Reply)则返回 false.
*/ */
public static boolean cancelReplyState(BaseAbilityBot bot, long chatId) { public static boolean cancelReplyState(BaseAbilityBot bot, long chatId) {
Map<Long, Integer> stateMap = bot.getDb().getMap("user_state_replies"); Map<Long, Integer> stateMap = bot.db().getMap("user_state_replies");
if (!stateMap.containsKey(chatId)) { if (!stateMap.containsKey(chatId)) {
return false; return false;
} }

View File

@ -2,35 +2,22 @@ package net.lamgc.scalabot.extension.util;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mapdb.DBMaker; import org.mapdb.DBMaker;
import org.telegram.telegrambots.abilitybots.api.bot.AbilityBot; import org.telegram.abilitybots.api.bot.AbilityBot;
import org.telegram.telegrambots.abilitybots.api.bot.BaseAbilityBot; import org.telegram.abilitybots.api.bot.BaseAbilityBot;
import org.telegram.telegrambots.abilitybots.api.db.MapDBContext; import org.telegram.abilitybots.api.db.MapDBContext;
import org.telegram.telegrambots.abilitybots.api.objects.*; import org.telegram.abilitybots.api.objects.*;
import org.telegram.telegrambots.abilitybots.api.sender.SilentSender; import org.telegram.abilitybots.api.sender.SilentSender;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update; import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.User; import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.api.objects.message.Message;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
public class AbilityBotsTest { public class AbilityBotsTest {
public static final User USER = User.builder() public static final User USER = new User(1L, "first", false, "last", "username", null, false, false, false, false, false);
.userName("username") public static final User CREATOR = new User(1337L, "creatorFirst", false, "creatorLast", "creatorUsername", null, false, false, false, false, false);
.id(1L)
.firstName("first")
.lastName("last")
.isBot(false)
.build();
public static final User CREATOR = User.builder()
.userName("creatorUsername")
.id(1337L)
.firstName("creatorFirst")
.lastName("creatorLast")
.isBot(false)
.build();
static Update mockFullUpdate(BaseAbilityBot bot, User user, String args) { static Update mockFullUpdate(BaseAbilityBot bot, User user, String args) {
bot.users().put(USER.getId(), USER); bot.users().put(USER.getId(), USER);
@ -52,51 +39,58 @@ public class AbilityBotsTest {
return update; return update;
} }
@Test
void getBotAccountIdTest() {
String expectToken = "1234567890:AAHXcNDBRZTKfyPED5Gi3PZDIKPOM6xhxwo";
long actual = AbilityBots.getBotAccountId(new TestingAbilityBot(expectToken, "test"));
assertEquals(1234567890, actual);
String badTokenA = "12c34d56a7890:AAHXcNDBRZTKfyPED5Gi3PZDIKPOM6xhxwo";
assertThrows(IllegalArgumentException.class, () ->
AbilityBots.getBotAccountId(new TestingAbilityBot(badTokenA, "test")));
String badTokenB = "12c34d56a7890AAHXcNDBRZTKfyPED5Gi3PZDIKPOM6xhxwo";
assertThrows(IllegalArgumentException.class, () ->
AbilityBots.getBotAccountId(new TestingAbilityBot(badTokenB, "test")));
}
@Test @Test
void cancelReplyStateTest() { void cancelReplyStateTest() {
User userA = User.builder() User userA = new User(10001L, "first", false, "last", "username", null, false, false, false, false, false);
.id(10001L) User userB = new User(10101L, "first", false, "last", "username", null, false, false, false, false, false);
.firstName("first")
.lastName("last")
.userName("username")
.isBot(false)
.build();
User userB = User.builder()
.id(10101L)
.firstName("first")
.lastName("last")
.userName("username")
.isBot(false)
.build();
SilentSender silent = mock(SilentSender.class); SilentSender silent = mock(SilentSender.class);
BaseAbilityBot bot = new TestingAbilityBot("", silent); BaseAbilityBot bot = new TestingAbilityBot("", "", silent);
bot.onRegister(); bot.onRegister();
bot.consume(mockFullUpdate(bot, userA, "/set_reply")); bot.onUpdateReceived(mockFullUpdate(bot, userA, "/set_reply"));
verify(silent, times(1)).send("Reply set!", userA.getId()); verify(silent, times(1)).send("Reply set!", userA.getId());
bot.consume(mockFullUpdate(bot, userA, "reply_01")); bot.onUpdateReceived(mockFullUpdate(bot, userA, "reply_01"));
verify(silent, times(1)).send("Reply 01", userA.getId()); verify(silent, times(1)).send("Reply 01", userA.getId());
assertTrue(AbilityBots.cancelReplyState(bot, userA.getId())); assertTrue(AbilityBots.cancelReplyState(bot, userA.getId()));
bot.consume(mockFullUpdate(bot, userA, "reply_02")); bot.onUpdateReceived(mockFullUpdate(bot, userA, "reply_02"));
verify(silent, never()).send("Reply 02", userA.getId()); verify(silent, never()).send("Reply 02", userA.getId());
assertFalse(AbilityBots.cancelReplyState(bot, userB.getId())); assertFalse(AbilityBots.cancelReplyState(bot, userB.getId()));
silent = mock(SilentSender.class); silent = mock(SilentSender.class);
bot = new TestingAbilityBot("", silent); bot = new TestingAbilityBot("", "", silent);
bot.onRegister(); bot.onRegister();
bot.consume(mockFullUpdate(bot, userA, "/set_reply")); bot.onUpdateReceived(mockFullUpdate(bot, userA, "/set_reply"));
verify(silent, times(1)).send("Reply set!", userA.getId()); verify(silent, times(1)).send("Reply set!", userA.getId());
bot.consume(mockFullUpdate(bot, userA, "reply_01")); bot.onUpdateReceived(mockFullUpdate(bot, userA, "reply_01"));
verify(silent, times(1)).send("Reply 01", userA.getId()); verify(silent, times(1)).send("Reply 01", userA.getId());
bot.consume(mockFullUpdate(bot, userA, "reply_02")); bot.onUpdateReceived(mockFullUpdate(bot, userA, "reply_02"));
verify(silent, times(1)).send("Reply 02", userA.getId()); verify(silent, times(1)).send("Reply 02", userA.getId());
} }
public static class TestingAbilityBot extends AbilityBot { public static class TestingAbilityBot extends AbilityBot {
public TestingAbilityBot(String botUsername, SilentSender silentSender) { public TestingAbilityBot(String botToken, String botUsername) {
super(new NoOpTelegramClient(), botUsername, new MapDBContext(DBMaker.heapDB().make())); super(botToken, botUsername, new MapDBContext(DBMaker.heapDB().make()));
}
public TestingAbilityBot(String botToken, String botUsername, SilentSender silentSender) {
super(botToken, botUsername, new MapDBContext(DBMaker.heapDB().make()));
this.silent = silentSender; this.silent = silentSender;
} }
@ -107,12 +101,12 @@ public class AbilityBotsTest {
.enableStats() .enableStats()
.locality(Locality.ALL) .locality(Locality.ALL)
.privacy(Privacy.PUBLIC) .privacy(Privacy.PUBLIC)
.action(ctx -> ctx.bot().getSilent().send("Reply set!", ctx.chatId())) .action(ctx -> ctx.bot().silent().send("Reply set!", ctx.chatId()))
.reply(ReplyFlow.builder(getDb()) .reply(ReplyFlow.builder(db())
.action((bot, upd) -> bot.getSilent().send("Reply 01", upd.getMessage().getChatId())) .action((bot, upd) -> bot.silent().send("Reply 01", upd.getMessage().getChatId()))
.onlyIf(upd -> upd.hasMessage() && upd.getMessage().getText().equals("reply_01")) .onlyIf(upd -> upd.hasMessage() && upd.getMessage().getText().equals("reply_01"))
.next(Reply.of((bot, upd) -> .next(Reply.of((bot, upd) ->
bot.getSilent().send("Reply 02", upd.getMessage().getChatId()), bot.silent().send("Reply 02", upd.getMessage().getChatId()),
upd -> upd.hasMessage() && upd.getMessage().getText().equals("reply_02"))) upd -> upd.hasMessage() && upd.getMessage().getText().equals("reply_02")))
.build() .build()
) )

View File

@ -1,229 +0,0 @@
package net.lamgc.scalabot.extension.util;
import org.telegram.telegrambots.meta.api.methods.botapimethods.BotApiMethod;
import org.telegram.telegrambots.meta.api.methods.groupadministration.SetChatPhoto;
import org.telegram.telegrambots.meta.api.methods.send.*;
import org.telegram.telegrambots.meta.api.methods.stickers.*;
import org.telegram.telegrambots.meta.api.methods.updates.SetWebhook;
import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageMedia;
import org.telegram.telegrambots.meta.api.objects.File;
import org.telegram.telegrambots.meta.api.objects.message.Message;
import org.telegram.telegrambots.meta.generics.TelegramClient;
import java.io.InputStream;
import java.io.Serializable;
import java.util.List;
import java.util.concurrent.CompletableFuture;
class NoOpTelegramClient implements TelegramClient {
@Override
public <T extends Serializable, Method extends BotApiMethod<T>> CompletableFuture<T> executeAsync(Method method) {
return null;
}
@Override
public <T extends Serializable, Method extends BotApiMethod<T>> T execute(Method method) {
return null;
}
@Override
public Message execute(SendDocument sendDocument) {
return null;
}
@Override
public Message execute(SendPhoto sendPhoto) {
return null;
}
@Override
public Boolean execute(SetWebhook setWebhook) {
return null;
}
@Override
public Message execute(SendVideo sendVideo) {
return null;
}
@Override
public Message execute(SendVideoNote sendVideoNote) {
return null;
}
@Override
public Message execute(SendSticker sendSticker) {
return null;
}
@Override
public Message execute(SendAudio sendAudio) {
return null;
}
@Override
public Message execute(SendVoice sendVoice) {
return null;
}
@Override
public List<Message> execute(SendMediaGroup sendMediaGroup) {
return List.of();
}
@Override
public List<Message> execute(SendPaidMedia sendPaidMedia) {
return List.of();
}
@Override
public Boolean execute(SetChatPhoto setChatPhoto) {
return null;
}
@Override
public Boolean execute(AddStickerToSet addStickerToSet) {
return null;
}
@Override
public Boolean execute(ReplaceStickerInSet replaceStickerInSet) {
return null;
}
@Override
public Boolean execute(SetStickerSetThumbnail setStickerSetThumbnail) {
return null;
}
@Override
public Boolean execute(CreateNewStickerSet createNewStickerSet) {
return null;
}
@Override
public File execute(UploadStickerFile uploadStickerFile) {
return null;
}
@Override
public Serializable execute(EditMessageMedia editMessageMedia) {
return null;
}
@Override
public java.io.File downloadFile(File file) {
return null;
}
@Override
public InputStream downloadFileAsStream(File file) {
return null;
}
@Override
public Message execute(SendAnimation sendAnimation) {
return null;
}
@Override
public CompletableFuture<Message> executeAsync(SendDocument sendDocument) {
return null;
}
@Override
public CompletableFuture<Message> executeAsync(SendPhoto sendPhoto) {
return null;
}
@Override
public CompletableFuture<Boolean> executeAsync(SetWebhook setWebhook) {
return null;
}
@Override
public CompletableFuture<Message> executeAsync(SendVideo sendVideo) {
return null;
}
@Override
public CompletableFuture<Message> executeAsync(SendVideoNote sendVideoNote) {
return null;
}
@Override
public CompletableFuture<Message> executeAsync(SendSticker sendSticker) {
return null;
}
@Override
public CompletableFuture<Message> executeAsync(SendAudio sendAudio) {
return null;
}
@Override
public CompletableFuture<Message> executeAsync(SendVoice sendVoice) {
return null;
}
@Override
public CompletableFuture<List<Message>> executeAsync(SendMediaGroup sendMediaGroup) {
return null;
}
@Override
public CompletableFuture<List<Message>> executeAsync(SendPaidMedia sendPaidMedia) {
return null;
}
@Override
public CompletableFuture<Boolean> executeAsync(SetChatPhoto setChatPhoto) {
return null;
}
@Override
public CompletableFuture<Boolean> executeAsync(AddStickerToSet addStickerToSet) {
return null;
}
@Override
public CompletableFuture<Boolean> executeAsync(ReplaceStickerInSet replaceStickerInSet) {
return null;
}
@Override
public CompletableFuture<Boolean> executeAsync(SetStickerSetThumbnail setStickerSetThumbnail) {
return null;
}
@Override
public CompletableFuture<Boolean> executeAsync(CreateNewStickerSet createNewStickerSet) {
return null;
}
@Override
public CompletableFuture<File> executeAsync(UploadStickerFile uploadStickerFile) {
return null;
}
@Override
public CompletableFuture<Serializable> executeAsync(EditMessageMedia editMessageMedia) {
return null;
}
@Override
public CompletableFuture<Message> executeAsync(SendAnimation sendAnimation) {
return null;
}
@Override
public CompletableFuture<java.io.File> downloadFileAsync(File file) {
return null;
}
@Override
public CompletableFuture<InputStream> downloadFileAsStreamAsync(File file) {
return null;
}
}

View File

@ -48,7 +48,6 @@ public final class net/lamgc/scalabot/config/BotConfig {
public fun equals (Ljava/lang/Object;)Z public fun equals (Ljava/lang/Object;)Z
public final fun getAccount ()Lnet/lamgc/scalabot/config/BotAccount; public final fun getAccount ()Lnet/lamgc/scalabot/config/BotAccount;
public final fun getAutoUpdateCommandList ()Z public final fun getAutoUpdateCommandList ()Z
public final fun getBaseApiTelegramUrl ()Lorg/telegram/telegrambots/meta/TelegramUrl;
public final fun getBaseApiUrl ()Ljava/lang/String; public final fun getBaseApiUrl ()Ljava/lang/String;
public final fun getDisableBuiltInAbility ()Z public final fun getDisableBuiltInAbility ()Z
public final fun getEnabled ()Z public final fun getEnabled ()Z
@ -58,10 +57,6 @@ public final class net/lamgc/scalabot/config/BotConfig {
public fun toString ()Ljava/lang/String; public fun toString ()Ljava/lang/String;
} }
public final class net/lamgc/scalabot/config/ConfigsKt {
public static final fun getDefaultTelegramApiUrl ()Ljava/lang/String;
}
public final class net/lamgc/scalabot/config/MavenRepositoryConfig { public final class net/lamgc/scalabot/config/MavenRepositoryConfig {
public fun <init> (Ljava/lang/String;Ljava/net/URL;Lorg/eclipse/aether/repository/Proxy;Ljava/lang/String;ZZLorg/eclipse/aether/repository/Authentication;)V public fun <init> (Ljava/lang/String;Ljava/net/URL;Lorg/eclipse/aether/repository/Proxy;Ljava/lang/String;ZZLorg/eclipse/aether/repository/Authentication;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/net/URL;Lorg/eclipse/aether/repository/Proxy;Ljava/lang/String;ZZLorg/eclipse/aether/repository/Authentication;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (Ljava/lang/String;Ljava/net/URL;Lorg/eclipse/aether/repository/Proxy;Ljava/lang/String;ZZLorg/eclipse/aether/repository/Authentication;ILkotlin/jvm/internal/DefaultConstructorMarker;)V

View File

@ -1,9 +1,7 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
kotlin("jvm") kotlin("jvm")
id("org.jetbrains.kotlinx.kover") id("org.jetbrains.kotlinx.kover")
id("org.jetbrains.dokka") version "1.9.20" id("org.jetbrains.dokka") version "1.9.10"
`maven-publish` `maven-publish`
signing signing
id("org.jetbrains.kotlinx.binary-compatibility-validator") id("org.jetbrains.kotlinx.binary-compatibility-validator")
@ -14,7 +12,7 @@ dependencies {
api("org.eclipse.aether:aether-api:$aetherVersion") api("org.eclipse.aether:aether-api:$aetherVersion")
implementation("org.eclipse.aether:aether-util:$aetherVersion") implementation("org.eclipse.aether:aether-util:$aetherVersion")
implementation("org.telegram:telegrambots-meta:8.0.0") implementation("org.telegram:telegrambots-meta:6.9.7.1")
api("com.google.code.gson:gson:2.10.1") api("com.google.code.gson:gson:2.10.1")
@ -27,16 +25,16 @@ dependencies {
} }
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach { tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions { kotlinOptions {
jvmTarget = JvmTarget.JVM_17 jvmTarget = "11"
} }
} }
java { java {
withJavadocJar() withJavadocJar()
withSourcesJar() withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_11
} }
tasks.withType<AbstractArchiveTask>().configureEach { tasks.withType<AbstractArchiveTask>().configureEach {

View File

@ -3,8 +3,7 @@ package net.lamgc.scalabot.config
import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Authentication
import org.eclipse.aether.repository.Proxy import org.eclipse.aether.repository.Proxy
import org.telegram.telegrambots.meta.TelegramUrl import org.telegram.telegrambots.meta.ApiConstants
import java.net.URI
import java.net.URL import java.net.URL
/** /**
@ -27,13 +26,6 @@ data class BotAccount(
get() = token.substringBefore(":").toLong() get() = token.substringBefore(":").toLong()
} }
val defaultTelegramApiUrl: String = URL(
TelegramUrl.DEFAULT_URL.schema,
TelegramUrl.DEFAULT_URL.host,
TelegramUrl.DEFAULT_URL.port,
"/"
).toExternalForm()
/** /**
* 机器人配置. * 机器人配置.
* *
@ -64,22 +56,8 @@ data class BotConfig(
*/ */
val extensions: Set<Artifact> = emptySet(), val extensions: Set<Artifact> = emptySet(),
val proxy: ProxyConfig = ProxyConfig(type = ProxyType.NO_PROXY), val proxy: ProxyConfig = ProxyConfig(type = ProxyType.NO_PROXY),
val baseApiUrl: String = defaultTelegramApiUrl val baseApiUrl: String = ApiConstants.BASE_URL
) { )
fun getBaseApiTelegramUrl(): TelegramUrl {
if (this.baseApiUrl == defaultTelegramApiUrl) {
return TelegramUrl.DEFAULT_URL
} else {
URI.create(baseApiUrl).let {
return TelegramUrl.builder()
.host(it.host)
.port(it.port)
.schema(it.scheme)
.build()
}
}
}
}
/** /**
* 代理类型. * 代理类型.

View File

@ -53,7 +53,7 @@ internal class ProxyTypeSerializerTest {
@Test @Test
fun `serialize test`() { fun `serialize test`() {
for (type in ProxyType.entries) { for (type in ProxyType.values()) {
assertEquals( assertEquals(
JsonPrimitive(type.name), ProxyTypeSerializer.serialize(type, null, null), JsonPrimitive(type.name), ProxyTypeSerializer.serialize(type, null, null),
"ProxyType 序列化结果与预期不符." "ProxyType 序列化结果与预期不符."
@ -79,7 +79,7 @@ internal class ProxyTypeSerializerTest {
ProxyTypeSerializer.deserialize(JsonNull.INSTANCE, null, null) ProxyTypeSerializer.deserialize(JsonNull.INSTANCE, null, null)
) )
for (type in ProxyType.entries) { for (type in ProxyType.values()) {
assertEquals( assertEquals(
type, ProxyTypeSerializer.deserialize(JsonPrimitive(type.name), null, null), type, ProxyTypeSerializer.deserialize(JsonPrimitive(type.name), null, null),
"ProxyType 反序列化结果与预期不符." "ProxyType 反序列化结果与预期不符."