Compare commits

..

245 Commits
v0.3.0 ... main

Author SHA1 Message Date
dependabot[bot]
d160fa7ee5
build(deps): bump org.jetbrains.dokka:javadoc-plugin (#187)
Bumps [org.jetbrains.dokka:javadoc-plugin](https://github.com/Kotlin/dokka) from 1.9.10 to 2.0.0.
- [Release notes](https://github.com/Kotlin/dokka/releases)
- [Commits](https://github.com/Kotlin/dokka/compare/v1.9.10...v2.0.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.dokka:javadoc-plugin
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-29 17:34:24 +08:00
dependabot[bot]
b053388f82
build(deps): bump com.google.code.gson:gson from 2.10.1 to 2.11.0 (#182)
Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.10.1 to 2.11.0.
- [Release notes](https://github.com/google/gson/releases)
- [Changelog](https://github.com/google/gson/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/gson/compare/gson-parent-2.10.1...gson-parent-2.11.0)

---
updated-dependencies:
- dependency-name: com.google.code.gson:gson
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-29 15:58:06 +08:00
818cb21df7
release: 发布 0.8.0-1 版本.
这是 0.8.0 版本的重新发布版本, 由于 0.8.0 版本在构建并发布到 Maven 仓库中出现问题,
为确保本项目的“可重现构建”承诺能正常实施, 因此决定发布 0.8.0-1 版本.
该版本与 0.8.0 版本除了补充缺失的 Javadoc 外并无其他差别, 最终用户依然可以继续使用 0.8.0 版本.
2024-12-11 23:12:02 +08:00
c681ebc4c1
build: 不再使用 kuku-repo 作为发布仓库.
Some checks failed
Binary compatibility verification (for API) / apiCompatibilityCheck (push) Has been cancelled
Build and test project / build (push) Has been cancelled
Build development version container image / build (push) Has been cancelled
2024-12-11 09:47:22 +08:00
44b7b5122f
docs(extension): 补充部分缺失的 Javadoc 文档. 2024-12-11 09:46:15 +08:00
bf4adc8437
release: 发布 0.8.0 版本. 2024-12-11 09:34:32 +08:00
dependabot[bot]
47d34b0fd8
build(deps): bump docker/build-push-action from 5 to 6 (#163)
Some checks are pending
Binary compatibility verification (for API) / apiCompatibilityCheck (push) Waiting to run
Build and test project / build (push) Waiting to run
Build development version container image / build (push) Waiting to run
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-11 00:56:54 +08:00
dependabot[bot]
14098d285c
build(deps): bump io.mockk:mockk from 1.13.9 to 1.13.13 (#176)
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.9 to 1.13.13.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.13.9...1.13.13)

---
updated-dependencies:
- dependency-name: io.mockk:mockk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-11 00:54:30 +08:00
dependabot[bot]
ad359935d6
build(deps): bump org.jetbrains.kotlinx:kotlinx-coroutines-core (#174)
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-11 00:51:46 +08:00
3e1bf4991d
build(deps): 将 Kotlin 更新至 2.1.0 版本. 2024-12-11 00:47:57 +08:00
bd7f556c31
build(gradle): 将 Gradle 更新至 8.11.1 版本. 2024-12-11 00:14:18 +08:00
dependabot[bot]
9c069adc4b
build(deps): bump ch.qos.logback:logback-classic from 1.4.14 to 1.5.12 (#179)
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.14 to 1.5.12.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.14...v_1.5.12)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-10 23:54:58 +08:00
dependabot[bot]
96a06c48ee
build(deps): bump org.jetbrains.kotlinx.kover from 0.7.5 to 0.8.3 (#167)
Bumps [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover) from 0.7.5 to 0.8.3.
- [Release notes](https://github.com/Kotlin/kotlinx-kover/releases)
- [Changelog](https://github.com/Kotlin/kotlinx-kover/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Kotlin/kotlinx-kover/compare/v0.7.5...v0.8.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-10 23:50:00 +08:00
ca4bfee03e
build(github-action): 将所有 Github Action 所使用的 Java 版本升级至 17. 2024-12-10 23:42:50 +08:00
16dc98fa85
Merge branch 'main' of github.com:LamGC/ScalaBot 2024-12-10 23:36:29 +08:00
8299316af8
refactor(extension): 使用 URI 而不是 URL 来排除重复项, 以避免潜在的 URL hashCode 问题. 2024-12-10 23:35:12 +08:00
e1c87aeae4
feat: 将 TelegramBots 升级至 8.0.0, 并适配 TelegramBots 的新改动.
将 TelegramBots 升级至新版本, 以支持新的 API.
由于 TelegramBots 发生无法兼容旧版本的重大变更, 因此 ScalaBot 将随着此次更新一同进行重大更改.

BREAKING CHANGE: ScalaBot 所依赖的 TelegramBots 发生重大更改, 所有扩展都需要进行适配.
  有关 TelegramBots 的重大变更说明请参考官方文档.
  ScalaBot 的最低 Java 版本已全部升级至 Java 17 (这是 TelegramBots 的最低兼容性要求), 所有扩展都应该至少迁移至 Java 17 版本.
  ScalaBot 的重大更改:
   - scalabot-extension
     - `net.lamgc.scalabot.extension.util.AbilityBots.getBotAccountId(BaseAbilityBot): long` 已被移除, 由于 BaseAbilityBot 不再允许获取 botToken, 因此该方法被移除. 作为代替, 请通过 `net.lamgc.scalabot.extension.BotExtensionFactory.createExtensionInstance` 所得到的 `BotExtensionCreateOptions` 中获取 botAccountId.

  另外, scalabot-extension 中的 `org.jetbrains.kotlinx.binary-compatibility-validator` 似乎不再对 Java 代码起作用, 因此移除该插件, 并在后续寻找替代品.
  TelegramBots 文档: https://rubenlagus.github.io/TelegramBotsDocumentation/how-to-update-7.html
2024-12-10 23:32:29 +08:00
dependabot[bot]
232e318522
build(deps): bump org.jetbrains.dokka from 1.9.10 to 1.9.20 (#147)
Some checks failed
Binary compatibility verification (for API) / apiCompatibilityCheck (push) Has been cancelled
Build and test project / build (push) Has been cancelled
Build development version container image / build (push) Has been cancelled
Bumps [org.jetbrains.dokka](https://github.com/Kotlin/dokka) from 1.9.10 to 1.9.20.
- [Release notes](https://github.com/Kotlin/dokka/releases)
- [Commits](https://github.com/Kotlin/dokka/compare/v1.9.10...v1.9.20)

---
updated-dependencies:
- dependency-name: org.jetbrains.dokka
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-07 01:09:11 +08:00
bac7239513
build(action): 将 Gradle 相关 Action 迁移到官方推荐的新版本. 2024-08-30 09:00:48 +08:00
cc7ef37539
build(action): 移除 gradle/actions/wrapper-validation 并将 gradle/actions/setup-gradle 升级至 v4 版本. 2024-08-30 08:52:40 +08:00
4cf670a4d0
build(action): 将 gradle/gradle-build-action@v3.1.0 迁移至 gradle/actions/setup-gradle@v3 2024-08-30 08:45:35 +08:00
dependabot[bot]
d6781dbc71
build(deps): bump gradle/gradle-build-action from 3.1.0 to 3.4.2 (#164)
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 3.1.0 to 3.4.2.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v3.1.0...v3.4.2)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-21 14:10:40 +08:00
63f68acccd
build(action): 将 gradle/wrapper-validation-action@v2 迁移至 gradle/actions/wrapper-validation@v3 2024-05-15 08:50:32 +08:00
506f073bf6
release: 发布 0.7.0 版本. 2024-05-15 08:32:34 +08:00
9dc4bfa28f
feat: 为 BotExtensionFactory 提供一些可用的信息. (#125)
为 BotExtensionFactory 的 createExtensionInstance 方法添加 BotExtensionCreateOptions 参数,
通过 BotExtensionCreateOptions 为扩展包 Factory 提供更多信息, 以避免扩展重复声明配置文件.

Issue #4
2024-03-25 23:22:26 +08:00
92a1deac50
build(deps): bump org.telegram:telegrambots-abilities from 6.8.0 to 6.9.7.1 2024-03-25 23:04:24 +08:00
0e2ce68a9c
feat(container): 将容器镜像的 Java 镜像由 OpenJDK:18 更改至 eclipse-temurin:21-jdk-alpine
这将有利于支持通过更高版本 Java 构建的扩展包, 且减少镜像体积.
2024-03-25 22:54:36 +08:00
d57e63f8de
build: 更新 Kotlin 版本由 1.9.22 到 1.9.23
更新 Kotlin 版本以更好的支持新特性.
2024-03-25 22:52:15 +08:00
dependabot[bot]
73653cabc6
build(deps): bump commons-codec:commons-codec from 1.16.0 to 1.16.1 (#144)
Bumps [commons-codec:commons-codec](https://github.com/apache/commons-codec) from 1.16.0 to 1.16.1.
- [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt)
- [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.16.0...rel/commons-codec-1.16.1)

---
updated-dependencies:
- dependency-name: commons-codec:commons-codec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 22:11:04 +08:00
dependabot[bot]
9e87591671
build(deps): bump org.mockito:mockito-core from 5.9.0 to 5.11.0 (#141)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.9.0 to 5.11.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.9.0...v5.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-15 18:10:17 +08:00
dependabot[bot]
126092487d
build(deps): bump softprops/action-gh-release from 1 to 2 (#142)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-15 17:16:18 +08:00
dependabot[bot]
44bc52050c
build(deps): bump gradle/wrapper-validation-action from 1 to 2 (#135)
Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 1 to 2.
- [Release notes](https://github.com/gradle/wrapper-validation-action/releases)
- [Commits](https://github.com/gradle/wrapper-validation-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: gradle/wrapper-validation-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-15 16:26:35 +08:00
dependabot[bot]
f809076df0
build(deps): bump org.jetbrains.kotlinx.binary-compatibility-validator (#134)
Bumps [org.jetbrains.kotlinx.binary-compatibility-validator](https://github.com/Kotlin/binary-compatibility-validator) from 0.13.2 to 0.14.0.
- [Release notes](https://github.com/Kotlin/binary-compatibility-validator/releases)
- [Commits](https://github.com/Kotlin/binary-compatibility-validator/compare/0.13.2...0.14.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx.binary-compatibility-validator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-15 16:21:33 +08:00
dependabot[bot]
b34f05a90b
build(deps): bump gradle/gradle-build-action from 2.11.1 to 3.1.0 (#140)
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2.11.1 to 3.1.0.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2.11.1...v3.1.0)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-15 16:12:29 +08:00
dependabot[bot]
ddc4de1340
build(deps): bump io.mockk:mockk from 1.13.8 to 1.13.9 (#123)
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.8 to 1.13.9.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/commits)

---
updated-dependencies:
- dependency-name: io.mockk:mockk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-21 23:28:07 +08:00
dependabot[bot]
a9134f378e
build(deps): bump org.slf4j:slf4j-api from 2.0.9 to 2.0.11 (#124)
Bumps org.slf4j:slf4j-api from 2.0.9 to 2.0.11.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-21 23:23:37 +08:00
dependabot[bot]
dbb2fcdee1
build(deps): bump org.mockito:mockito-core from 5.8.0 to 5.9.0 (#128)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.8.0 to 5.9.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.8.0...v5.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-21 23:17:55 +08:00
dependabot[bot]
ce00369a22
build(deps): bump org.jetbrains.kotlin:kotlin-reflect (#120)
Bumps [org.jetbrains.kotlin:kotlin-reflect](https://github.com/JetBrains/kotlin) from 1.9.21 to 1.9.22.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.21...v1.9.22)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-reflect
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-07 19:31:51 +08:00
dependabot[bot]
65bac28303
build(deps): bump jvm from 1.9.10 to 1.9.22 (#119)
Bumps [jvm](https://github.com/JetBrains/kotlin) from 1.9.10 to 1.9.22.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.10...v1.9.22)

---
updated-dependencies:
- dependency-name: jvm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-07 18:47:23 +08:00
dependabot[bot]
8bd6d5ffe5
build(deps): bump ch.qos.logback:logback-classic from 1.4.11 to 1.4.14 (#118)
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.11 to 1.4.14.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.11...v_1.4.14)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-07 17:42:06 +08:00
dependabot[bot]
1c4cc122c9
build(deps): bump org.jetbrains.kotlinx.kover from 0.7.4 to 0.7.5 (#117)
Bumps [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover) from 0.7.4 to 0.7.5.
- [Release notes](https://github.com/Kotlin/kotlinx-kover/releases)
- [Changelog](https://github.com/Kotlin/kotlinx-kover/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Kotlin/kotlinx-kover/compare/0.7.4...v0.7.5)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx.kover
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-07 17:41:57 +08:00
dependabot[bot]
6083f4fc68
build(deps): bump org.junit.jupiter:junit-jupiter-api (#115)
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.10.0 to 5.10.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.1)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-07 17:41:41 +08:00
dependabot[bot]
b33de4d63a
build(deps): bump gradle/gradle-build-action from 2.11.0 to 2.11.1 (#121)
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2.11.0 to 2.11.1.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2.11.0...v2.11.1)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-07 17:41:27 +08:00
fd67eb99eb
feat: 在启动机器人时调用 GetMe 接口, 以检查 Token 有效性.
通过这种方式, 可以帮助用户在启动阶段发现 Bot Token 无效的问题.
2023-12-18 22:21:58 +08:00
ba306e679f
build(deps): bump all telegrambots deps from 6.7.0 to 6.8.0 (#114)
Bumps [org.telegram:telegrambots](https://github.com/rubenlagus/TelegramBots) from 6.7.0 to 6.8.0.
- [Release notes](https://github.com/rubenlagus/TelegramBots/releases)
- [Commits](rubenlagus/TelegramBots@v6.7.0...v6.8.0)

---
updated-dependencies:
- dependency-name: org.telegram:telegrambots-abilities
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.telegram:telegrambots
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.telegram:telegrambots-meta
  dependency-type: direct:production
  update-type: version-update:semver-minor
...
2023-12-17 23:10:52 +08:00
dependabot[bot]
f188d5bfa9
build(deps): bump org.mockito:mockito-core from 5.6.0 to 5.8.0 (#111)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.6.0 to 5.8.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.6.0...v5.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-17 22:49:24 +08:00
dependabot[bot]
376086daa1
build(deps): bump org.junit.jupiter:junit-jupiter-engine (#112)
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.10.0 to 5.10.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.1)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-17 22:42:54 +08:00
dependabot[bot]
cee8797c40
build(deps): bump org.jetbrains.dokka:javadoc-plugin (#103)
Bumps [org.jetbrains.dokka:javadoc-plugin](https://github.com/Kotlin/dokka) from 1.9.0 to 1.9.10.
- [Release notes](https://github.com/Kotlin/dokka/releases)
- [Commits](https://github.com/Kotlin/dokka/compare/v1.9.0...v1.9.10)

---
updated-dependencies:
- dependency-name: org.jetbrains.dokka:javadoc-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 12:16:49 +08:00
dependabot[bot]
77b5867dd7
build(deps): bump org.jetbrains.kotlin:kotlin-reflect (#105)
Bumps [org.jetbrains.kotlin:kotlin-reflect](https://github.com/JetBrains/kotlin) from 1.9.10 to 1.9.21.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.10...v1.9.21)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-reflect
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 12:16:37 +08:00
dependabot[bot]
33eefd9e60
build(deps): bump gradle/gradle-build-action from 2.9.0 to 2.11.0 (#110)
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2.9.0 to 2.11.0.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2.9.0...v2.11.0)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 12:16:10 +08:00
dependabot[bot]
0c81ca6d5f
build(deps): bump actions/setup-python from 4 to 5 (#108)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 12:15:57 +08:00
dependabot[bot]
705150b961
build(deps): bump actions/setup-java from 3 to 4 (#106)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 12:09:50 +08:00
dependabot[bot]
e6f0550985
build(deps): bump org.jetbrains.dokka from 1.9.0 to 1.9.10 (#102)
Bumps [org.jetbrains.dokka](https://github.com/Kotlin/dokka) from 1.9.0 to 1.9.10.
- [Release notes](https://github.com/Kotlin/dokka/releases)
- [Commits](https://github.com/Kotlin/dokka/compare/v1.9.0...v1.9.10)

---
updated-dependencies:
- dependency-name: org.jetbrains.dokka
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-04 08:41:26 +08:00
dependabot[bot]
90c57ff9b1
build(deps): bump org.mockito:mockito-core from 5.5.0 to 5.6.0 (#100)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.5.0 to 5.6.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.5.0...v5.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 13:37:50 +08:00
dependabot[bot]
d773cba776
build(deps): bump org.jetbrains.kotlinx.kover from 0.7.3 to 0.7.4 (#101)
Bumps [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover) from 0.7.3 to 0.7.4.
- [Release notes](https://github.com/Kotlin/kotlinx-kover/releases)
- [Changelog](https://github.com/Kotlin/kotlinx-kover/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Kotlin/kotlinx-kover/compare/v0.7.3...0.7.4)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx.kover
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 13:37:30 +08:00
dependabot[bot]
f1c36aacdb
build(deps): bump io.mockk:mockk from 1.13.7 to 1.13.8 (#97)
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.7 to 1.13.8.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.13.7...1.13.8)

---
updated-dependencies:
- dependency-name: io.mockk:mockk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 00:59:36 +08:00
dependabot[bot]
fe9e7059c8
build(deps): bump gradle/gradle-build-action from 2.8.0 to 2.9.0 (#99)
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2.8.0 to 2.9.0.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2.8.0...v2.9.0)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 22:22:51 +08:00
dependabot[bot]
b42cc6bd2c
build(deps): bump docker/setup-qemu-action from 2 to 3 (#94)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 01:46:49 +08:00
dependabot[bot]
ebfc387505
build(deps): bump docker/build-push-action from 4 to 5 (#96)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 01:46:39 +08:00
dependabot[bot]
340f6ba3f3
build(deps): bump org.jetbrains.kotlin:kotlin-reflect (#91)
Bumps [org.jetbrains.kotlin:kotlin-reflect](https://github.com/JetBrains/kotlin) from 1.9.0 to 1.9.10.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.9.10/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.0...v1.9.10)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-reflect
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 01:46:13 +08:00
dependabot[bot]
e8f9dbbb05
build(deps): bump org.jetbrains.dokka from 1.8.20 to 1.9.0 (#92)
Bumps [org.jetbrains.dokka](https://github.com/Kotlin/dokka) from 1.8.20 to 1.9.0.
- [Release notes](https://github.com/Kotlin/dokka/releases)
- [Commits](https://github.com/Kotlin/dokka/compare/v1.8.20...v1.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 01:02:31 +08:00
dependabot[bot]
cdbfa822b0
build(deps): bump docker/setup-buildx-action from 2 to 3 (#93)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 01:02:07 +08:00
dependabot[bot]
6d5ce324a5
build(deps): bump docker/login-action from 2 to 3 (#95)
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 01:01:47 +08:00
dependabot[bot]
734a871fdc
build(deps): bump org.slf4j:slf4j-api from 2.0.7 to 2.0.9 (#88)
Bumps org.slf4j:slf4j-api from 2.0.7 to 2.0.9.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-24 00:57:50 +08:00
dependabot[bot]
a9ee432dec
build(deps): bump io.mockk:mockk from 1.13.5 to 1.13.7 (#81)
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.5 to 1.13.7.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/commits)

---
updated-dependencies:
- dependency-name: io.mockk:mockk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 10:33:55 +08:00
dependabot[bot]
1fc386a226
build(deps): bump org.mockito:mockito-core from 5.3.1 to 5.5.0 (#83)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.3.1 to 5.5.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.3.1...v5.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 10:30:14 +08:00
dependabot[bot]
40524e89c6
build(deps): bump org.jetbrains.dokka:javadoc-plugin (#86)
Bumps [org.jetbrains.dokka:javadoc-plugin](https://github.com/Kotlin/dokka) from 1.8.20 to 1.9.0.
- [Release notes](https://github.com/Kotlin/dokka/releases)
- [Commits](https://github.com/Kotlin/dokka/compare/v1.8.20...v1.9.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.dokka:javadoc-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-14 23:12:21 +08:00
dependabot[bot]
c972759ffb
build(deps): bump org.junit.jupiter:junit-jupiter-engine (#76)
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.9.3 to 5.10.0.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.3...r5.10.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-14 17:50:02 +08:00
dependabot[bot]
9b169ac957
build(deps): bump ch.qos.logback:logback-classic from 1.4.8 to 1.4.11 (#80)
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.8 to 1.4.11.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.8...v_1.4.11)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-14 17:42:21 +08:00
f93a41af96
chore(meta): 确认由 Kotlin 版本更新导致的 api 变动.
自 Kotlin 1.9.0 开始, `Enum.values` 将正式更改为 `Enum.getEntries`(对应 Kotlin 中的
`Enum.entries` 属性), 已确认该改动不会影响现版本的兼容性.
--------------------------
KEEP 文档:
https://github.com/Kotlin/KEEP/blob/master/proposals/enum-entries.md
Kotlin 问题跟踪器:
https://youtrack.jetbrains.com/issue/KT-48872
2023-09-14 13:33:04 +08:00
dependabot[bot]
8eb993da3e
build(deps): bump actions/checkout from 3 to 4 (#87)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-14 11:43:11 +08:00
dependabot[bot]
9065df0e04
build(deps): bump jvm from 1.8.21 to 1.9.10 (#84)
Bumps [jvm](https://github.com/JetBrains/kotlin) from 1.8.21 to 1.9.10.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.9.10/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.8.21...v1.9.10)

---
updated-dependencies:
- dependency-name: jvm
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-09 23:19:55 +08:00
dependabot[bot]
9514c7b57e
build(deps): bump gradle/gradle-build-action from 2.7.0 to 2.8.0 (#85)
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2.7.0...v2.8.0)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-09 23:10:11 +08:00
24219df63b
release: 发布 6.1.0 版本. 2023-08-03 09:11:28 +08:00
dependabot[bot]
14e9c4d686
build(deps): bump org.jetbrains.dokka from 1.8.10 to 1.8.20 (#72)
Bumps [org.jetbrains.dokka](https://github.com/Kotlin/dokka) from 1.8.10 to 1.8.20.
- [Release notes](https://github.com/Kotlin/dokka/releases)
- [Commits](https://github.com/Kotlin/dokka/compare/v1.8.10...v1.8.20)

---
updated-dependencies:
- dependency-name: org.jetbrains.dokka
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 18:29:25 +08:00
dependabot[bot]
8e83f714a7
build(deps): bump org.jetbrains.kotlinx:kotlinx-coroutines-core (#74)
Bumps [org.jetbrains.kotlinx:kotlinx-coroutines-core](https://github.com/Kotlin/kotlinx.coroutines) from 1.7.1 to 1.7.3.
- [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.1...1.7.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 18:12:12 +08:00
dependabot[bot]
939aed5850
build(deps): bump ch.qos.logback:logback-classic from 1.4.7 to 1.4.8 (#73)
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.7 to 1.4.8.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.7...v_1.4.8)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 18:08:43 +08:00
dependabot[bot]
a839cc63e6
build(deps): bump org.junit.jupiter:junit-jupiter-api (#71)
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.9.3 to 5.10.0.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.3...r5.10.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 17:58:19 +08:00
dependabot[bot]
8be3f770bf
build(deps): bump commons-codec:commons-codec from 1.15 to 1.16.0 (#75)
Bumps [commons-codec:commons-codec](https://github.com/apache/commons-codec) from 1.15 to 1.16.0.
- [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt)
- [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.15...rel/commons-codec-1.16.0)

---
updated-dependencies:
- dependency-name: commons-codec:commons-codec
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 17:54:46 +08:00
b1e7643b09
build(deps): 将 telegrambots 从 6.5.0 更新至 6.7.0
更新 Api 版本以支持新的 Api 改动.
2023-08-02 17:45:35 +08:00
4537e08107
fix(config): 将 local 列为 Maven 仓库 Id 的保留字.
local 是 Aether 中代表 LocalRepository 的仓库 Id, 因此拒绝用户使用 local 作为远端仓库的 Id.
2023-08-02 17:45:31 +08:00
3ea1eec2dd
ci(github-action): 提高 dependabot 依赖项更新检查的频率
提高检查频率, 以加快新版本的响应.
2023-08-02 17:40:35 +08:00
dependabot[bot]
fe9978dbe8
build(deps): bump org.jetbrains.kotlinx.kover from 0.7.0 to 0.7.3 (#69)
Bumps [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover) from 0.7.0 to 0.7.3.
- [Release notes](https://github.com/Kotlin/kotlinx-kover/releases)
- [Changelog](https://github.com/Kotlin/kotlinx-kover/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Kotlin/kotlinx-kover/compare/v0.7.0...v0.7.3)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx.kover
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 17:12:57 +08:00
dependabot[bot]
aece8221f1
build(deps): bump org.jetbrains.kotlin:kotlin-reflect (#68)
Bumps [org.jetbrains.kotlin:kotlin-reflect](https://github.com/JetBrains/kotlin) from 1.8.21 to 1.9.0.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.8.21...v1.9.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-reflect
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 17:08:50 +08:00
dependabot[bot]
4c9256f82f
build(deps): bump org.telegram:telegrambots-meta from 6.5.0 to 6.7.0 (#64)
Bumps [org.telegram:telegrambots-meta](https://github.com/rubenlagus/TelegramBots) from 6.5.0 to 6.7.0.
- [Release notes](https://github.com/rubenlagus/TelegramBots/releases)
- [Commits](https://github.com/rubenlagus/TelegramBots/compare/v6.5.0...v6.7.0)

---
updated-dependencies:
- dependency-name: org.telegram:telegrambots-meta
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 17:04:47 +08:00
dependabot[bot]
3f2e4e2d1f
build(deps): bump org.jetbrains.dokka:javadoc-plugin (#63)
Bumps [org.jetbrains.dokka:javadoc-plugin](https://github.com/Kotlin/dokka) from 1.8.10 to 1.8.20.
- [Release notes](https://github.com/Kotlin/dokka/releases)
- [Commits](https://github.com/Kotlin/dokka/compare/v1.8.10...v1.8.20)

---
updated-dependencies:
- dependency-name: org.jetbrains.dokka:javadoc-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 16:59:18 +08:00
dependabot[bot]
3810224667
build(deps): bump org.jetbrains.kotlinx.binary-compatibility-validator (#61)
Bumps [org.jetbrains.kotlinx.binary-compatibility-validator](https://github.com/Kotlin/binary-compatibility-validator) from 0.13.1 to 0.13.2.
- [Release notes](https://github.com/Kotlin/binary-compatibility-validator/releases)
- [Commits](https://github.com/Kotlin/binary-compatibility-validator/compare/0.13.1...0.13.2)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx.binary-compatibility-validator
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 16:39:17 +08:00
dependabot[bot]
3241bd2da9
build(deps): bump younited/get-previous-tag-action from 1.0.0 to 1.1.0 (#60)
Bumps [younited/get-previous-tag-action](https://github.com/younited/get-previous-tag-action) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/younited/get-previous-tag-action/releases)
- [Commits](https://github.com/younited/get-previous-tag-action/compare/v1.0.0...v1.1.0)

---
updated-dependencies:
- dependency-name: younited/get-previous-tag-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 13:57:51 +08:00
dependabot[bot]
dcac53fe65
build(deps): bump gradle/gradle-build-action from 2.4.2 to 2.7.0 (#70)
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2.4.2 to 2.7.0.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2.4.2...v2.7.0)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-01 19:01:48 +08:00
dependabot[bot]
0bc77ada5f
build(deps): bump org.jetbrains.kotlinx:kotlinx-coroutines-core (#58)
Bumps [org.jetbrains.kotlinx:kotlinx-coroutines-core](https://github.com/Kotlin/kotlinx.coroutines) from 1.6.4 to 1.7.1.
- [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.6.4...1.7.1)

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-30 10:44:16 +08:00
dependabot[bot]
f0b45568d1
build(deps): bump org.junit.jupiter:junit-jupiter-engine (#59)
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.9.2 to 5.9.3.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.2...r5.9.3)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-30 10:36:30 +08:00
dependabot[bot]
a3dd9d4114
build(deps): bump org.jetbrains.kotlinx.kover from 0.6.1 to 0.7.0 (#57)
Bumps [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover) from 0.6.1 to 0.7.0.
- [Release notes](https://github.com/Kotlin/kotlinx-kover/releases)
- [Changelog](https://github.com/Kotlin/kotlinx-kover/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Kotlin/kotlinx-kover/compare/v0.6.1...v0.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-30 10:31:55 +08:00
dependabot[bot]
3afe3b52ed
build(deps): bump org.jetbrains.kotlin:kotlin-reflect (#56)
Bumps [org.jetbrains.kotlin:kotlin-reflect](https://github.com/JetBrains/kotlin) from 1.8.20 to 1.8.21.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.8.20...v1.8.21)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-reflect
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 10:14:34 +08:00
dependabot[bot]
bdf75f1046
build(deps): bump org.junit.jupiter:junit-jupiter-api (#54)
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.9.2 to 5.9.3.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.2...r5.9.3)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 09:46:58 +08:00
dependabot[bot]
e834d4c7c7
build(deps): bump jvm from 1.8.20 to 1.8.21 (#53)
Bumps [jvm](https://github.com/JetBrains/kotlin) from 1.8.20 to 1.8.21.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.8.20...v1.8.21)

---
updated-dependencies:
- dependency-name: jvm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 09:40:53 +08:00
dependabot[bot]
2e1fae87b3
build(deps): bump ch.qos.logback:logback-classic from 1.4.6 to 1.4.7 (#50)
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.6 to 1.4.7.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.6...v_1.4.7)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 09:33:17 +08:00
dependabot[bot]
191bef5d68
build(deps): bump org.jetbrains.kotlinx.binary-compatibility-validator (#49)
Bumps [org.jetbrains.kotlinx.binary-compatibility-validator](https://github.com/Kotlin/binary-compatibility-validator) from 0.13.0 to 0.13.1.
- [Release notes](https://github.com/Kotlin/binary-compatibility-validator/releases)
- [Commits](https://github.com/Kotlin/binary-compatibility-validator/compare/0.13.0...0.13.1)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx.binary-compatibility-validator
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 09:24:24 +08:00
dependabot[bot]
55c435c4c0
build(deps): bump gradle/gradle-build-action in /.github/workflows (#55)
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2.4.0 to 2.4.2.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2.4.0...v2.4.2)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 09:14:13 +08:00
dependabot[bot]
758e0a9f16
build(deps): bump org.mockito:mockito-core from 5.3.0 to 5.3.1 (#51)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.3.0...v5.3.1)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-11 14:28:55 +08:00
dependabot[bot]
5814817f3a
build(deps): bump org.mockito:mockito-core from 5.2.0 to 5.3.0 (#47)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.2.0...v5.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-23 23:40:48 +08:00
dependabot[bot]
2641901240
build(deps): bump io.mockk:mockk from 1.13.4 to 1.13.5 (#48)
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.4 to 1.13.5.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/v1.13.4...1.13.5)

---
updated-dependencies:
- dependency-name: io.mockk:mockk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-20 17:54:51 +08:00
dependabot[bot]
0070b400bb
build(deps): bump org.jetbrains.dokka:javadoc-plugin (#40)
Bumps [org.jetbrains.dokka:javadoc-plugin](https://github.com/Kotlin/dokka) from 1.7.20 to 1.8.10.
- [Release notes](https://github.com/Kotlin/dokka/releases)
- [Commits](https://github.com/Kotlin/dokka/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.dokka:javadoc-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-12 18:57:09 +08:00
dependabot[bot]
73e20ab737
build(deps): bump ch.qos.logback:logback-classic from 1.4.5 to 1.4.6 (#44)
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.5 to 1.4.6.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.5...v_1.4.6)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-12 18:56:57 +08:00
dependabot[bot]
03d24fbfe3
build(deps): bump org.jetbrains.kotlin:kotlin-reflect (#45)
Bumps [org.jetbrains.kotlin:kotlin-reflect](https://github.com/JetBrains/kotlin) from 1.8.10 to 1.8.20.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.8.20/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.8.10...v1.8.20)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-reflect
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-12 18:56:36 +08:00
dependabot[bot]
53e47353e8
build(deps): bump kotlin plugin from 1.8.10 to 1.8.20 (#46)
Bumps [jvm](https://github.com/JetBrains/kotlin) from 1.8.10 to 1.8.20.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.8.20/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.8.10...v1.8.20)

---
updated-dependencies:
- dependency-name: jvm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-12 18:56:23 +08:00
dependabot[bot]
81a97ee8e3
build(deps): bump org.jetbrains.dokka from 1.7.20 to 1.8.10 (#41)
Bumps [org.jetbrains.dokka](https://github.com/Kotlin/dokka) from 1.7.20 to 1.8.10.
- [Release notes](https://github.com/Kotlin/dokka/releases)
- [Commits](https://github.com/Kotlin/dokka/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-31 13:57:26 +08:00
dependabot[bot]
dbcbc88e1b
build(deps): bump io.mockk:mockk from 1.13.2 to 1.13.4 (#38)
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.2 to 1.13.4.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.13.2...v1.13.4)

---
updated-dependencies:
- dependency-name: io.mockk:mockk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-23 21:29:01 +08:00
dependabot[bot]
f233a25323
build(deps): bump org.slf4j:slf4j-api from 2.0.6 to 2.0.7 (#43)
Bumps [org.slf4j:slf4j-api](https://github.com/qos-ch/slf4j) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/qos-ch/slf4j/releases)
- [Commits](https://github.com/qos-ch/slf4j/commits)

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-23 21:28:20 +08:00
dependabot[bot]
2c91884db8
build(deps): bump org.mockito:mockito-core from 5.1.1 to 5.2.0 (#42)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.1.1 to 5.2.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.1.1...v5.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-23 21:26:44 +08:00
6b92b7e377
build(publish): 增加 self-git Maven 仓库.
目前 Gitea 的构件仓库已经完善到基本可用的程度, 决定将 extension 和 meta 模块也一同发布到 self-git 中.
2023-02-25 17:14:03 +08:00
375b815659
build(deps): extension 模块不再引入 Slf4j-api 依赖.
考虑到发布的频率, 决定不再为 extension 引入 slf4j-api 依赖;
不过还是建议开发者使用 Slf4j-api 打印日志, 这样可以将日志整合在一起, 方便排查问题.
2023-02-25 17:11:27 +08:00
f26d642320
build(gradle): 将 extension 模块的构建插件改为 java-library
先前由于不了解 Gradle 的插件划分而使用了 kotlin-jvm 插件, 现在将其修正, 改用 java-library 插件.
2023-02-24 15:55:58 +08:00
2db0b78962
style(extension): 为 getBotToken 设置 SuppressWarnings 用于忽略无关的弃用警告.
根据文档说明, 弃用仅针对重写方法, 对方法的使用并无大碍.
2023-02-24 15:53:32 +08:00
8b76a9aa9b
release: 发布 0.6.0 版本. 2023-02-21 23:46:48 +08:00
bbc3288535
ci(github-action): 启用 API 二进制兼容性验证. (#37)
使用二进制兼容验证, 可以快速了解 API 是否出现修改, 这个功能有利于防止无意中修改 API.
引入该过程后, 需谨慎检查 api 列表,
以确保改动是必须的. 当 api 出现改动时, 需按照改动类型分配合适的版本号(遵循 SemVer 规范).
2023-02-21 23:22:40 +08:00
dependabot[bot]
45ad734c7b
build(deps): bump org.junit.jupiter:junit-jupiter-engine (#34)
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.9.0 to 5.9.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.0...r5.9.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 18:52:21 +08:00
dependabot[bot]
1eaed8ce6f
build(deps): bump org.mockito:mockito-core from 4.8.0 to 5.1.1 (#35)
Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 4.8.0 to 5.1.1.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.8.0...v5.1.1)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-core
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 18:42:42 +08:00
dependabot[bot]
a624fcd37d
build(deps): bump com.google.code.gson:gson from 2.9.0 to 2.10.1 (#36)
Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.9.0 to 2.10.1.
- [Release notes](https://github.com/google/gson/releases)
- [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/gson/compare/gson-parent-2.9.0...gson-parent-2.10.1)

---
updated-dependencies:
- dependency-name: com.google.code.gson:gson
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 18:38:18 +08:00
dependabot[bot]
59aa83c93e
build(deps): bump org.junit.jupiter:junit-jupiter-api (#33)
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.9.0 to 5.9.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.0...r5.9.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 18:36:02 +08:00
3ea0f4eacb
feat(metrics): 指标信息增加 bot_id label.
由于 bot_name 不能作为机器人的唯一标识, 因此增加 bot_id 为监控平台提供唯一标识.
2023-02-21 18:32:35 +08:00
d3e18d80ca
ci(github-action): 移除针对子模块的依赖项更新.
根据目前的效果来看, 即使不指定子模块的路径, dependabot 也会检查子模块的依赖项更新情况, 因此尝试移除子模块配置.
2023-02-21 16:00:08 +08:00
1e0eebbd7e
build(deps): 更新日志相关依赖(Slf4j 2.0.6, Logback-classic 1.4.5, Kotlin-logging 3.0.5).
Slf4j: 2.0.3 -> 2.0.6
Logback-classic: 1.4.4 -> 1.4.5
Kotlin-logging: 3.0.2 -> 3.0.5
2023-02-21 15:51:28 +08:00
dependabot[bot]
6f3c1ec592
build(deps): bump org.junit.jupiter:junit-jupiter-api (#19)
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.9.0 to 5.9.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.9.0...r5.9.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 15:29:54 +08:00
dependabot[bot]
c013dafdd7
build(deps): bump io.mockk:mockk from 1.13.2 to 1.13.4 in /scalabot-app (#26)
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.2 to 1.13.4.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.13.2...v1.13.4)

---
updated-dependencies:
- dependency-name: io.mockk:mockk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 15:08:06 +08:00
dependabot[bot]
e0bfd8796c
build(deps): bump org.jetbrains.kotlin:kotlin-reflect in /scalabot-app (#29)
Bumps [org.jetbrains.kotlin:kotlin-reflect](https://github.com/JetBrains/kotlin) from 1.6.20 to 1.8.10.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.8.10/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.6.20...v1.8.10)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-reflect
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 15:07:45 +08:00
dependabot[bot]
67d561c0ab
build(deps): bump org.jetbrains.kotlinx.kover from 0.5.1 to 0.6.1 (#32)
Bumps [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover) from 0.5.1 to 0.6.1.
- [Release notes](https://github.com/Kotlin/kotlinx-kover/releases)
- [Changelog](https://github.com/Kotlin/kotlinx-kover/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Kotlin/kotlinx-kover/compare/v0.5.1...v0.6.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 12:53:54 +08:00
63687da0bb
build(dependencies): 更新 binary-compatibility-validator 插件(0.11.1 -> 0.13.0).
更新后插件将支持 Kotlin 1.8.10 版本.
2023-02-21 12:44:06 +08:00
dependabot[bot]
cd76249c33
build(deps): bump gradle/gradle-build-action from 2.2.1 to 2.4.0 (#23)
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2.2.1 to 2.4.0.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2.2.1...v2.4.0)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 11:24:23 +08:00
dependabot[bot]
5a8afd1549
build(deps): bump docker/build-push-action from 3 to 4 (#18)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3 to 4.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-21 11:21:09 +08:00
651569dc8f
ci(github-action): 增加 Dependabot 配置文件.
使用 Dependabot 来自动检查依赖新版本, 减轻工作量.
2023-02-21 10:51:30 +08:00
bf2ea9a367
refactor(launch): 在启动时输出 JVM 和 Kotlin 的版本号.
在日志中添加版本信息, 方便在反馈问题时可以了解出现问题所使用的环境.
2023-02-19 16:12:15 +08:00
540fe84f26
build(dependencies): 更新 Kotlin 版本(1.7.10 -> 1.8.10).
更新 Kotlin 版本, 支持新的平台优化.
2023-02-19 16:09:52 +08:00
712378b3ff
build(dependencies): 更新 telegrambots 依赖项.
将 telegrambots 依赖项更新至 6.5.0, 支持新版 Api;
已确认 telegrambots 6.5.0 版本升级了jackson-databind 版本,
因此移除 jackson-databind 的显性依赖声明.
2023-02-08 10:12:33 +08:00
961382fb56
build(dependencies): 更新 TelegramBots 依赖项的版本号.
将 TelegramBots 依赖版本升级至 6.3.0, 支持新的 Telegram Bot API.
2022-12-16 01:15:09 +08:00
34d9ece6d7
feat(logging): 支持通过参数控制日志输出级别.
通过添加新的 -D 参数 `scalabot.log.level`, 使得用户可以根据需要, 调整日志输出级别, 通过日志来自行(或协助开发者)排查问题,
对于与网络有关的 Debug 级别日志, 则由 `scalabot.log.network.verbose` 参数进行控制;
另外, 由于开发过程中可通过该参数修改日志级别, 因此移除 `logback-test.xml` 文件.
2022-11-11 18:17:05 +08:00
30a2cb34d5
refactor(launch): 将 AppPaths 从 Launcher 解耦.
解耦后有助于后续改进, 以及单元测试的编写.
2022-11-07 11:16:41 +08:00
c94e0476b5
refactor(metrics): 为指标增加 namespace 名称.
遵循 OpenMetrics 规范指南, 为运行指标添加 namespace 名称, 防止指标混乱.

BREAKING CHANGE: 运行指标名称变更,
如果用户启用了运行指标功能, 请注意修改使用指标的名称.
2022-10-28 01:33:03 +08:00
6e5cd07c51
build(dependencies): 更新依赖项版本.
更新与日志和测试有关的依赖项版本.
2022-10-25 17:18:24 +08:00
d4b1438b0b
release: 发布 0.5.2 版本. 2022-10-20 16:04:27 +08:00
90110335f5
fix: 更新 jackson-databind 依赖版本以缓解 CVE-2022-42004 漏洞.
CVE-2022-42004 漏洞报告指出, 由于 jackson-databind 的有关错误, 导致对象序列化过程可能存在数据错误的问题,
目前 jackson-databind 已发布 2.13.4.2 版本以解决该问题, 由于引入 jackson-databind 的 telegrambots 库尚未发布针对该问题的修复版本,
故在本项目中引入新版依赖项, 以确保用户不受该问题影响.
该版本已在 TelegramBots 项目(版本 6.1.0)中进行测试, 测试通过.
------------------------------------------
https://devhub.checkmarx.com/cve-details/CVE-2022-42004/
2022-10-20 01:41:48 +08:00
9c32d26c0d
fix(dependencies): 更新 Commons-codec 依赖项的版本.
由于目前从 TelegramBots-Abilities 引入的 Commons-codec 存在 Base 32 和 64 的编解码漏洞,
考虑到需要防范潜在的安全问题, 因此决定更新 Commons-codec
的版本号.
----------------
参考链接:
https://devhub.checkmarx.com/cve-details/Cxeb68d52e-5509/
2022-09-19 14:32:40 +08:00
dfab6b14bd
build(compatibility): 新增 API 兼容性检查插件(尚未启用).
新增 Jetbrains 的二进制兼容性验证插件, 该插件可确保在兼容性出现更改时及时报告出来.
计划在下一次发布版本时启用.
2022-09-19 14:28:06 +08:00
437cee499a
build(dependencies): 更新依赖项版本.
更新版本有利于维持项目安全性.
2022-09-19 01:14:31 +08:00
4c30a1ac68
ci(github-action): 更改工作流名称.
后续可能会加上 Maven 的发布, 所以把名字和文件名都改一下, 防止出现歧义.
2022-08-25 16:11:10 +08:00
3932db11a1
release: 发布 0.5.1 版本. 2022-08-23 13:40:21 +08:00
d18c059498
ci(github-action): 显性声明允许工作流写入仓库.
安全起见, 仓库将设置工作流的 Github Token 默认不可写, 因此需要在该工作流添加权限声明, 以支持其创建 Release.
2022-08-23 13:40:20 +08:00
e6b581b8cd
ci(github-action): 移除容器镜像对 windows-amd64 的构建.
windows-amd64 的镜像构建依然不太不稳定, 因此移除 windows-amd64 平台的镜像构建.
2022-08-23 13:40:18 +08:00
26d7443c87
ci(github-action): 把 Gradle Wrapper 检查步骤加入到所有构建工作流中.
单独检查 Wrapper 并不能阻止其他工作流被恶意 jar 影响, 所以取消单独的检查工作流, 并将检查步骤插入到其他构建工作流中.
2022-08-23 13:40:17 +08:00
2bf4eb684e
ci(github-action): 延长镜像构建工作流的超时时间.
鉴于镜像构建可能会很慢, 因此将时间统一延长到 15 分钟.
2022-08-23 13:40:15 +08:00
5251b62733
ci(github-action): 修改开发版镜像构建工作流的名称和配置文件名.
为了与发布版镜像构建工作流区分开来, 防止弄混乱了.
2022-08-23 13:40:13 +08:00
3ec5a8e9c3
ci(github-action): 增加自动发布工作流.
使用 Action 自动执行发布过程, 可避免不少发布问题(比如构建成品包括了其他代码, 内容错误等).
同时在确认发布版本后,将自动构建并发布容器镜像, 支持多平台.
2022-08-23 13:40:11 +08:00
62f6c08cd2
ci(github-action): 移除对测试分支的容器镜像构建.
因为要并入 main 分支了.
2022-08-23 13:40:09 +08:00
5e6f64f056
ci(github-action): 尝试为 Windows 平台构建容器镜像.
看 OpenJdk 18 应该是有对 Windows/amd64 支持的, 所以添加上去, 看看能不能构建成功.
2022-08-23 13:40:07 +08:00
a036695330
ci(github-action): 修复推送 tag 错误的问题.
修复一下这个问题, 如果不加用户名, 就会推送到 library 去.
2022-08-23 13:40:05 +08:00
e9c975f5c5
ci(github-action): 更改容器发布配置名称
忘记改名字了, 改名后会更好的识别工作流.
2022-08-23 13:40:02 +08:00
9aac42d414
ci(github-action): 增加一段容器构建发布配置.
试一下看看能不能生成容器, 如果可以的话就整合到发布工作流中.
2022-08-23 13:39:58 +08:00
5add6d9909
build(container): 增加一个 Docker Context 过滤文件.
该文件用于优化向 Docker Daemon 发送 Context 文件的文件数量, 增加之后, 从四千个文件降至143个文件, 优化显著.
2022-08-23 13:39:56 +08:00
eee6b7d2c9
build(container): 新增一个比较基础的 Dockerfile.
初步测试了一下运行, 没啥问题.
2022-08-23 13:39:53 +08:00
ff396425a7
refactor: 补充缺失代码.
由于先前的修改导致代码缺失, 我现在同意暂存功能很好用了.
2022-08-18 01:06:54 +08:00
580d9122e5
refactor(utils): 把日志记录器的获取方式改的优雅一点(对于测试来说).
由于 Kover 一直不把无代码高阶函数忽略掉, 所以稍微改一下这个日志记录器的获取方法, 让测试覆盖数据好看点(doge).
2022-08-17 23:01:06 +08:00
2a607f1129
fix(extension): 修复 ExtensionClassLoader 无法过滤非扩展包内 BotExtensionFactory 对象的问题.
按照 ServiceLoader 的规范, 文件应该是在 META-INF/services/{class} 这里的, 但当时忘记这个设计了, 导致直接判断 name == class, 然后失效.
修复好了.
2022-08-15 02:00:34 +08:00
2d6da7c1ae
build(test): 添加 Jacoco 插件.
给 Extension 模块添加 Jacoco 插件, 以后估计会开 Codecov 来统计测试覆盖率.
2022-08-15 01:45:22 +08:00
6235c5f51a
build(dependencies): 更新 Kotlin 版本(1.6.10 -> 1.7.10). 2022-08-15 01:39:08 +08:00
255a02c93c
refactor(config): 重构 AppPaths 的构造方法, 应对将来 Kotlin 更新中的特性.
先前的方法是利用了初始化与调用的顺序, 来实现的 Supplier 互补(虽然在代码中, 确实存在未初始化调用的情况, 但实际运行的时候, 会先初始化, 再调用 Supplier),
但是未来 Kotlin 的更新中,编译器会把这个操作视为未初始化错误, 所以在这次改动中修复掉这个 bug 操作.
2022-08-15 01:38:08 +08:00
dce28be9c7
fix(logging): 修复日志滚动文件路径错误.
由于在滚动格式中没有使用 DATA_LOGS 占位符, 导致在日志滚动时会把日志归档文件保存在运行目录下, 而不是指定的数据目录,
该改动已修复该问题.
2022-08-13 13:19:09 +08:00
673c6d8392
build: 为项目支持可重现构建.
为确保项目的使用者(无论是开发者, 还是最终用户)可以完全重现构建, 确保安全, 故调整相关配置, 以实现"可重现构建".
有关可重现构建, 可以看这个: https://reproducible-builds.org/
2022-08-06 03:23:35 +08:00
d586ca378e
fix(launch): 修复缺省的 Maven 中央库不遵循代理规则的问题.
这个属于漏网之鱼, 已修复.
2022-08-05 00:23:13 +08:00
3ba4364a07
test(config): 补全对 ProxyConfig 的单元测试.
先前加了个 ProxyConfig.toString 方法, 所以补充了一下测试项.
2022-08-04 19:13:45 +08:00
eda0e522cd
docs(config): 补充关于 Gson 类型适配器的使用指导.
为 AppConfig 和 BotConfig 补充关于 Gson 类型适配器使用的信息, 以便于开发者正确使用类型适配器解析和编码 Json.
2022-07-22 21:59:14 +08:00
c7fedf3882
release: 发布 0.5.0 版本. 2022-07-19 01:51:00 +08:00
a7de85eacb
test(extension): 修正更新 TelegramBots 依赖项所导致的测试错误.
忘记执行测试了, 我的锅)
2022-07-16 21:03:12 +08:00
b6013e2fbe
refactor(extension): 将构件下载请求跟构件解析请求对齐.
防止在构件处理过程中因仓库原因出现问题, 因此统一两个请求所使用的仓库列表.
2022-07-16 20:55:24 +08:00
f79a4e4ff3
refactor(extension): 在 MavenRepositoryExtensionFinder 增加一个扩展包信息日志.
当初写的时候没留意这个问题, 结果最近给坑了, 所以补个日志, 打印出 Maven 搜索器所解析出来的构件信息.
2022-07-16 20:51:06 +08:00
93b9c6b727
refactor(launch): 统一代理的使用.
之前的版本中, 如果未指定 Maven 仓库的独立代理配置, 同时 Bot 拥有独立代理配置的情况下, Aether 将不会使用 Bot
的独立代理配置, 这样弄比较乱, 因此统一代理配置的使用顺序:
- 如果配置中包括了代理配置, 则优先使用独立代理配置;
- 如果不包括独立代理配置, 则使用关联 Bot 的独立代理配置;
- 如果关联 Bot 没有独立代理配置, 则使用 AppConfig 中的全局配置(如无配置则直连访问).
2022-07-16 20:46:15 +08:00
a8a0a9576f
build(dependencies): 更新 TelegramBots 依赖项的版本 (6.0.1 -> 6.1.0)
更新版本将有利于开发者和用户使用新的功能.
2022-07-16 20:30:22 +08:00
e8711e9974
refactor(meta): 为 ProxyConfig 覆盖 toString 方法.
覆盖 toString 方法后, 可以输出易懂的代理信息(我担心按照 data class 的格式输出, 用户可能看不懂).
2022-07-16 20:26:45 +08:00
93685e9440
test(config): 优化单元测试.
将 AppPaths 的单元测试也覆盖了, 直接方便了.
2022-07-12 01:29:19 +08:00
92b7e84b3a
test(config): 补充相关的单元测试.
经检查, 已确定完全覆盖代码, 为完成单元测试的编写, 稍微改了一下 AppPaths 的代码, 不会有影响的 :P
2022-07-12 01:20:53 +08:00
8c4e48e3eb
refactor(launch): 更改初始化配置中, 退出进程的时机.
为了能在单元测试中检查 initialFiles 是否正常, 故将 exitProcess 移到 main 方法中, 方便进行测试.
2022-07-12 00:27:18 +08:00
7f7b2b8895
build(meta): 调整部分依赖的引入范围.
将 Aether-api 和 telegrambots-meta 的引入范围由 implementation 改为 api,
方便依赖的其他项目使用.
2022-07-11 23:58:22 +08:00
441991b705
docs(extension): 补充部分扩展组件的 KDoc.
补充部分方法的文档, 不过文档内容嘛...有待加强.
2022-07-09 01:20:57 +08:00
51d036c4c6
feat(launch): 延后 BotConfig 的反序列化时机.
通过将 BotConfig 的反序列化时机延后到启动机器人的时候, 可以避免因某个机器人配置错误导致所有机器人都无法启动的问题.
注意, 语法错误还是会在启动时报错, 只是说部分序列化器会检查字段值是否有误, 通过延后反序列化来防止全反序列化的时候一个配置炸了影响全部而已.
2022-07-04 16:40:17 +08:00
3c54c33364
fix(config): 修复因正则表达式错误导致的 Token 检查失败.
由于表达式中限定的 BotId 为 Int 范围, 而目前的 TelegramUserId 已经扩展到 Long 了, 所以新的 Bot 是无法通过检查的, 已修正表达式问题.
2022-07-04 16:26:01 +08:00
43dd0e7bea
docs(meta): 加一个 Readme.
加一个文档来稍微说明一下这个模块的用途.
2022-07-03 02:50:32 +08:00
c144755913
feat(config): 增加 BotAccountSerializer, 主要用于检查字段值.
增加 BotAccount 的序列化器, 便于检查有关字段的值是否有效.
2022-07-03 02:32:37 +08:00
9ed55204c0
refactor(config): 调整序列化工具类, 便于进行测试.
修正序列化工具类的类名, 并调整访问权为 internal.
同时将 `checkJsonKey` 改成更方便的 `JsonObject.getPrimitiveValueOrThrow`.
2022-07-03 01:35:23 +08:00
9b7fc30512
fix(config): 修复因使用了错误的 Gson 对象而导致的配置初始化警告.
导致的原因是在初始化 app.json 时错误的使用了 botConfigGson, 而 botConfigGson 未配置用于 MetricsServer 的 UsernameAuthenticator 序列化器, 导致在初始化配置文件中序列化 AppConfig 时, Gson 会反射调用 BasicAuthenticator, 导致被 Java 模块系统拦截并报错, 现已修复该问题.
2022-07-02 22:01:05 +08:00
27dc26160d
refactor(config): 对配置文件的 AppPath 对象更名.
先前为确保后续可以增加指定配置文件路径的功能, 在命名上标记的 DEFAULT 在现在已经不符合实际意义了, 故将 DEFAULT 前缀移除.
2022-06-30 00:15:07 +08:00
ae411ce829
refactor(metrics): 调整 MetricsHttpServer 注册关闭钩子的时机.
将关闭钩子的时机调整到 main 方法中, 可以减少多余的钩子注册(比如测试时无需注册钩子, 却还是注册了).
2022-06-29 03:06:15 +08:00
1afe0f07a8
perf(extension): 优化 printExtensionFileConflictError 日志输出.
Kotlin-logging 在日志输出方法中做了检查, 如果级别未开启则不会调用方法获取日志内容,
故将内容构造部分移入 error 代码块以避免无意义的生成日志内容.
2022-06-29 03:03:16 +08:00
cf8e746bd4
release: 发布 0.4.0 版本. 2022-06-28 01:40:23 +08:00
fc66cd16f4
build: 优化构建配置脚本.
由于 rootProject 已经为子模块定义 repositories, 且子模块没有定义仓库的需要, 故移除子模块中的 repositories.
2022-06-28 01:37:38 +08:00
1340f0aa32
build(meta): 优化构建插件的版本管理.
根据 Gradle 官方指南[1], 将 meta 模块的插件版本号移除, 以便于用 rootProject 集中管理插件版本.
---------------------------------
[1]: https://docs.gradle.org/current/userguide/plugins.html#sec:subprojects_plugins_dsl
2022-06-28 01:13:26 +08:00
099c452fe7
feat(meta): 新增 meta 模块.
将与配置相关的内容迁移到 scalabot-meta 模块.
其他项目可以使用 meta 模块来生成 ScalaBot 的配置文件, 通过配置文件管理 ScalaBot 的运行.

BREAKING CHANGE: 与配置有关的类迁移到了 scalabot-meta 模块.

目前仅所有配置类 (以 `Config` 结尾的 Class) 和相应的序列化类 (以 `Serializer` 结尾的) 都迁移到了 meta 模块, 但其工具方法则作为扩展函数保留在 app 模块中.
这么做的好处是为了方便其他应用 (例如 ScalaBot 外部管理程序) 根据需要生成配置文件.
scalabot-meta 将会作为依赖项发布, 可根据需要获取 ScalaBot-meta 生成 ScalaBot 的配置.
 + 此次改动普通用户无需迁移。
2022-06-27 19:24:52 +08:00
aa31bcd3a8
build(meta): 增加发布配置.
为 meta 模块增加 Maven 构件发布配置, 但仍需本地发布(因为要 GPG 签名).
2022-06-27 18:49:40 +08:00
5843e37196
docs(config): 补充配置类的文档.
由于先前的更新忘记补充文档了, 所以补充一下.
2022-06-26 13:25:44 +08:00
06acc78180
test(config): 补全单元测试用例.
由于 `MavenRepositoryConfig.authentication` 有关联的序列化器, 因此不可忽略对该属性进行检查;
现已补充单元测试用例以确保反序列化结果正确.
2022-06-26 03:06:14 +08:00
db010d6c86
test(config): 为测试补充一个新的断言.
补充新的断言状况, 以保证功能正常使用.
2022-06-26 02:52:11 +08:00
61b611b22e
test(config): 为 BotConfig 补充最少参数的反序列化测试项.
补充最少参数的反序列化测试项, 以确保在 Json 属性缺失的情况下依然能正确反序列化出正确的对象.
2022-06-26 02:51:33 +08:00
c7c24fa454
fix(config): 修复潜在的无状况错误问题.
由于在 MavenRepositoryConfigSerializer 反序列化中过滤了 Json 的类型, 导致用户在配置中使用了错误的 Json 数据类型将不会有任何错误信息.
该改动已解决该问题.
2022-06-26 02:42:35 +08:00
045b3e5d54
test(config): 统一 Test 注解的使用, 修改测试代码的顺序.
Test 注解将统一使用 kotlin.test.Test, 这么做可以保持兼容性;
将 MavenRepositoryConfigSerializerTest.`json primitive deserialize test` 中的两段代码顺序调整一下, 以避免出现歧义.
2022-06-26 02:32:53 +08:00
d6b25c4560
test(config): 将 UsernameAuthenticatorSerializerTest 迁移到 SerializersKtTest.
将测试类移动到对应的文件中.
2022-06-25 23:02:11 +08:00
581eeba20b
feat(config): 新增 HTTPS 代理类型, 增加 Maven 对 HTTPS 代理的支持.
为 ProxyType 增加 HTTPS 类型, 同时为 Aether 增加 Https 代理支持, 方便用户使用现有的公开代理下载依赖包.
2022-06-25 23:00:51 +08:00
896305f4a3
style: 改一下代码格式.
把“宝塔”改掉, 看起来太难受了.
2022-06-25 21:17:27 +08:00
df484d6bd7
test(config): 调整测试数据, 以符合该测试用例的情况.
为 BotConfig 的完整序列化测试添加 Artifact 值, 覆盖解析 BotConfig 所涉及的 Artifact 序列化.
2022-06-25 19:19:55 +08:00
b8a99a4491
fix(config): 修正 BotConfigSerializer 中使用的错误默认值.
由于默认值未及时变更, 导致出现默认值与预期不符的情况;
目前已调整了新的默认值获取方式, 以便于后续调整默认值.
2022-06-24 19:51:27 +08:00
128e33e545
test(config): 移动 UsernameAuthenticatorTest 到新的包路径.
UsernameAuthenticatorTest 所测试的 UsernameAuthenticator 是属于 config 包的, 所以修正了一下这个问题.
2022-06-24 19:44:31 +08:00
85e59f4a64
test(config): 将 ArtifactSerializerTest 合并到 SerializersKtTest, 并添加新的单元测试.
为了归类单元测试, 所以将 ArtifactSerializerTest 合并到 SerializersKtTest;
添加 ProxyConfig 和 BotConfig 的单元测试类.
2022-06-24 19:43:06 +08:00
8f8d763566
test(config): 合并 BotAccountTest 并补充其他配置类的解析测试.
将 BotAccountTest 合并入 ConfigsTest, 方便归类测试, 并补充其他配置类的 JSON 解析;
此部分独立于 Serializer 以防止后续更改出现潜在的解析错误.
2022-06-24 19:38:47 +08:00
084280564a
fix(config): 添加两个序列化器来修复因 Gson 导致的解析错误.
由于 Gson 的解析方式不能正确处理 Kotlin 的 null-safety 属性, 因此添加两个 Serializer, 手动解析 Json 以避开这个问题.

Close #9
2022-06-24 19:36:39 +08:00
4a160ad42b
refactor(config): 更改 BotConfig.enabled 的默认值为 false.
更改 enabled 的默认值, 以防止意外启动 Bot.
同时让 bot.json 在初始化时设为 true, 方便用户改完就能启动.
2022-06-24 19:10:39 +08:00
a1790a0716
perf(config): 优化配置使用过程中的判断.
通过调整部分属性的 null-safety 特性, 移除了部分 non-null 判断, 略微(真的很略微)提高了性能(虽然仅限于启动).
2022-06-24 19:08:41 +08:00
b12758bd18
refactor(config): 更改部分配置类的属性默认值.
为了保证扩展中命令的权限判断有效性, 故移除 BotAccount 中 creatorId 字段的默认值, 此改动将要求用户提供准确的 Bot 创建者 Id.
这个改动拖得越久, 影响的范围就越大.
另外, 为 BotConfig 中的 extensions 属性和 proxy 属性增加默认值, 以减少意义重复的情况(例如当用户没设置 proxy 属性时提供一个 type 为 NO_PROXY 的 ProxyConfig, 无需判断是否为 null).
2022-06-24 02:00:11 +08:00
a2667438f2
refactor(config): 包装 Serializer 可能抛出的异常.
增加对 Serializer 中可能抛出的异常(例如 MalformedURLException, IllegalArgumentException)包装成 JsonParseException, 以避免异常类型混乱的问题.
2022-06-23 12:25:14 +08:00
d5e66156b9
perf(config): 优化 Artifact 的序列化过程.
AbstractArtifact 已经有官方的 toString 实现了, 故不再多此一举.
同时, 如果有不基于 AbstractArtifact 的 Artifact 实现, 将会转换成 DefaultArtifact 并直接使用 toString.
2022-06-23 11:37:37 +08:00
8a33448b19
refactor(config): 调整方法访问权.
迁移前, createDefaultRepositoryId 方法和 checkRepositoryLayout 方法已经是 Private 了,
迁移中出现差错导致变更为 internal, 现已修复.

Pull Request #8
2022-06-23 03:52:04 +08:00
48a5c27cf7
docs: 调整标题段样式.
试一下把标题段落设为居中, 好看点. :P
2022-06-22 21:32:14 +08:00
0c252f69fb
build(action): 设置 Gradle Build Action 为准确的发布版本号.
设置准确的发布版本号, 有利于保证 Action 运行过程的稳定性.
2022-06-22 17:47:18 +08:00
a55f00edf0
build: 指定 Javadoc 的编码为 UTF-8.
指定编码以防止在不同环境下因编码不同而导致项目构建失败.
2022-06-22 15:03:40 +08:00
45244c1fb1
build: 将子项目间共用的 Plugin 声明在根项目.
根据 Gradle 建议[1], 可以将子项目中都有使用的插件, 更改到根项目中, 这么做可以让我们仅更新根项目的插件版本, 让 Gradle 自动同步子项目的插件版本.
---------------------------------
[1]: https://docs.gradle.org/current/userguide/plugins.html#sec:subprojects_plugins_dsl
2022-06-22 15:02:30 +08:00
cfdfa21619
build: 更新 Gradle Wrapper 脚本属性.
为两个 Gradle Wrapper 脚本添加执行权限, 以便于其他开发者使用 Gradle 编译项目.
2022-06-22 12:33:24 +08:00
8e0bf3c22b
build(action): 添加 Action 配置, 用于检查 Commit 状况.
添加了用于检查 Gradle Wrapper 合法性和用于执行测试的 Action,,这两个 Action 将会在 Push 和 Pull Request 中检查代码状况,以便于及时发现问题。
2022-06-22 02:04:40 +08:00
c64f5e739b
refactor(database): 为旧版数据库适配器添加 @Deprecated 注解.
添加注解以表明该适配器已弃用, 但仍然保留适配器以保证旧版数据库正常使用(并迁移到新版数据库).
2022-06-21 01:43:54 +08:00
289b9678f2
refactor(config): 将与配置相关的内容迁移到 scalabot-meta 模块.
通过将配置迁移到单独的模块, 可以方便使用其他程序扩展 ScaleBot, 而不仅仅是让 ScaleBot 成为扩展的平台.

BREAKING CHANGE: 与配置有关的 Class 移动到了 scalabot-meta 模块.

目前仅所有配置类(以 `Config` 结尾的 Class)和相应的序列化类(以 `Serializer` 结尾的)都迁移到了 meta 模块, 但其工具方法则作为扩展函数保留在 app 模块中.
这么做的好处是为了方便其他应用(例如 ScalaBot 外部管理程序)根据需要生成配置文件.
scalabot-meta 将会作为依赖项发布, 可根据需要获取 ScalaBot-meta 生成 ScalaBot 的配置.
此次改动普通用户无需迁移.
2022-06-20 20:55:04 +08:00
dbc4232dd6
test(config): 调整 BotAccount 的单元测试代码.
调整代码有利于后续更新测试用例时减少出错的可能性(虽然基本不换), 修复一个词汇错误.
2022-06-20 16:01:35 +08:00
c662b970f0
test(utils): 补充 deepListFiles 的单元测试.
补充单元测试项, 目前已实现 Utils 单元测试全覆盖(Logger Class 不算).
2022-06-19 02:47:26 +08:00
f148c21390
fix(utils): 修复 deepListFiles 错误地返回了 null 的情况.
预期中, deepListFiles 返回 null 与否是与 listFiles 相同的, 当 File 无法访问, 或者不是一个目录的情况下才会返回 null,
但由于语法疏漏, 导致可能出现即使 listFiles 返回 null 时 deepListFiles 也不返回 null 的情况.
现已修复该问题.
2022-06-19 02:07:24 +08:00
c41aac735c
build: 更新依赖项版本.
已确定无兼容性问题.
2022-06-18 09:57:03 +08:00
ae64de00e7
test(config): 完善 AppPaths 类的单元测试.
补充分支测试内容.
2022-06-18 09:53:45 +08:00
215a4670db
feat(metrics): 运行指标服务端支持设置 HTTP 认证.
支持对运行指标服务端设置 HTTP 认证, 以防止运行指标被非法获取.
2022-06-18 09:20:46 +08:00
c5fe96c02d
test: 新增对 BotAccount.id 字段的单元测试.
该测试有助于确保 id 能正常地从 Token 中获取.
2022-06-15 02:22:58 +08:00
508f14f271
test: 新增对 Artifact.equalsArtifact 的完整单元测试.
该测试已确保完全覆盖(100%).
2022-06-15 02:19:14 +08:00
35c77f6093
perf: 优化 Artifact 的判断条件.
根据相关文档[1], baseVersion 和 Version 不需要同时判断, 只需要单独判断 Version 即可确认版本是否符合.
另外, 如果 Version 不符, 那么 isSnapshot 就没有必要判断(不可能出现 Version 相同的情况下, 一个是快照版, 一个是发布版的情况), 故移除对 baseVersion 和 isSnapshot 的检查.
另外, Properties 属于 Aether 内部的非持久化信息交换方式, 不是必须纳入检查的项目, 故新增参数用于选择是否检查 Properties 是否相同.
------------------------------------
[1]: https://community.sonatype.com/t/what-is-the-differences-between-maven-baseversion-and-maven-version/2937
2022-06-15 02:18:01 +08:00
7e48f4bf0b
build(test): 增加 Kover 测试覆盖率插件, 升级 Mockk 依赖项版本(1.12.3 -> 1.12.4).
Kover 是 Kotlin 官方为弥补 Kotlin 不能使用 Jacoco 所推出的替代品, 目前使用上没有问题.
顺便更新一下 Mockk 版本.
2022-06-15 01:20:35 +08:00
9c05726849
refactor(config): 改进配置读取错误时输出的错误信息.
改进后的信息有助于让用户了解到底发生了什么, 可帮助用户找到出错的配置文件并修复错误的配置格式.
2022-06-11 16:19:08 +08:00
ac0a398afc
release: 发布 0.3.1 版本. 2022-06-07 00:27:20 +08:00
145e5a2141
build: 暂时将发布仓库迁移到 Kuku 的仓库.
由于私有仓库所在的服务器出现问题, 所以暂时将仓库改到 Kuku 的那边.
在此感谢 Kuku 提供仓库!
2022-06-07 00:25:50 +08:00
b5c85e213b
test: 完善序列化器的单元测试.
目前经测试, 已完善到 100% 覆盖率.
2022-05-19 23:54:27 +08:00
746221a085
feat(config): 简化凭证配置过程.
由于先前的配置过程较为麻烦, 故将凭证配置简化为只有用户名和密码.
2022-05-19 23:53:25 +08:00
24f34aa27f
refactor: 调整 checkJsonKey 的所在类, 以便于编写测试用例.
通过调整所在类, 可更好的在单元测试中获取方法对象, 进行测试调用.
2022-05-19 18:20:46 +08:00
31366575a9
test: 补充部分序列化单元测试.
补充一部分测试内容.
2022-05-19 17:55:36 +08:00
37c3275bb6
fix(config): 修复因 Maven 仓库配置中未包括 layout 属性导致解析错误的问题.
当 Maven 仓库采用 JsonObject 形式配置, 且未配置 "layout" 属性时, 将会引发 NPE,
该改动已修复该问题.
2022-05-19 16:54:47 +08:00
72e26bd677
fix(config): 更改 MavenRepositoryConfig.proxy 的默认值.
防止因默认值导致出现错误的代理配置, 故将默认值更改为无代理(null).
2022-05-19 16:43:25 +08:00
9aab3c2a24
feat(config): 将代理类型为 null 的情况视为不使用代理.
为简化用户配置难度, 关闭代理可选择将 type 设为 null, 来表示不需要使用代理.
2022-05-19 15:46:18 +08:00
cac055bb08
test: 完善 AppPaths 中对 BOT_DATA_PATH 环境变量的测试流程.
通过使用 System-Lambda 库, 补充 AppPaths 中对环境变量使用的测试.
2022-05-19 15:01:16 +08:00
51 changed files with 4033 additions and 720 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
**/
!scalabot-app/build/install/

33
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,33 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "daily"
# - package-ecosystem: "gradle"
# directory: "/scalabot-app"
# schedule:
# interval: "weekly"
# - package-ecosystem: "gradle"
# directory: "/scalabot-meta"
# schedule:
# interval: "weekly"
# - package-ecosystem: "gradle"
# directory: "/scalabot-extension"
# schedule:
# interval: "weekly"
# - package-ecosystem: "gradle"
# directory: "/scalabot-ext-example"
# schedule:
# interval: "weekly"

View File

@ -0,0 +1,33 @@
name: Binary compatibility verification (for API)
on:
push:
paths:
- 'scalabot-meta/**'
- 'scalabot-extension/**'
pull_request:
paths:
- 'scalabot-meta/**'
- 'scalabot-extension/**'
permissions:
contents: read
jobs:
apiCompatibilityCheck:
timeout-minutes: 8
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt-hotspot'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build and run binary compatibility verification
run: ./gradlew apiCheck

32
.github/workflows/build-and-test.yml vendored Normal file
View File

@ -0,0 +1,32 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Build and test project
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
timeout-minutes: 8
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt-hotspot'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build and test
run: ./gradlew clean test

62
.github/workflows/create-release.yml vendored Normal file
View File

@ -0,0 +1,62 @@
name: Create release draft
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'
# 该 Action 有以下步骤:
# 1. 拉取并构建代码, 然后生成 Application 发行包;
# 2. 创建 Release, 并标记为 Draft(草稿);
# 3. 上传 Application 发行包;
permissions:
contents: write
jobs:
create-release:
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# 创建更新日志.
- name: 'Get Previous tag'
id: previous-tag
uses: younited/get-previous-tag-action@v1.1.0
with:
match: "v*.*.*"
- name: Set up Python 3
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install Commitizen
run: pip install -U commitizen
- name: Create Change log
run: cz ch --start-rev ${{ steps.previous-tag.outputs.previous-tag }} --file-name ${{ github.workspace }}/CURRENT_CHANGELOG.md
# 开始构建项目.
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt-hotspot'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build and test
run: ./gradlew clean test assembleDist
# 创建新的发行版本
- name: Create Release
uses: softprops/action-gh-release@v2
with:
draft: true
body_path: ${{ github.workspace }}/CURRENT_CHANGELOG.md
files: |
*/build/distributions/*
*/build/libs/*

46
.github/workflows/publish-artifacts.yml vendored Normal file
View File

@ -0,0 +1,46 @@
name: Publish artifacts
on:
release:
types:
- published
env:
IMAGE_NAME: lamgc/scalabot
jobs:
publish-container-image:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt-hotspot'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build and test
run: ./gradlew clean test installDist
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push container image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ env.IMAGE_NAME }}:latest, ${{ env.IMAGE_NAME }}:${{ github.ref_name }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -0,0 +1,46 @@
name: Build development version container image
on:
push:
branches:
- "main"
permissions:
contents: read
jobs:
build:
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt-hotspot'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build project and install Distribution package
run: ./gradlew clean test installDist
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push container image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: lamgc/scalabot:dev
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max

8
Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM eclipse-temurin:21-jdk-alpine
ENV BOT_DATA_PATH /scalabot/data/
WORKDIR /scalabot/run/
CMD ["/scalabot/app/bin/scalabot-app"]
COPY scalabot-app/build/install/scalabot-app/ /scalabot/app/

View File

@ -1,7 +1,11 @@
<div style="text-align: center;">
# ScalaBot # ScalaBot
基于 [rubenlagus/TelegramBots](https://github.com/rubenlagus/TelegramBots) 的可扩展机器人服务器。 Extensible robot server based 基于 [rubenlagus/TelegramBots](https://github.com/rubenlagus/TelegramBots) 的可扩展机器人服务器。
on [rubenlagus/TelegramBots](https://github.com/rubenlagus/TelegramBots). Extensible robot server based on [rubenlagus/TelegramBots](https://github.com/rubenlagus/TelegramBots).
</div>
## 背景 ## 背景

View File

@ -1,3 +1,9 @@
plugins {
kotlin("jvm") version "2.1.0" apply false
id("org.jetbrains.kotlinx.kover") version "0.8.3" apply false
id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.16.3" apply false
}
allprojects { allprojects {
repositories { repositories {
mavenCentral() mavenCentral()
@ -7,5 +13,5 @@ allprojects {
} }
group = "net.lamgc" group = "net.lamgc"
version = "0.3.0" version = "0.8.0-1"
} }

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-7.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

10
gradlew vendored Normal file → Executable file
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:
# #

0
gradlew.bat vendored Normal file → Executable file
View File

View File

@ -1,17 +1,19 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") version "1.6.10" kotlin("jvm")
application application
// id("org.jetbrains.kotlin") version "1.6.10" id("org.jetbrains.kotlinx.kover")
} }
dependencies { dependencies {
implementation(project(":scalabot-meta"))
implementation(project(":scalabot-extension")) implementation(project(":scalabot-extension"))
implementation("org.slf4j:slf4j-api:1.7.36") implementation("org.slf4j:slf4j-api:2.0.11")
implementation("io.github.microutils:kotlin-logging:2.1.21") implementation("io.github.microutils:kotlin-logging:3.0.5")
implementation("ch.qos.logback:logback-classic:1.2.11") implementation("ch.qos.logback:logback-classic:1.5.12")
val aetherVersion = "1.1.0" val aetherVersion = "1.1.0"
implementation("org.eclipse.aether:aether-api:$aetherVersion") implementation("org.eclipse.aether:aether-api:$aetherVersion")
@ -21,30 +23,35 @@ 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.6.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.20") implementation("org.jetbrains.kotlin:kotlin-reflect:2.1.0")
implementation("com.google.code.gson:gson:2.9.0") implementation("com.google.code.gson:gson:2.11.0")
implementation("org.jdom:jdom2:2.0.6.1") implementation("org.jdom:jdom2:2.0.6.1")
implementation("org.telegram:telegrambots-abilities:6.0.1") implementation("org.telegram:telegrambots-abilities:8.0.0")
implementation("org.telegram:telegrambots:6.0.1") implementation("org.telegram:telegrambots-longpolling:8.0.0")
implementation("org.telegram:telegrambots-client:8.0.0")
implementation("io.prometheus:simpleclient:0.15.0") implementation("io.prometheus:simpleclient:0.16.0")
implementation("io.prometheus:simpleclient_httpserver:0.15.0") implementation("io.prometheus:simpleclient_httpserver:0.16.0")
testImplementation(kotlin("test")) testImplementation(kotlin("test"))
testImplementation("io.mockk:mockk:1.12.3") testImplementation("io.mockk:mockk:1.13.13")
testImplementation("com.github.stefanbirkner:system-lambda:1.2.1")
} }
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> {
kotlinOptions.jvmTarget = "11" compilerOptions {
jvmTarget = JvmTarget.JVM_17
}
} }
application { application {
@ -54,3 +61,8 @@ application {
tasks.jar.configure { tasks.jar.configure {
exclude("**/logback-test.xml") exclude("**/logback-test.xml")
} }
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}

View File

@ -3,175 +3,104 @@ package net.lamgc.scalabot
import ch.qos.logback.core.PropertyDefinerBase import ch.qos.logback.core.PropertyDefinerBase
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken import com.google.gson.JsonArray
import mu.KotlinLogging import mu.KotlinLogging
import net.lamgc.scalabot.util.ArtifactSerializer import net.lamgc.scalabot.config.*
import net.lamgc.scalabot.util.AuthenticationSerializer import net.lamgc.scalabot.config.serializer.*
import net.lamgc.scalabot.util.MavenRepositoryConfigSerializer
import net.lamgc.scalabot.util.ProxyTypeSerializer
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.eclipse.aether.repository.RemoteRepository import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.repository.RepositoryPolicy import org.eclipse.aether.repository.RepositoryPolicy
import org.telegram.telegrambots.bots.DefaultBotOptions import org.slf4j.event.Level
import org.telegram.telegrambots.meta.ApiConstants
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
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import kotlin.system.exitProcess import java.util.function.Supplier
import kotlin.reflect.KProperty
private val log = KotlinLogging.logger { } private val log = KotlinLogging.logger { }
/** internal fun ProxyType.toJavaProxyType(): java.net.Proxy.Type? {
* 机器人帐号信息. return when (this) {
* @property name 机器人名称, 建议与实际设定的名称相同. ProxyType.NO_PROXY -> null
* @property token 机器人 API Token. ProxyType.HTTP -> java.net.Proxy.Type.HTTP
* @property creatorId 机器人创建者, 管理机器人需要使用该信息. ProxyType.HTTPS -> java.net.Proxy.Type.HTTP
*/ ProxyType.SOCKS4 -> java.net.Proxy.Type.SOCKS
internal data class BotAccount( ProxyType.SOCKS5 -> java.net.Proxy.Type.SOCKS
val name: String,
val token: String,
val creatorId: Long = -1
) {
val id
// 不要想着每次获取都要从 token 里取出有性能损耗.
// 由于 Gson 解析方式, 如果不这么做, 会出现 token 设置前 id 初始化完成, 就只有"0"了,
// 虽然能过单元测试, 但实际使用过程是不能正常用的.
get() = token.substringBefore(":").toLong()
}
/**
* 机器人配置.
* @property account 机器人帐号信息, 用于访问 API.
* @property disableBuiltInAbility 是否禁用 AbilityBot 自带命令.
* @property extensions 该机器人启用的扩展.
* @property proxy 为该机器人单独设置的代理配置, 如无设置, 则使用 AppConfig 中的代理配置.
*/
internal data class BotConfig(
val enabled: Boolean = true,
val account: BotAccount,
val disableBuiltInAbility: Boolean = false,
val autoUpdateCommandList: Boolean = false,
/*
* 使用构件坐标来选择机器人所使用的扩展包.
* 这么做的原因是我暂时没找到一个合适的方法来让开发者方便地设定自己的扩展 Id,
* 而构件坐标(POM Reference 或者叫 GAV 坐标)是开发者创建 Maven/Gradle 项目时一定会设置的,
* 所以就直接用了. :P
*/
val extensions: Set<Artifact>,
val proxy: ProxyConfig? = ProxyConfig(),
val baseApiUrl: String? = ApiConstants.BASE_URL
)
/**
* 代理配置.
* @property type 代理类型.
* @property host 代理服务端地址.
* @property port 代理服务端端口.
*/
internal data class ProxyConfig(
val type: DefaultBotOptions.ProxyType = DefaultBotOptions.ProxyType.NO_PROXY,
val host: String = "127.0.0.1",
val port: Int = 1080
) {
fun toAetherProxy(): Proxy? {
return if (type == DefaultBotOptions.ProxyType.HTTP) {
Proxy(Proxy.TYPE_HTTP, host, port)
} else {
null
}
} }
} }
internal data class MetricsConfig( internal fun ProxyConfig.toAetherProxy(): Proxy? {
val enable: Boolean = false, val typeStr = when (type) {
val port: Int = 9386, ProxyType.HTTP -> Proxy.TYPE_HTTP
val bindAddress: String? = "0.0.0.0" ProxyType.HTTPS -> Proxy.TYPE_HTTPS
) else -> return null
}
return Proxy(typeStr, host, port)
}
/** internal fun MavenRepositoryConfig.toRemoteRepository(proxyConfig: ProxyConfig? = null): RemoteRepository {
* Maven 远端仓库配置. val repositoryId = if (id == null) {
* @property url 仓库地址. val generatedRepoId = createDefaultRepositoryId()
* @property proxy 访问仓库所使用的代理, 仅支持 http/https 代理. log.debug { "仓库 Url `$url` 未设置仓库 Id, 已分配缺省 Id: $generatedRepoId" }
* @property layout 仓库布局版本, Maven 2 及以上使用 `default`, Maven 1 使用 `legacy`. generatedRepoId
*/ } else if ("local".contentEquals(id, ignoreCase = true)) {
internal data class MavenRepositoryConfig( val generatedRepoId = createDefaultRepositoryId()
val id: String? = null, log.debug { "仓库 Url `$url` 不允许使用 `local` 作为仓库 Id, 已分配缺省 Id: $generatedRepoId" }
val url: URL, generatedRepoId
val proxy: Proxy? = Proxy("http", "127.0.0.1", 1080), } else {
val layout: String = "default", id
val enableReleases: Boolean = true, }
val enableSnapshots: Boolean = true, val builder = RemoteRepository.Builder(repositoryId, checkRepositoryLayout(layout), url.toString())
// 可能要设计个 type 来判断解析成什么类型的 Authentication. if (proxy != null) {
val authentication: Authentication? = null val selfProxy = proxy!!
) { builder.setProxy(selfProxy)
log.debug { "仓库 $repositoryId 已使用独立的代理配置: ${selfProxy.type}://${selfProxy.host}:${selfProxy.port}" }
fun toRemoteRepository(proxyConfig: ProxyConfig): RemoteRepository { } else if (proxyConfig != null) {
val builder = if (proxyConfig.type in (ProxyType.HTTP..ProxyType.HTTPS)) {
RemoteRepository.Builder(id ?: createDefaultRepositoryId(), checkRepositoryLayout(layout), url.toString())
if (proxy != null) {
builder.setProxy(proxy)
} else if (proxyConfig.type == DefaultBotOptions.ProxyType.HTTP) {
builder.setProxy(proxyConfig.toAetherProxy()) builder.setProxy(proxyConfig.toAetherProxy())
log.debug { "仓库 $repositoryId 已使用 全局/Bot 代理配置: $proxyConfig" }
} else {
log.debug { "仓库 $repositoryId 不支持 全局/Bot 的代理配置: `$proxyConfig` (仅支持 HTTP 和 HTTPS)" }
} }
} else {
builder.setReleasePolicy( log.debug { "仓库 $repositoryId 不使用代理." }
RepositoryPolicy(
enableReleases,
RepositoryPolicy.UPDATE_POLICY_NEVER,
RepositoryPolicy.CHECKSUM_POLICY_FAIL
)
)
builder.setSnapshotPolicy(
RepositoryPolicy(
enableSnapshots,
RepositoryPolicy.UPDATE_POLICY_ALWAYS,
RepositoryPolicy.CHECKSUM_POLICY_WARN
)
)
return builder.build()
} }
private companion object { builder.setReleasePolicy(
fun checkRepositoryLayout(layoutType: String): String { RepositoryPolicy(
val type = layoutType.trim().lowercase() enableReleases,
if (type != "default" && type != "legacy") { RepositoryPolicy.UPDATE_POLICY_NEVER,
throw IllegalArgumentException("Invalid layout type (expecting 'default' or 'legacy')") RepositoryPolicy.CHECKSUM_POLICY_FAIL
} )
return type )
} builder.setSnapshotPolicy(
RepositoryPolicy(
enableSnapshots,
RepositoryPolicy.UPDATE_POLICY_ALWAYS,
RepositoryPolicy.CHECKSUM_POLICY_WARN
)
)
private val repoNumber = AtomicInteger(1) return builder.build()
fun createDefaultRepositoryId(): String {
return "Repository-${repoNumber.getAndIncrement()}"
}
}
} }
/** private fun checkRepositoryLayout(layoutType: String): String {
* ScalaBot App 配置. val type = layoutType.trim().lowercase()
* if (type != "default" && type != "legacy") {
* App 配置信息与 BotConfig 分开, 分别存储在各自单独的文件中. throw IllegalArgumentException("Invalid layout type (expecting 'default' or 'legacy')")
* @property proxy Telegram API 代理配置. }
* @property metrics 运行指标数据配置. 可通过时序数据库记录运行数据. return type
* @property mavenRepositories Maven 远端仓库配置. }
* @property mavenLocalRepository Maven 本地仓库路径. 相对于运行目录 (而不是 DATA_ROOT 目录)
*/ private val repoNumberGenerator = AtomicInteger(1)
internal data class AppConfig(
val proxy: ProxyConfig = ProxyConfig(), private fun createDefaultRepositoryId(): String {
val metrics: MetricsConfig = MetricsConfig(), return "Repository-${repoNumberGenerator.getAndIncrement()}"
val mavenRepositories: List<MavenRepositoryConfig> = emptyList(), }
val mavenLocalRepository: String? = null
)
/** /**
* 需要用到的路径. * 需要用到的路径.
@ -179,9 +108,9 @@ internal data class AppConfig(
* 必须提供 `pathSupplier` `fileSupplier` 其中一个, 才能正常提供路径. * 必须提供 `pathSupplier` `fileSupplier` 其中一个, 才能正常提供路径.
*/ */
internal enum class AppPaths( internal enum class AppPaths(
private val pathSupplier: () -> String = { fileSupplier.invoke().canonicalPath }, private val pathSupplier: PathSupplier,
private val initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer, private val initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer,
private val fileSupplier: () -> File = { File(pathSupplier()) } private val fileSupplier: FileSupplier,
) { ) {
/** /**
* 数据根目录. * 数据根目录.
@ -190,7 +119,7 @@ internal enum class AppPaths(
* *
* 提示: 结尾不带 `/`. * 提示: 结尾不带 `/`.
*/ */
DATA_ROOT(fileSupplier = { DATA_ROOT(fileSupplier = FileSupplier {
File( File(
System.getProperty(PathConst.PROP_DATA_PATH) ?: System.getenv(PathConst.ENV_DATA_PATH) System.getProperty(PathConst.PROP_DATA_PATH) ?: System.getenv(PathConst.ENV_DATA_PATH)
?: System.getProperty("user.dir") ?: "." ?: System.getProperty("user.dir") ?: "."
@ -202,10 +131,10 @@ internal enum class AppPaths(
} }
}), }),
DEFAULT_CONFIG_APPLICATION({ "$DATA_ROOT/config.json" }, { CONFIG_APPLICATION(PathSupplier { "$DATA_ROOT/config.json" }, {
if (!file.exists()) { if (!file.exists()) {
file.bufferedWriter(StandardCharsets.UTF_8).use { file.bufferedWriter(StandardCharsets.UTF_8).use {
GsonConst.botConfigGson.toJson( GsonConst.appConfigGson.toJson(
AppConfig( AppConfig(
mavenRepositories = listOf( mavenRepositories = listOf(
MavenRepositoryConfig( MavenRepositoryConfig(
@ -218,13 +147,13 @@ internal enum class AppPaths(
} }
} }
}), }),
DEFAULT_CONFIG_BOT({ "$DATA_ROOT/bot.json" }, { CONFIG_BOT(PathSupplier { "$DATA_ROOT/bot.json" }, {
if (!file.exists()) { if (!file.exists()) {
file.bufferedWriter(StandardCharsets.UTF_8).use { file.bufferedWriter(StandardCharsets.UTF_8).use {
GsonConst.botConfigGson.toJson( GsonConst.botConfigGson.toJson(
setOf( setOf(
BotConfig( BotConfig(
enabled = false, enabled = true,
proxy = ProxyConfig(), proxy = ProxyConfig(),
account = BotAccount( account = BotAccount(
"Bot Username", "Bot Username",
@ -244,10 +173,25 @@ internal enum class AppPaths(
TEMP({ "$DATA_ROOT/tmp/" }) TEMP({ "$DATA_ROOT/tmp/" })
; ;
val file: File constructor(pathSupplier: PathSupplier, initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer) : this(
get() = fileSupplier.invoke() fileSupplier = FileSupplier { File(pathSupplier.path).canonicalFile },
val path: String pathSupplier = pathSupplier,
get() = pathSupplier.invoke() initializer = initializer
)
constructor(fileSupplier: FileSupplier, initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer) : this(
fileSupplier = fileSupplier,
pathSupplier = PathSupplier { fileSupplier.file.canonicalPath },
initializer = initializer
)
constructor(pathSupplier: () -> String) : this(
fileSupplier = FileSupplier { File(pathSupplier.invoke()).canonicalFile },
pathSupplier = PathSupplier { pathSupplier.invoke() }
)
val file: File by fileSupplier
val path: String by pathSupplier
private val initialized = AtomicBoolean(false) private val initialized = AtomicBoolean(false)
@ -259,15 +203,44 @@ internal enum class AppPaths(
} }
} }
/**
* 一个内部方法, 用于将 [initialized] 状态重置.
*
* 如果不重置该状态, 将使得单元测试无法让 AppPath 重新初始化文件.
*
* 警告: 该方法不应该被非测试代码调用.
*/
@Suppress("unused")
private fun reset() {
log.warn {
"初始化状态已重置: `${this.name}`, 如果在非测试环境中重置状态, 请报告该问题."
}
initialized.set(false)
}
override fun toString(): String { override fun toString(): String {
return path return path
} }
private object PathConst { object PathConst {
const val PROP_DATA_PATH = "bot.path.data" const val PROP_DATA_PATH = "bot.path.data"
const val ENV_DATA_PATH = "BOT_DATA_PATH" const val ENV_DATA_PATH = "BOT_DATA_PATH"
} }
private class FileSupplier(private val supplier: Supplier<File>) {
operator fun getValue(appPaths: AppPaths, property: KProperty<*>): File = supplier.get()
val file: File
get() = supplier.get()
}
private class PathSupplier(private val supplier: Supplier<String>) {
operator fun getValue(appPaths: AppPaths, property: KProperty<*>): String = supplier.get()
val path: String
get() = supplier.get()
}
} }
/** /**
@ -279,8 +252,37 @@ internal class LogDirectorySupplier : PropertyDefinerBase() {
} }
} }
internal class LogLevelSupplier : PropertyDefinerBase() {
override fun getPropertyValue(): String {
val property = System.getProperty("scalabot.log.level", System.getenv("BOT_LOG_LEVEL"))
val level = if (property != null) {
try {
Level.valueOf(property.uppercase())
} catch (e: IllegalArgumentException) {
addWarn("Invalid log level: `$property`, the log will be output using the Info log level.")
Level.INFO
}
} else {
Level.INFO
}
return level.name
}
}
internal class NetworkVerboseLogSupplier : PropertyDefinerBase() {
override fun getPropertyValue(): String {
val propertyValue = System.getProperty("scalabot.log.network.verbose", "false")
return if (propertyValue.toBoolean()) {
"DEBUG"
} else {
"INFO"
}
}
}
internal object Const { internal object Const {
val config = loadAppConfig() val config = loadAppConfig()
const val METRICS_NAMESPACE = "scalabot"
} }
private fun AppPaths.defaultInitializer() { private fun AppPaths.defaultInitializer() {
@ -298,46 +300,65 @@ private fun AppPaths.defaultInitializer() {
} }
} }
internal fun initialFiles() { /**
val configFilesNotInitialized = !AppPaths.DEFAULT_CONFIG_APPLICATION.file.exists() * 执行 AppPaths 所有项目的初始化, 并检查是否停止运行, 让用户编辑配置.
&& !AppPaths.DEFAULT_CONFIG_BOT.file.exists() *
* @return 如果需要让用户编辑配置, 则返回 `true`.
*/
internal fun initialFiles(): Boolean {
val configFilesNotInitialized = !AppPaths.CONFIG_APPLICATION.file.exists()
&& !AppPaths.CONFIG_BOT.file.exists()
for (path in AppPaths.values()) { for (path in AppPaths.entries) {
path.initial() path.initial()
} }
if (configFilesNotInitialized) { if (configFilesNotInitialized) {
log.warn { "配置文件已初始化, 请根据需要修改配置文件后重新启动本程序." } log.warn { "配置文件已初始化, 请根据需要修改配置文件后重新启动本程序." }
exitProcess(1) return true
} }
return false
} }
private object GsonConst { internal object GsonConst {
val baseGson: Gson = GsonBuilder() private val baseGson: Gson = GsonBuilder()
.setPrettyPrinting() .setPrettyPrinting()
.serializeNulls() .serializeNulls()
.create() .create()
val appConfigGson: Gson = baseGson.newBuilder() val appConfigGson: Gson = baseGson.newBuilder()
.registerTypeAdapter(DefaultBotOptions.ProxyType::class.java, ProxyTypeSerializer) .registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer)
.registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer) .registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer)
.registerTypeAdapter(Authentication::class.java, AuthenticationSerializer) .registerTypeAdapter(Authentication::class.java, AuthenticationSerializer)
.registerTypeAdapter(UsernameAuthenticator::class.java, UsernameAuthenticatorSerializer)
.create() .create()
val botConfigGson: Gson = baseGson.newBuilder() val botConfigGson: Gson = baseGson.newBuilder()
.registerTypeAdapter(DefaultBotOptions.ProxyType::class.java, ProxyTypeSerializer) .registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer)
.registerTypeAdapter(BotConfig::class.java, BotConfigSerializer)
.registerTypeAdapter(Artifact::class.java, ArtifactSerializer) .registerTypeAdapter(Artifact::class.java, ArtifactSerializer)
.registerTypeAdapter(BotAccount::class.java, BotAccountSerializer)
.create() .create()
} }
internal fun loadAppConfig(configFile: File = AppPaths.DEFAULT_CONFIG_APPLICATION.file): AppConfig { internal fun loadAppConfig(configFile: File = AppPaths.CONFIG_APPLICATION.file): AppConfig {
configFile.bufferedReader(StandardCharsets.UTF_8).use { try {
return GsonConst.appConfigGson.fromJson(it, AppConfig::class.java)!! configFile.bufferedReader(StandardCharsets.UTF_8).use {
return GsonConst.appConfigGson.fromJson(it, AppConfig::class.java)!!
}
} catch (e: Exception) {
log.error { "读取 config.json 时发生错误, 请检查配置格式是否正确." }
throw e
} }
} }
internal fun loadBotConfig(botConfigFile: File = AppPaths.DEFAULT_CONFIG_BOT.file): Set<BotConfig> { internal fun loadBotConfigJson(botConfigFile: File = AppPaths.CONFIG_BOT.file): JsonArray? {
botConfigFile.bufferedReader(StandardCharsets.UTF_8).use { try {
return GsonConst.botConfigGson.fromJson(it, object : TypeToken<Set<BotConfig>>() {}.type)!! botConfigFile.bufferedReader(StandardCharsets.UTF_8).use {
return GsonConst.botConfigGson.fromJson(it, JsonArray::class.java)!!
}
} catch (e: Exception) {
log.error(e) { "读取 Bot 配置文件 (bot.json) 时发生错误, 请检查配置格式是否正确." }
return null
} }
} }

View File

@ -1,16 +1,22 @@
package net.lamgc.scalabot package net.lamgc.scalabot
import com.google.gson.JsonParseException
import io.prometheus.client.exporter.HTTPServer import io.prometheus.client.exporter.HTTPServer
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import mu.KotlinLogging import mu.KotlinLogging
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.bots.DefaultBotOptions import org.telegram.telegrambots.client.okhttp.OkHttpTelegramClient
import org.telegram.telegrambots.meta.TelegramBotsApi import org.telegram.telegrambots.longpolling.BotSession
import org.telegram.telegrambots.meta.generics.BotSession import org.telegram.telegrambots.longpolling.TelegramBotsLongPollingApplication
import org.telegram.telegrambots.updatesreceivers.DefaultBotSession import org.telegram.telegrambots.meta.api.methods.GetMe
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException
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
@ -24,12 +30,15 @@ private val log = KotlinLogging.logger { }
fun main(args: Array<String>): Unit = runBlocking { fun main(args: Array<String>): Unit = runBlocking {
log.info { "ScalaBot 正在启动中..." } log.info { "ScalaBot 正在启动中..." }
log.info { "数据目录: ${AppPaths.DATA_ROOT}" } log.info { "数据目录: ${AppPaths.DATA_ROOT}" }
log.debug { "Kotlin: ${KotlinVersion.CURRENT}, JVM: ${Runtime.version()}" }
log.debug { "启动参数: ${args.joinToString(prefix = "[", postfix = "]")}" } log.debug { "启动参数: ${args.joinToString(prefix = "[", postfix = "]")}" }
initialFiles() if (initialFiles()) {
exitProcess(1)
}
val launcher = Launcher() val launcher = Launcher()
.registerShutdownHook() .registerShutdownHook()
startMetricsServer() startMetricsServer()?.registerShutdownHook()
if (!launcher.launch()) { if (!launcher.launch()) {
exitProcess(1) exitProcess(1)
} }
@ -39,61 +48,60 @@ fun main(args: Array<String>): Unit = runBlocking {
* 启动运行指标服务器. * 启动运行指标服务器.
* 使用 Prometheus 指标格式. * 使用 Prometheus 指标格式.
*/ */
internal fun startMetricsServer(config: MetricsConfig = Const.config.metrics) { internal fun startMetricsServer(config: MetricsConfig = Const.config.metrics): HTTPServer? {
if (!config.enable) { if (!config.enable) {
log.debug { "运行指标服务器已禁用." } log.debug { "运行指标服务器已禁用." }
return return null
} }
val builder = HTTPServer.Builder() val builder = HTTPServer.Builder()
.withDaemonThreads(true) .withDaemonThreads(true)
.withAuthenticator(config.authenticator)
.withPort(config.port) .withPort(config.port)
.withHostname(config.bindAddress) .withHostname(config.bindAddress)
val httpServer = builder val httpServer = builder
.build() .build()
.registerShutdownHook()
log.info { "运行指标服务器已启动. (Port: ${httpServer.port})" } log.info { "运行指标服务器已启动. (Port: ${httpServer.port})" }
return httpServer
} }
internal class Launcher(private val config: AppConfig = Const.config) : AutoCloseable { internal class Launcher(
private val config: AppConfig = Const.config,
private val configFile: File = AppPaths.CONFIG_APPLICATION.file,
) : AutoCloseable {
companion object { companion object {
@JvmStatic @JvmStatic
private val log = KotlinLogging.logger { } private val log = KotlinLogging.logger { }
} }
private val botApi = TelegramBotsApi(DefaultBotSession::class.java) private val botApi = TelegramBotsLongPollingApplication()
private val botSessionMap = mutableMapOf<ScalaBot, BotSession>() private val botSessionMap = mutableMapOf<ScalaBot, BotSession>()
private val mavenLocalRepository = getMavenLocalRepository() private val mavenLocalRepository = getMavenLocalRepository()
private fun getMavenLocalRepository(): LocalRepository { private fun getMavenLocalRepository(): LocalRepository {
val localPath = val localPath =
if (config.mavenLocalRepository != null && config.mavenLocalRepository.isNotEmpty()) { if (config.mavenLocalRepository != null && config.mavenLocalRepository!!.isNotEmpty()) {
val repoPath = AppPaths.DATA_ROOT.file.toPath() val repoPath = configFile.toPath().resolve(config.mavenLocalRepository!!).apply {
.resolve(config.mavenLocalRepository) if (!exists()) {
.apply { if (!parent.isWritable() || !parent.isReadable()) {
if (!exists()) { throw IOException("Unable to read and write the directory where Maven repository is located.")
if (!parent.isWritable() || !parent.isReadable()) { }
throw IOException("Unable to read and write the directory where Maven repository is located.") if (System.getProperty("os.name").lowercase().startsWith("windows")) {
} createDirectories()
if (System.getProperty("os.name").lowercase().startsWith("windows")) { } else {
createDirectories() val fileAttributes = setOf(
} else { PosixFilePermission.OWNER_READ,
createDirectories( PosixFilePermission.OWNER_WRITE,
PosixFilePermissions.asFileAttribute( PosixFilePermission.GROUP_READ,
setOf( PosixFilePermission.GROUP_WRITE,
PosixFilePermission.OWNER_READ, PosixFilePermission.OTHERS_READ,
PosixFilePermission.OWNER_WRITE, )
PosixFilePermission.GROUP_READ, createDirectories(PosixFilePermissions.asFileAttribute(fileAttributes))
PosixFilePermission.GROUP_WRITE,
PosixFilePermission.OTHERS_READ,
)
)
)
}
} }
} }
}
.toRealPath() .toRealPath()
.toFile() .toFile()
repoPath repoPath
@ -108,22 +116,47 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos
@Synchronized @Synchronized
fun launch(): Boolean { fun launch(): Boolean {
val botConfigs = loadBotConfig() val botConfigs = loadBotConfigJson() ?: return false
if (botConfigs.isEmpty()) { if (botConfigs.isEmpty) {
log.warn { "尚未配置任何机器人, 请先配置机器人后再启动本程序." } log.warn { "尚未配置任何机器人, 请先配置机器人后再启动本程序." }
return false return false
} else if (botConfigs.none { it.enabled }) {
log.warn { "配置文件中没有已启用的机器人, 请至少启用一个机器人." }
return false
} }
for (botConfig in botConfigs) { var launchedCounts = 0
for (botConfigJson in botConfigs) {
val botConfig = try {
GsonConst.botConfigGson.fromJson(botConfigJson, BotConfig::class.java)
} catch (e: JsonParseException) {
val botName = try {
botConfigJson.asJsonObject.get("account")?.asJsonObject?.get("name")?.asString ?: "Unknown"
} catch (e: Exception) {
"Unknown"
}
log.error(e) { "机器人 `$botName` 配置有误, 跳过该机器人的启动." }
continue
}
try { try {
launchBot(botConfig) launchBot(botConfig)
launchedCounts++
} catch (e: Exception) { } catch (e: Exception) {
log.error(e) { "机器人 `${botConfig.account.name}` 启动时发生错误." } if (e is TelegramApiRequestException && e.errorCode == 401) {
log.error { "机器人 `${botConfig.account.name}` 的 Bot Token 无效, 请检查配置: [${e.errorCode}] ${e.apiResponse}" }
} else {
log.error(e) { "机器人 `${botConfig.account.name}` 启动时发生错误." }
}
} }
} }
return true
botApi.start()
botApi.registerShutdownHook()
return if (launchedCounts != 0) {
log.info { "已启动 $launchedCounts 个机器人." }
true
} else {
log.warn { "未启动任何机器人, 请检查配置并至少启用一个机器人." }
false
}
} }
private fun launchBot(botConfig: BotConfig) { private fun launchBot(botConfig: BotConfig) {
@ -132,36 +165,38 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos
return return
} }
log.info { "正在启动机器人 `${botConfig.account.name}`..." } log.info { "正在启动机器人 `${botConfig.account.name}`..." }
val botOption = DefaultBotOptions().apply { val proxyConfig =
val proxyConfig = if (botConfig.proxy.type != ProxyType.NO_PROXY) {
if (botConfig.proxy != null && botConfig.proxy.type != DefaultBotOptions.ProxyType.NO_PROXY) { log.debug { "[Bot ${botConfig.account.name}] 使用独立代理: ${botConfig.proxy.type}" }
botConfig.proxy botConfig.proxy
} else if (config.proxy.type != DefaultBotOptions.ProxyType.NO_PROXY) { } else if (config.proxy.type != ProxyType.NO_PROXY) {
config.proxy log.debug { "[Bot ${botConfig.account.name}] 使用全局代理: ${botConfig.proxy.type}" }
} else { config.proxy
null } else {
} log.debug { "[Bot ${botConfig.account.name}] 不使用代理." }
if (proxyConfig != null) { ProxyConfig(type = ProxyType.NO_PROXY)
proxyType = proxyConfig.type
proxyHost = config.proxy.host
proxyPort = config.proxy.port
log.debug { "机器人 `${botConfig.account.name}` 已启用代理配置: $proxyConfig" }
} }
if (botConfig.baseApiUrl != null) { val okhttpClientBuilder = OkHttpClient.Builder()
baseUrl = botConfig.baseApiUrl
} if (proxyConfig.type != ProxyType.NO_PROXY) {
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(config.proxy) } .map { it.toRemoteRepository(proxyConfig) }
.toMutableList().apply { .toMutableList().apply {
if (this.none { if (this.none {
it.url == MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL it.url == MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL
|| it.url == MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL.trimEnd('/') || it.url == MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL.trimEnd('/')
}) { }) {
add(MavenRepositoryExtensionFinder.getMavenCentralRepository(proxy = config.proxy.toAetherProxy())) add(MavenRepositoryExtensionFinder.getMavenCentralRepository(proxy = proxyConfig.toAetherProxy()))
} }
}.toList() }.toList()
val extensionPackageFinders = setOf( val extensionPackageFinders = setOf(
@ -174,11 +209,15 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos
val bot = ScalaBot( val bot = ScalaBot(
BotDBMaker.getBotDbInstance(account), BotDBMaker.getBotDbInstance(account),
botOption, telegramClient,
extensionPackageFinders, extensionPackageFinders,
botConfig botConfig
) )
botSessionMap[bot] = botApi.registerBot(bot)
val botUser = bot.telegramClient.execute(GetMe())
log.debug { "已验证 Bot Token 有效性, Bot Username: ${botUser.userName}" }
botSessionMap[bot] = botApi.registerBot(botConfig.account.token, bot)
log.info { "机器人 `${bot.botUsername}` 已启动." } log.info { "机器人 `${bot.botUsername}` 已启动." }
if (botConfig.autoUpdateCommandList) { if (botConfig.autoUpdateCommandList) {

View File

@ -2,21 +2,23 @@ package net.lamgc.scalabot
import com.google.common.io.Files import com.google.common.io.Files
import mu.KotlinLogging import mu.KotlinLogging
import net.lamgc.scalabot.config.BotAccount
import net.lamgc.scalabot.util.toHexString 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.abilitybots.api.db.DBContext import org.telegram.telegrambots.abilitybots.api.db.DBContext
import org.telegram.abilitybots.api.db.MapDBContext import org.telegram.telegrambots.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
/** /**
* 数据库适配器. * 数据库适配器列表.
*
* 应按照新到旧的顺序放置, 新的适配器应该在上面. * 应按照新到旧的顺序放置, 新的适配器应该在上面.
* @suppress 由于本列表需要设置已弃用的适配器以保证旧版数据库的正常使用, 故忽略弃用警告.
*/ */
@Suppress("DEPRECATION")
private val adapters = arrayListOf<DbAdapter>( private val adapters = arrayListOf<DbAdapter>(
BotAccountIdDbAdapter, // since [v0.2.0 ~ latest) BotAccountIdDbAdapter, // since [v0.2.0 ~ latest)
BotTokenDbAdapter // since [v0.0.1 ~ v0.2.0) BotTokenDbAdapter // since [v0.0.1 ~ v0.2.0)
@ -155,7 +157,6 @@ 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()) {
@ -183,8 +184,9 @@ private object BotAccountIdDbAdapter : FileDbAdapter("BotAccountId", { botAccoun
* *
* **已弃用**: 由于 Token 可以重新生成, Token 改变后数据库文件名也会改变, 故弃用该方法. * **已弃用**: 由于 Token 可以重新生成, Token 改变后数据库文件名也会改变, 故弃用该方法.
*/ */
@Deprecated(message = "由于 BotToken 可变, 故不再使用该适配器.", level = DeprecationLevel.WARNING)
private object BotTokenDbAdapter : FileDbAdapter("BotToken_v0.1.0", { botAccount -> private object BotTokenDbAdapter : FileDbAdapter("BotToken_v0.1.0", { botAccount ->
val digest: MessageDigest = MessageDigest.getInstance("SHA-256") val digest: MessageDigest = MessageDigest.getInstance("SHA-256")
val digestBytes = digest.digest(botAccount.token.toByteArray(StandardCharsets.UTF_8)) val digestBytes = digest.digest(botAccount.token.toByteArray(StandardCharsets.UTF_8))
File(AppPaths.DATA_DB.file, "${digestBytes.toHexString()}.db") File(AppPaths.DATA_DB.file, "${digestBytes.toHexString()}.db")
}) })

View File

@ -1,10 +1,11 @@
package net.lamgc.scalabot package net.lamgc.scalabot
import mu.KotlinLogging import mu.KotlinLogging
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.abilitybots.api.util.AbilityExtension import org.telegram.telegrambots.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
@ -14,6 +15,18 @@ import java.nio.charset.StandardCharsets
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
/**
* 扩展加载器.
*
* 扩展加载器并非负责加载扩展的 Class, 而是委派搜索器发现并获取扩展, 然后加载扩展实例.
*
* 注意, 扩展加载器将内置两个 Finder: [FileNameFinder] [MavenMetaInformationFinder].
*
* @param bot 扩展加载器所负责的 ScalaBot 实例.
* @param extensionsDataFolder 提供给扩展用于数据存储的根目录(实际目录为 `{root}/{group...}/{artifact}`).
* @param extensionsPath 提供给 Finder 用于搜索扩展的本地扩展包存放路径.
* @param extensionFinders 加载器所使用的搜索器集合. 加载扩展时将使用所提供的的加载器.
*/
internal class ExtensionLoader( internal class ExtensionLoader(
private val bot: ScalaBot, private val bot: ScalaBot,
private val extensionsDataFolder: File = AppPaths.DATA_EXTENSIONS.file, private val extensionsDataFolder: File = AppPaths.DATA_EXTENSIONS.file,
@ -27,6 +40,13 @@ internal class ExtensionLoader(
MavenMetaInformationFinder MavenMetaInformationFinder
).apply { addAll(extensionFinders) }.toSet() ).apply { addAll(extensionFinders) }.toSet()
/**
* 加载扩展, 并返回扩展项.
*
* 调用本方法后, 将会指派提供的 Finder 搜索 ScalaBot 配置的扩展包.
*
* @return 返回存放了所有已加载扩展项的 Set. 可通过 [LoadedExtensionEntry] 获取扩展的有关信息.
*/
fun getExtensions(): Set<LoadedExtensionEntry> { fun getExtensions(): Set<LoadedExtensionEntry> {
val extensionEntries = mutableSetOf<LoadedExtensionEntry>() val extensionEntries = mutableSetOf<LoadedExtensionEntry>()
for (extensionArtifact in bot.extensions) { for (extensionArtifact in bot.extensions) {
@ -52,6 +72,17 @@ internal class ExtensionLoader(
/** /**
* 检查是否发生冲突. * 检查是否发生冲突.
*
* 扩展包冲突有两种情况:
* 1. 有多个同为最高优先级的搜索器搜索到了扩展包.
* 2. 唯一的最高优先级搜索器搜索到了多个扩展包.
*
* 扩展包冲突指的是**有多个具有相同构件坐标的扩展包被搜索到**,
* 如果不顾扩展包冲突直接加载的话, 将会出现安全隐患,
* 因此在加载器发现冲突的情况下将输出相关信息, 提示用户进行排查.
*
* @param foundResult 扩展包搜索结果.
*
* @return 如果出现冲突, 返回 `true`. * @return 如果出现冲突, 返回 `true`.
*/ */
private fun checkConflict(foundResult: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>>): Boolean { private fun checkConflict(foundResult: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>>): Boolean {
@ -68,6 +99,9 @@ internal class ExtensionLoader(
} }
} }
/**
* 从结果中过滤出由最高优先级的搜索器搜索到的扩展包.
*/
private fun filterHighPriorityResult(foundResult: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>>) private fun filterHighPriorityResult(foundResult: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>>)
: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>> { : Map<ExtensionPackageFinder, Set<FoundExtensionPackage>> {
val finders: List<ExtensionPackageFinder> = foundResult.keys val finders: List<ExtensionPackageFinder> = foundResult.keys
@ -89,7 +123,13 @@ internal class ExtensionLoader(
for (factory in extClassLoader.serviceLoader) { for (factory in extClassLoader.serviceLoader) {
try { try {
val extension = val extension =
factory.createExtensionInstance(bot, getExtensionDataFolder(extensionArtifact)) factory.createExtensionInstance(
bot, getExtensionDataFolder(extensionArtifact),
BotExtensionCreateOptions(
bot.accountId,
bot.botConfig.proxy.copy()
)
)
if (extension == null) { if (extension == null) {
log.debug { "Factory ${factory::class.java} 创建插件时返回了 null, 已跳过. (BotName: ${bot.botUsername})" } log.debug { "Factory ${factory::class.java} 创建插件时返回了 null, 已跳过. (BotName: ${bot.botUsername})" }
continue continue
@ -102,6 +142,11 @@ internal class ExtensionLoader(
return factories.toSet() return factories.toSet()
} }
/**
* 只是用来统计扩展包搜索结果的数量而已.
*
* @return 返回扩展包的数量.
*/
private fun allFoundedPackageNumber(filesMap: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>>): Int { private fun allFoundedPackageNumber(filesMap: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>>): Int {
var number = 0 var number = 0
for (files in filesMap.values) { for (files in filesMap.values) {
@ -110,6 +155,14 @@ internal class ExtensionLoader(
return number return number
} }
/**
* 搜索指定构件坐标的依赖包.
*
* 搜索扩展包将根据搜索器优先级从高到低依次搜索, 当某一个优先级的搜索器搜到扩展包后将停止搜索.
* 可以根据不同优先级的搜索器, 配置扩展包的主用和备用文件.
*
* @return 返回各个搜索器返回的搜索结果.
*/
private fun findExtensionPackage( private fun findExtensionPackage(
extensionArtifact: Artifact, extensionArtifact: Artifact,
): Map<ExtensionPackageFinder, Set<FoundExtensionPackage>> { ): Map<ExtensionPackageFinder, Set<FoundExtensionPackage>> {
@ -138,31 +191,45 @@ internal class ExtensionLoader(
return result return result
} }
/**
* 检查扩展包搜索器是否设置了 [FinderRules] 注解.
* @return 如果已设置注解, 则返回 `true`.
*/
private fun checkExtensionPackageFinder(finder: ExtensionPackageFinder): Boolean = private fun checkExtensionPackageFinder(finder: ExtensionPackageFinder): Boolean =
finder::class.java.getDeclaredAnnotation(FinderRules::class.java) != null finder::class.java.getDeclaredAnnotation(FinderRules::class.java) != null
/**
* 在日志中输出有关扩展包冲突的错误信息.
*/
private fun printExtensionFileConflictError( private fun printExtensionFileConflictError(
extensionArtifact: Artifact, extensionArtifact: Artifact,
foundResult: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>> foundResult: Map<ExtensionPackageFinder, Set<FoundExtensionPackage>>
) { ) {
val errMessage = StringBuilder( log.error {
""" val errMessage = StringBuilder(
[Bot ${bot.botUsername}] 扩展包 $extensionArtifact 存在多个文件, 为防止安全问题, 已禁止加载该扩展包: """
""".trimIndent() [Bot ${bot.botUsername}] 扩展包 $extensionArtifact 存在多个文件, 为防止安全问题, 已禁止加载该扩展包:
).append('\n') """.trimIndent()
).append('\n')
foundResult.forEach { (finder, files) -> foundResult.forEach { (finder, files) ->
errMessage.append("\t- 搜索器 `").append(finder::class.simpleName).append("`") errMessage.append("\t- 搜索器 `").append(finder::class.simpleName).append("`")
.append("(Priority: ${finder.getPriority()})") .append("(Priority: ${finder.getPriority()})")
.append(" 找到了以下扩展包: \n") .append(" 找到了以下扩展包: \n")
for (file in files) { for (file in files) {
errMessage.append("\t\t* ") errMessage.append("\t\t* ")
.append(URLDecoder.decode(file.getRawUrl().toString(), StandardCharsets.UTF_8)).append('\n') .append(URLDecoder.decode(file.getRawUrl().toString(), StandardCharsets.UTF_8)).append('\n')
}
} }
errMessage
} }
log.error { errMessage }
} }
/**
* 创建扩展数据目录, 并返回 [File] 对象.
* @param extensionArtifact 扩展包构件坐标.
* @return 返回对应的数据存储目录.
*/
private fun getExtensionDataFolder(extensionArtifact: Artifact): File { private fun getExtensionDataFolder(extensionArtifact: Artifact): File {
val dataFolder = val dataFolder =
File(extensionsDataFolder, "${extensionArtifact.groupId}/${extensionArtifact.artifactId}") File(extensionsDataFolder, "${extensionArtifact.groupId}/${extensionArtifact.artifactId}")
@ -172,6 +239,12 @@ internal class ExtensionLoader(
return dataFolder return dataFolder
} }
/**
* 已加载扩展项.
* @property extensionArtifact 扩展的构件坐标([Artifact]).
* @property factoryClass 扩展的工厂类.
* @property extension 扩展实例.
*/
data class LoadedExtensionEntry( data class LoadedExtensionEntry(
val extensionArtifact: Artifact, val extensionArtifact: Artifact,
val factoryClass: Class<out BotExtensionFactory>, val factoryClass: Class<out BotExtensionFactory>,
@ -181,6 +254,10 @@ internal class ExtensionLoader(
} }
/** /**
* 扩展的类加载器清除器.
*
* 原计划是用来通过关闭 ClassLoader 来卸载扩展的, 但似乎并没有这么做.
*
* 该类为保留措施, 尚未启用. * 该类为保留措施, 尚未启用.
*/ */
internal object ExtensionClassLoaderCleaner { internal object ExtensionClassLoaderCleaner {
@ -257,7 +334,7 @@ internal interface ExtensionPackageFinder {
/** /**
* 已找到的扩展包信息. * 已找到的扩展包信息.
* 通过实现该接口, 以寻找远端文件的 [ExtensionPackageFinder] * 通过实现该接口, 以寻找远端文件的 [ExtensionPackageFinder];
* 可以在适当的时候将扩展包下载到本地, 而无需在搜索阶段下载扩展包. * 可以在适当的时候将扩展包下载到本地, 而无需在搜索阶段下载扩展包.
*/ */
internal interface FoundExtensionPackage { internal interface FoundExtensionPackage {
@ -296,6 +373,7 @@ private fun FoundExtensionPackage.createClassLoader(): ExtensionClassLoader =
* 已找到的扩展包文件. * 已找到的扩展包文件.
* @param artifact 扩展包构件坐标. * @param artifact 扩展包构件坐标.
* @param file 已找到的扩展包文件. * @param file 已找到的扩展包文件.
* @param finder 搜索到该扩展包的搜索器.
*/ */
internal class FileFoundExtensionPackage( internal class FileFoundExtensionPackage(
private val artifact: Artifact, private val artifact: Artifact,
@ -342,14 +420,14 @@ internal class ExtensionClassLoader(urls: Array<URL>, dependencyLoader: ClassLoa
// 以免使用了不来自扩展包的机器人扩展. // 以免使用了不来自扩展包的机器人扩展.
override fun getResources(name: String?): Enumeration<URL> { override fun getResources(name: String?): Enumeration<URL> {
if (BotExtensionFactory::class.java.equals(name)) { if ("META-INF/services/${BotExtensionFactory::class.java.name}" == name) {
return findResources(name) return findResources(name)
} }
return super.getResources(name) return super.getResources(name)
} }
override fun getResource(name: String?): URL? { override fun getResource(name: String?): URL? {
if (BotExtensionFactory::class.java.equals(name)) { if ("META-INF/services/${BotExtensionFactory::class.java}" == name) {
return findResource(name) return findResource(name)
} }
return super.getResource(name) return super.getResource(name)

View File

@ -26,6 +26,7 @@ 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.*
@ -257,23 +258,25 @@ internal class MavenRepositoryExtensionFinder(
} }
override fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set<FoundExtensionPackage> { override fun findByArtifact(extensionArtifact: Artifact, extensionsPath: File): Set<FoundExtensionPackage> {
val repositories = repositorySystem.newResolutionRepositories(repoSystemSession, remoteRepositories).toList()
log.debug { log.debug {
StringBuilder().apply { StringBuilder().apply {
append("构件 $extensionArtifact 将在以下仓库拉取: \n") append("构件 $extensionArtifact 将在以下仓库拉取: \n")
remoteRepositories.forEach { repositories.forEach {
append("\t- ${it}\n") append("\t- $it\n")
} }
} }
} }
val extensionArtifactResult = repositorySystem.resolveArtifact( val extensionArtifactResult = repositorySystem.resolveArtifact(
repoSystemSession, repoSystemSession,
ArtifactRequest( ArtifactRequest(
extensionArtifact, extensionArtifact,
repositorySystem.newResolutionRepositories(repoSystemSession, remoteRepositories), repositories,
null null
) )
) )
val extResolvedArtifact = extensionArtifactResult.artifact val resolvedArtifact: Artifact? = extensionArtifactResult.artifact
if (!extensionArtifactResult.isResolved) { if (!extensionArtifactResult.isResolved) {
if (extensionArtifactResult.isMissing) { if (extensionArtifactResult.isMissing) {
log.warn { "在指定的仓库中找不到构件: ${extensionArtifactResult.artifact}" } log.warn { "在指定的仓库中找不到构件: ${extensionArtifactResult.artifact}" }
@ -281,17 +284,26 @@ internal class MavenRepositoryExtensionFinder(
printArtifactResultExceptions(extensionArtifactResult.exceptions) printArtifactResultExceptions(extensionArtifactResult.exceptions)
} }
return emptySet() return emptySet()
} else if (resolvedArtifact == null) {
log.warn { "无法在指定的仓库中解析构件: $extensionArtifact" }
return emptySet()
}
log.info {
"已从 Maven 仓库 `${extensionArtifactResult.repository.id}` 中找到" +
"扩展包 `${resolvedArtifact.groupId}:${resolvedArtifact.artifactId}` " +
"版本号 `${resolvedArtifact.version}`."
} }
val request = DependencyRequest( val request = DependencyRequest(
CollectRequest(Dependency(extResolvedArtifact, null), remoteRepositories), CollectRequest(Dependency(resolvedArtifact, null), repositories),
ScopeDependencyFilter(setOf("runtime", "compile", "provided"), null) ScopeDependencyFilter(setOf("runtime", "compile", "provided"), null)
) )
val dependencyResult = repositorySystem.resolveDependencies(repoSystemSession, request) val dependencyResult = repositorySystem.resolveDependencies(repoSystemSession, request)
val dependencies = checkAndCollectDependencyArtifacts(extensionArtifact, dependencyResult.artifactResults) val dependencies = checkAndCollectDependencyArtifacts(extensionArtifact, dependencyResult.artifactResults)
?: return emptySet() ?: return emptySet()
return setOf(MavenExtensionPackage(this, extResolvedArtifact, extensionArtifactResult.repository, dependencies)) return setOf(MavenExtensionPackage(this, resolvedArtifact, extensionArtifactResult.repository, dependencies))
} }
private fun checkAndCollectDependencyArtifacts( private fun checkAndCollectDependencyArtifacts(
@ -385,7 +397,6 @@ 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/"
/** /**
@ -455,17 +466,19 @@ internal class MavenRepositoryExtensionFinder(
throw IllegalArgumentException("Unsupported FoundExtensionPackage type: $foundExtensionPackage") throw IllegalArgumentException("Unsupported FoundExtensionPackage type: $foundExtensionPackage")
} }
val urls = mutableSetOf<URL>() val urls = mutableSetOf<URI>()
for (dependency in foundExtensionPackage.dependencies) { for (dependency in foundExtensionPackage.dependencies) {
val dependencyFile = dependency.file ?: continue val dependencyFile = dependency.file ?: continue
urls.add(dependencyFile.toURI().toURL()) urls.add(dependencyFile.toURI())
} }
// 将依赖的 ClassLoader 与 ExtensionPackage 的 ClassLoader 分开 // 将依赖的 ClassLoader 与 ExtensionPackage 的 ClassLoader 分开
// 这么做可以防范依赖中隐藏的 SPI 注册, 避免安全隐患. // 这么做可以防范依赖中隐藏的 SPI 注册, 避免安全隐患.
val dependenciesUrlArray = urls.toTypedArray() val dependenciesUrlArray = urls.toTypedArray()
val dependenciesClassLoader = URLClassLoader(dependenciesUrlArray) val dependenciesClassLoader = URLClassLoader(
dependenciesUrlArray.map { it.toURL() }.toTypedArray()
)
return ExtensionClassLoader( return ExtensionClassLoader(
arrayOf(foundExtensionPackage.getPackageFile().toURI().toURL()), arrayOf(foundExtensionPackage.getPackageFile().toURI().toURL()),

View File

@ -4,44 +4,44 @@ import io.prometheus.client.Counter
import io.prometheus.client.Gauge import io.prometheus.client.Gauge
import io.prometheus.client.Summary import io.prometheus.client.Summary
import mu.KotlinLogging import mu.KotlinLogging
import net.lamgc.scalabot.config.BotConfig
import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.Artifact
import org.telegram.abilitybots.api.bot.AbilityBot import org.telegram.telegrambots.abilitybots.api.bot.AbilityBot
import org.telegram.abilitybots.api.db.DBContext import org.telegram.telegrambots.abilitybots.api.db.DBContext
import org.telegram.abilitybots.api.objects.Ability import org.telegram.telegrambots.abilitybots.api.objects.Ability
import org.telegram.abilitybots.api.toggle.BareboneToggle import org.telegram.telegrambots.abilitybots.api.toggle.BareboneToggle
import org.telegram.abilitybots.api.toggle.DefaultToggle import org.telegram.telegrambots.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,
options: DefaultBotOptions, client: TelegramClient,
extensionFinders: Set<ExtensionPackageFinder>, extensionFinders: Set<ExtensionPackageFinder>,
botConfig: BotConfig, val botConfig: BotConfig,
private val creatorId: Long = botConfig.account.creatorId,
val accountId: Long = botConfig.account.id, val accountId: Long = botConfig.account.id,
private val creatorId: Long = botConfig.account.creatorId,
val extensions: Set<Artifact> = botConfig.extensions val extensions: Set<Artifact> = botConfig.extensions
) : ) :
AbilityBot( AbilityBot(
botConfig.account.token, client,
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(
@ -49,6 +49,8 @@ internal class ScalaBot(
extensionFinders = extensionFinders extensionFinders = extensionFinders
) )
private val accountIdString = accountId.toString()
init { init {
log.info { "[Bot $botUsername] 正在加载扩展..." } log.info { "[Bot $botUsername] 正在加载扩展..." }
val extensionEntries = extensionLoader.getExtensions() val extensionEntries = extensionLoader.getExtensions()
@ -64,19 +66,19 @@ internal class ScalaBot(
override fun creatorId(): Long = creatorId override fun creatorId(): Long = creatorId
override fun onUpdateReceived(update: Update?) { override fun consume(update: Update?) {
botUpdateCounter.labels(botUsername).inc() botUpdateCounter.labels(botUsername, accountIdString).inc()
botUpdateGauge.labels(botUsername).inc() botUpdateGauge.labels(botUsername, accountIdString).inc()
val timer = updateProcessTime.labels(botUsername).startTimer() val timer = updateProcessTime.labels(botUsername, accountIdString).startTimer()
try { try {
super.onUpdateReceived(update) super.consume(update)
} catch (e: Exception) { } catch (e: Exception) {
exceptionHandlingCounter.labels(botUsername).inc() exceptionHandlingCounter.labels(botUsername, accountIdString).inc()
throw e throw e
} finally { } finally {
timer.observeDuration() timer.observeDuration()
botUpdateGauge.labels(botUsername).dec() botUpdateGauge.labels(botUsername, accountIdString).dec()
} }
} }
@ -89,11 +91,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)"
@ -109,9 +111,10 @@ internal class ScalaBot(
return true return true
} }
val setMyCommands = SetMyCommands() val setMyCommands = SetMyCommands.builder()
setMyCommands.commands = botCommands .commands(botCommands)
return execute(DeleteMyCommands()) && execute(setMyCommands) .build()
return telegramClient.execute(DeleteMyCommands()) && telegramClient.execute(setMyCommands)
} }
override fun onRegister() { override fun onRegister() {
@ -119,10 +122,6 @@ 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 { }
@ -133,7 +132,8 @@ internal class ScalaBot(
private val botUpdateCounter = Counter.build() private val botUpdateCounter = Counter.build()
.name("updates_total") .name("updates_total")
.help("Total number of updates received by all bots.") .help("Total number of updates received by all bots.")
.labelNames("bot_name") .labelNames("bot_name", "bot_id")
.namespace(Const.METRICS_NAMESPACE)
.subsystem("telegrambots") .subsystem("telegrambots")
.register() .register()
@ -141,7 +141,8 @@ internal class ScalaBot(
private val botUpdateGauge = Gauge.build() private val botUpdateGauge = Gauge.build()
.name("updates_in_progress") .name("updates_in_progress")
.help("Number of updates in process by all bots.") .help("Number of updates in process by all bots.")
.labelNames("bot_name") .labelNames("bot_name", "bot_id")
.namespace(Const.METRICS_NAMESPACE)
.subsystem("telegrambots") .subsystem("telegrambots")
.register() .register()
@ -149,6 +150,7 @@ internal class ScalaBot(
private val onlineBotGauge = Gauge.build() private val onlineBotGauge = Gauge.build()
.name("bots_online") .name("bots_online")
.help("Number of bots Online.") .help("Number of bots Online.")
.namespace(Const.METRICS_NAMESPACE)
.subsystem("telegrambots") .subsystem("telegrambots")
.register() .register()
@ -160,7 +162,8 @@ internal class ScalaBot(
"so it may be different from the actual execution time of ability. " + "so it may be different from the actual execution time of ability. " +
"It is not recommended to use it as the accurate execution time of ability)" "It is not recommended to use it as the accurate execution time of ability)"
) )
.labelNames("bot_name") .labelNames("bot_name", "bot_id")
.namespace(Const.METRICS_NAMESPACE)
.subsystem("telegrambots") .subsystem("telegrambots")
.register() .register()
@ -168,7 +171,8 @@ internal class ScalaBot(
private val exceptionHandlingCounter = Counter.build() private val exceptionHandlingCounter = Counter.build()
.name("updates_exception_handling") .name("updates_exception_handling")
.help("Number of exceptions during processing.") .help("Number of exceptions during processing.")
.labelNames("bot_name") .labelNames("bot_name", "bot_id")
.namespace(Const.METRICS_NAMESPACE)
.subsystem("telegrambots") .subsystem("telegrambots")
.register() .register()
} }

View File

@ -1,168 +0,0 @@
package net.lamgc.scalabot.util
import com.google.gson.*
import mu.KotlinLogging
import net.lamgc.scalabot.MavenRepositoryConfig
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.artifact.DefaultArtifact
import org.eclipse.aether.repository.Authentication
import org.eclipse.aether.repository.Proxy
import org.eclipse.aether.util.repository.AuthenticationBuilder
import org.telegram.telegrambots.bots.DefaultBotOptions
import java.lang.reflect.Type
import java.net.URL
internal object ProxyTypeSerializer : JsonDeserializer<DefaultBotOptions.ProxyType>,
JsonSerializer<DefaultBotOptions.ProxyType> {
override fun deserialize(
json: JsonElement,
typeOfT: Type?,
context: JsonDeserializationContext?
): DefaultBotOptions.ProxyType {
if (!json.isJsonPrimitive) {
throw JsonParseException("Wrong configuration value type.")
}
val value = json.asString.trim()
try {
return DefaultBotOptions.ProxyType.valueOf(value.uppercase())
} catch (e: IllegalArgumentException) {
throw JsonParseException("Invalid value: $value")
}
}
override fun serialize(
src: DefaultBotOptions.ProxyType,
typeOfSrc: Type?,
context: JsonSerializationContext?
): JsonElement {
return JsonPrimitive(src.toString())
}
}
internal object ArtifactSerializer : JsonSerializer<Artifact>, JsonDeserializer<Artifact> {
override fun serialize(src: Artifact, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
val gavBuilder = StringBuilder("${src.groupId}:${src.artifactId}")
if (!src.extension.equals("jar")) {
gavBuilder.append(':').append(src.extension)
}
if (src.classifier.isNotEmpty()) {
gavBuilder.append(':').append(src.classifier)
}
return JsonPrimitive(gavBuilder.append(':').append(src.version).toString())
}
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Artifact {
if (!json!!.isJsonPrimitive) {
throw JsonParseException("Wrong configuration value type.")
}
return DefaultArtifact(json.asString.trim())
}
}
internal object AuthenticationSerializer : JsonDeserializer<Authentication> {
private val log = KotlinLogging.logger { }
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Authentication? {
val builder = AuthenticationBuilder()
when (json) {
is JsonArray -> {
for (element in json) {
if (element is JsonArray) {
builder.addCustom(jsonArrayToAuthentication(element))
} else if (element is JsonObject) {
jsonToAuthentication(element, builder)
}
}
}
is JsonObject -> {
jsonToAuthentication(json, builder)
}
else -> {
throw JsonParseException("Unsupported JSON data type: ${json::class.java}")
}
}
return builder.build()
}
private fun jsonArrayToAuthentication(jsonArray: JsonArray): Authentication {
val builder = AuthenticationBuilder()
for (element in jsonArray) {
when (element) {
is JsonObject -> jsonToAuthentication(element, builder)
is JsonArray -> builder.addCustom(jsonArrayToAuthentication(element))
else -> log.warn { "不支持的 Json 类型: ${element::class.java}" }
}
}
return builder.build()
}
private const val KEY_TYPE = "type"
private fun jsonToAuthentication(json: JsonObject, builder: AuthenticationBuilder) {
if (!json.has(KEY_TYPE)) {
log.warn { "缺少 type 字段, 无法判断 Maven 认证信息类型." }
return
} else if (!json.get(KEY_TYPE).isJsonPrimitive) {
log.warn { "type 字段类型错误(应为 Primitive 类型), 无法判断 Maven 认证信息类型.(实际类型: `${json::class.java}`)" }
return
}
when (json.get(KEY_TYPE).asString.trim().lowercase()) {
"string" -> {
builder.addString(checkJsonKey(json, "key"), checkJsonKey(json, "value"))
}
"secret" -> {
builder.addSecret(checkJsonKey(json, "key"), checkJsonKey(json, "value"))
}
}
}
}
private fun checkJsonKey(json: JsonObject, key: String): String {
if (!json.has(key)) {
throw JsonParseException("Required field does not exist: $key")
} else if (!json.get(key).isJsonPrimitive) {
throw JsonParseException("Wrong field `$key` type: ${json.get(key)::class.java}")
}
return json.get(key).asString
}
internal object MavenRepositoryConfigSerializer
: JsonDeserializer<MavenRepositoryConfig> {
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): MavenRepositoryConfig {
return when (json) {
is JsonObject -> {
MavenRepositoryConfig(
id = json.get("id")?.asString,
url = URL(checkJsonKey(json, "url")),
proxy = if (json.has("proxy") && json.get("proxy").isJsonObject)
context.deserialize<Proxy>(
json.getAsJsonObject("proxy"), Proxy::class.java
) else null,
layout = json.get("layout").asString ?: "default",
enableReleases = json.get("enableReleases")?.asBoolean ?: true,
enableSnapshots = json.get("enableSnapshots")?.asBoolean ?: true,
authentication = if (json.has("authentication") && json.get("authentication").isJsonObject)
context.deserialize<Authentication>(
json.getAsJsonObject("authentication"), Authentication::class.java
) else null
)
}
is JsonPrimitive -> {
MavenRepositoryConfig(url = URL(json.asString))
}
else -> {
throw JsonParseException("Unsupported Maven warehouse configuration type.")
}
}
}
}

View File

@ -0,0 +1,29 @@
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,16 +10,14 @@ import java.io.FilenameFilter
internal fun ByteArray.toHexString(): String = joinToString("") { it.toString(16) } internal fun ByteArray.toHexString(): String = joinToString("") { it.toString(16) }
internal fun Artifact.equalsArtifact(that: Artifact): Boolean = internal fun Artifact.equalsArtifact(that: Artifact, checkProperties: Boolean = false): Boolean =
this.groupId.equals(that.groupId) && this.groupId.equals(that.groupId) &&
this.artifactId.equals(that.artifactId) && this.artifactId.equals(that.artifactId) &&
this.version.equals(that.version) && this.version.equals(that.version) &&
this.baseVersion.equals(that.baseVersion) &&
this.isSnapshot == that.isSnapshot &&
this.classifier.equals(that.classifier) && this.classifier.equals(that.classifier) &&
this.extension.equals(that.extension) && this.extension.equals(that.extension) &&
(if (this.file == null) that.file == null else this.file.equals(that.file)) && (if (this.file == null) that.file == null else this.file.equals(that.file)) &&
this.properties.equals(that.properties) (!checkProperties || this.properties.equals(that.properties))
internal fun File.deepListFiles( internal fun File.deepListFiles(
addSelf: Boolean = false, addSelf: Boolean = false,
@ -27,13 +25,13 @@ internal fun File.deepListFiles(
fileFilter: FileFilter? = null, fileFilter: FileFilter? = null,
filenameFilter: FilenameFilter? = null filenameFilter: FilenameFilter? = null
): Array<File>? { ): Array<File>? {
val files = if (fileFilter != null) { val files = (if (fileFilter != null) {
this.listFiles(fileFilter) this.listFiles(fileFilter)
} else if (filenameFilter != null) { } else if (filenameFilter != null) {
this.listFiles(filenameFilter) this.listFiles(filenameFilter)
} else { } else {
this.listFiles() this.listFiles()
} ?: return null }) ?: return null
val result = if (addSelf) mutableSetOf(this) else mutableSetOf() val result = if (addSelf) mutableSetOf(this) else mutableSetOf()
for (file in files) { for (file in files) {
@ -73,11 +71,10 @@ fun <T : AutoCloseable> T.registerShutdownHook(): T {
return this return this
} }
private val log = KotlinLogging.logger { }
private object UtilsInternal { private object UtilsInternal {
val autoCloseableSet = mutableSetOf<AutoCloseable>() val autoCloseableSet = mutableSetOf<AutoCloseable>()
private val log = KotlinLogging.logger(UtilsInternal::class.java.name)
init { init {
Runtime.getRuntime().addShutdownHook(Thread(this::doCloseResources, "Shutdown-AutoCloseable")) Runtime.getRuntime().addShutdownHook(Thread(this::doCloseResources, "Shutdown-AutoCloseable"))

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<included> <included>
<define name="DATA_LOGS" class="net.lamgc.scalabot.LogDirectorySupplier"/> <define name="DATA_LOGS" class="net.lamgc.scalabot.LogDirectorySupplier"/>
<define name="LOG_LEVEL" class="net.lamgc.scalabot.LogLevelSupplier"/>
<define name="NETWORK_LOG_LEVEL" class="net.lamgc.scalabot.NetworkVerboseLogSupplier"/>
<appender name="STD_OUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STD_OUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
@ -24,7 +26,7 @@
<appender name="FILE_OUT" class="ch.qos.logback.core.rolling.RollingFileAppender"> <appender name="FILE_OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${DATA_LOGS}/latest.log</file> <file>${DATA_LOGS}/latest.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>data/logs/%d{yyyy-MM-dd}.log.gz</fileNamePattern> <fileNamePattern>${DATA_LOGS}/%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory> <maxHistory>30</maxHistory>
</rollingPolicy> </rollingPolicy>
<encoder> <encoder>

View File

@ -1,16 +0,0 @@
<configuration scan="false" debug="false">
<include resource="base-logback.xml"/>
<logger name="org.apache.http" level="INFO"/>
<logger name="org.eclipse.aether.internal.impl.DefaultTransporterProvider" level="INFO"/>
<logger name="org.eclipse.aether.internal.impl.DefaultRepositoryConnectorProvider" level="INFO"/>
<logger name="org.eclipse.aether.internal.impl.EnhancedLocalRepositoryManager" level="INFO"/>
<logger name="org.telegram.telegrambots.facilities.proxysocketfactorys" level="INFO"/>
<logger name="org.eclipse.aether.internal.impl.DefaultUpdateCheckManager" level="INFO"/>
<root level="DEBUG">
<appender-ref ref="FILE_OUT"/>
<appender-ref ref="STD_ERR"/>
<appender-ref ref="STD_OUT"/>
</root>
</configuration>

View File

@ -1,7 +1,14 @@
<configuration scan="false" debug="false"> <configuration scan="false" debug="false">
<include resource="base-logback.xml"/> <include resource="base-logback.xml"/>
<root level="INFO"> <logger name="org.apache.http" level="${NETWORK_LOG_LEVEL}"/>
<logger name="org.eclipse.aether.internal.impl.DefaultTransporterProvider" level="${NETWORK_LOG_LEVEL}"/>
<logger name="org.eclipse.aether.internal.impl.DefaultRepositoryConnectorProvider" level="${NETWORK_LOG_LEVEL}"/>
<logger name="org.eclipse.aether.internal.impl.EnhancedLocalRepositoryManager" level="${NETWORK_LOG_LEVEL}"/>
<logger name="org.telegram.telegrambots.facilities.proxysocketfactorys" level="${NETWORK_LOG_LEVEL}"/>
<logger name="org.eclipse.aether.internal.impl.DefaultUpdateCheckManager" level="${NETWORK_LOG_LEVEL}"/>
<root level="${LOG_LEVEL}">
<appender-ref ref="FILE_OUT"/> <appender-ref ref="FILE_OUT"/>
<appender-ref ref="STD_ERR"/> <appender-ref ref="STD_ERR"/>
<appender-ref ref="STD_OUT"/> <appender-ref ref="STD_OUT"/>

View File

@ -1,62 +1,60 @@
package net.lamgc.scalabot package net.lamgc.scalabot
import com.google.gson.Gson import com.github.stefanbirkner.systemlambda.SystemLambda
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import mu.KotlinLogging
import net.lamgc.scalabot.config.MavenRepositoryConfig
import net.lamgc.scalabot.config.ProxyConfig
import net.lamgc.scalabot.config.ProxyType
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.api.io.TempDir
import java.io.File import java.io.File
import java.util.* import java.io.IOException
import kotlin.math.abs import java.net.Proxy
import kotlin.test.Test import java.net.URL
import kotlin.test.assertEquals import java.nio.file.Files
import kotlin.test.assertNotNull import java.nio.file.Path
import kotlin.test.assertTrue import kotlin.io.path.deleteExisting
import kotlin.test.*
internal class BotAccountTest {
@Test
fun deserializerTest() {
val accountId = abs(Random().nextInt()).toLong()
val creatorId = abs(Random().nextInt()).toLong()
val botAccount = Gson().fromJson(
"""
{
"name": "TestBot",
"token": "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10",
"creatorId": $creatorId
}
""".trimIndent(), BotAccount::class.java
)
assertEquals("TestBot", botAccount.name)
assertEquals("${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", botAccount.token)
assertEquals(accountId, botAccount.id, "Botaccount ID does not match expectations.")
assertEquals(creatorId, botAccount.creatorId)
}
}
internal class AppPathsTest { internal class AppPathsTest {
@Test @Test
fun `Data root path priority`() { fun `Consistency check`() {
System.setProperty("bot.path.data", "A") for (path in AppPaths.entries) {
assertEquals("A", AppPaths.DATA_ROOT.file.path, "`DATA_ROOT`没有优先返回 Property 的值.")
System.getProperties().remove("bot.path.data")
if (System.getenv("BOT_DATA_PATH") != null) {
assertEquals( assertEquals(
System.getenv("BOT_DATA_PATH"), AppPaths.DATA_ROOT.file.path, File(path.path).canonicalPath,
"`DATA_ROOT`没有返回 env 的值." path.file.canonicalPath,
"路径 File 与 Path 不一致: ${path.name}"
) )
} else { }
}
@Test
fun `Data root path priority`() {
System.setProperty(AppPaths.PathConst.PROP_DATA_PATH, "fromSystemProperties")
assertEquals("fromSystemProperties", AppPaths.DATA_ROOT.file.path, "`DATA_ROOT`没有优先返回 Property 的值.")
System.getProperties().remove(AppPaths.PathConst.PROP_DATA_PATH)
val expectEnvValue = "fromEnvironmentVariable"
SystemLambda.withEnvironmentVariable(AppPaths.PathConst.ENV_DATA_PATH, expectEnvValue).execute {
assertEquals(
expectEnvValue, AppPaths.DATA_ROOT.file.path,
"`DATA_ROOT`没有优先返回 env 的值."
)
}
SystemLambda.withEnvironmentVariable(AppPaths.PathConst.ENV_DATA_PATH, null).execute {
assertEquals( assertEquals(
System.getProperty("user.dir"), AppPaths.DATA_ROOT.file.path, System.getProperty("user.dir"), AppPaths.DATA_ROOT.file.path,
"`DATA_ROOT`没有返回 `user.dir` 的值." "`DATA_ROOT`没有返回 System.properties `user.dir` 的值."
) )
val userDir = System.getProperty("user.dir") val userDir = System.getProperty("user.dir")
System.getProperties().remove("user.dir") System.getProperties().remove("user.dir")
assertEquals(".", AppPaths.DATA_ROOT.file.path, "`DATA_ROOT`没有返回 `.`(当前目录).") assertEquals(".", AppPaths.DATA_ROOT.file.path, "`DATA_ROOT`没有返回替补值 `.`(当前目录).")
System.setProperty("user.dir", userDir) System.setProperty("user.dir", userDir)
assertNotNull(System.getProperty("user.dir"), "环境还原失败!") assertNotNull(System.getProperty("user.dir"), "环境还原失败!")
} }
@ -115,8 +113,304 @@ internal class AppPathsTest {
verify(exactly = 0) { mkdirs() } verify(exactly = 0) { mkdirs() }
} }
mockk<File> {
every { exists() }.returns(false)
every { canonicalPath }.answers { alreadyExistsFile.canonicalPath }
every { createNewFile() }.answers { false }
every { mkdirs() }.answers { false }
every { mkdir() }.answers { false }
}.apply {
mockk<AppPaths> {
every { file }.returns(this@apply)
every { path }.returns(this@apply.canonicalPath)
every { initial() }.answers {
defaultInitializerMethod.invoke(null, this@mockk)
}
}.initial()
verify(exactly = 1) { createNewFile() }
verify(exactly = 0) { mkdir() }
verify(exactly = 0) { mkdirs() }
}
defaultInitializerMethod.isAccessible = false defaultInitializerMethod.isAccessible = false
} }
} @Test
fun `loadBotConfig test`(@TempDir testDir: File) {
assertNull(loadBotConfigJson(File("/NOT_EXISTS_FILE")), "加载 BotConfigs 失败时应该返回 null.")
SystemLambda.withEnvironmentVariable(AppPaths.PathConst.ENV_DATA_PATH, testDir.canonicalPath).execute {
assertNull(loadBotConfigJson(), "加载 BotConfigs 失败时应该返回 null.")
File(testDir, "bot.json").apply {
//language=JSON5
writeText(
"""
[
{
"enabled": false,
"account": {
"name": "TestBot",
"token": "123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10",
"creatorId": 123456789
},
"proxy": {
"host": "localhost",
"port": 8080,
"type": "HTTP"
},
"disableBuiltInAbility": false,
"autoUpdateCommandList": true,
"extensions": [
"org.example.test:test-extension:1.0.0"
],
"baseApiUrl": "http://localhost:8080"
}
]
""".trimIndent()
)
}
val botConfigJsons = loadBotConfigJson()
assertNotNull(botConfigJsons)
assertEquals(1, botConfigJsons.size())
}
}
@Test
fun `loadAppConfig test`(@TempDir testDir: File) {
assertThrows<IOException>("加载失败时应该抛出 IOException.") {
loadAppConfig(File("/NOT_EXISTS_FILE"))
}
SystemLambda.withEnvironmentVariable(AppPaths.PathConst.ENV_DATA_PATH, testDir.canonicalPath).execute {
assertThrows<IOException>("加载失败时应该抛出 IOException.") {
loadAppConfig()
}
File(testDir, "config.json").apply {
//language=JSON5
writeText(
"""
{
"proxy": {
"type": "HTTP",
"host": "localhost",
"port": 8080
},
"metrics": {
"enable": true,
"port": 8800,
"bindAddress": "127.0.0.1",
"authenticator": {
"username": "username",
"password": "password"
}
},
"mavenRepositories": [
{
"url": "https://repository.maven.apache.org/maven2/"
}
],
"mavenLocalRepository": "file:///tmp/maven-local-repository"
}
""".trimIndent()
)
}
val appConfigs = loadAppConfig()
assertNotNull(appConfigs)
}
}
@Test
fun `ProxyType_toTelegramBotsType test`() {
val expectTypeMapping = mapOf(
ProxyType.NO_PROXY to null,
ProxyType.SOCKS5 to Proxy.Type.SOCKS,
ProxyType.SOCKS4 to Proxy.Type.SOCKS,
ProxyType.HTTP to Proxy.Type.HTTP,
ProxyType.HTTPS to Proxy.Type.HTTP
)
for (proxyType in ProxyType.entries) {
assertEquals(
expectTypeMapping[proxyType],
proxyType.toJavaProxyType(),
"ProxyType 转换失败."
)
}
}
@Test
fun `ProxyConfig_toAetherProxy test`() {
val host = "proxy.example.org"
val port = 1080
val expectNotNullProxyType = setOf(
ProxyType.HTTP,
ProxyType.HTTPS
)
for (proxyType in ProxyType.entries) {
val proxyConfig = ProxyConfig(proxyType, host, port)
val aetherProxy = proxyConfig.toAetherProxy()
if (expectNotNullProxyType.contains(proxyType)) {
assertNotNull(aetherProxy, "支持的代理类型应该不为 null.")
assertEquals(host, aetherProxy.host)
assertEquals(port, aetherProxy.port)
} else {
assertNull(aetherProxy, "不支持的代理类型应该返回 null.")
}
}
}
@Test
fun `MavenRepositoryConfig_toRemoteRepository test`() {
val defaultMavenRepositoryConfig = MavenRepositoryConfig(
url = URL(MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL),
enableReleases = true,
enableSnapshots = false
)
val remoteRepositoryWithoutId = defaultMavenRepositoryConfig.toRemoteRepository(
ProxyConfig(ProxyType.NO_PROXY, "", 0)
)
assertEquals(MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL, remoteRepositoryWithoutId.url.toString())
assertNotNull(remoteRepositoryWithoutId.id)
assertTrue(remoteRepositoryWithoutId.getPolicy(false).isEnabled)
assertFalse(remoteRepositoryWithoutId.getPolicy(true).isEnabled)
val remoteRepositoryWithId = defaultMavenRepositoryConfig.copy(id = "test-repo").toRemoteRepository(
ProxyConfig(ProxyType.HTTP, "127.0.0.1", 1080)
)
assertEquals("test-repo", remoteRepositoryWithId.id)
assertEquals(MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL, remoteRepositoryWithId.url.toString())
assertEquals("http", remoteRepositoryWithId.proxy.type)
assertEquals("127.0.0.1", remoteRepositoryWithId.proxy.host)
assertEquals(1080, remoteRepositoryWithId.proxy.port)
assertEquals(remoteRepositoryWithId.id, remoteRepositoryWithId.id)
val remoteRepositoryWithProxy = defaultMavenRepositoryConfig.copy(
id = "test-repo",
proxy = ProxyConfig(ProxyType.HTTP, "example.org", 1080).toAetherProxy()
).toRemoteRepository(ProxyConfig(ProxyType.HTTP, "localhost", 8080))
assertEquals("http", remoteRepositoryWithProxy.proxy.type)
assertEquals("example.org", remoteRepositoryWithProxy.proxy.host, "未优先使用 MavenRepositoryConfig 中的 proxy 属性.")
assertEquals(1080, remoteRepositoryWithProxy.proxy.port, "未优先使用 MavenRepositoryConfig 中的 proxy 属性.")
}
@Test
fun `checkRepositoryLayout test`() {
val noProxyConfig = ProxyConfig(ProxyType.NO_PROXY, "", 0)
assertEquals(
"default", MavenRepositoryConfig(url = URL("https://repo.example.org"))
.toRemoteRepository(noProxyConfig).contentType
)
assertEquals(
"legacy", MavenRepositoryConfig(url = URL("https://repo.example.org"), layout = "LEgaCY")
.toRemoteRepository(noProxyConfig).contentType
)
assertThrows<IllegalArgumentException> {
MavenRepositoryConfig(
url = URL("https://repo.example.org"),
layout = "NOT_EXISTS_LAYOUT"
).toRemoteRepository(noProxyConfig)
}
}
@Test
fun `initialFiles test`(@TempDir testDir: Path) {
// 这么做是为了让日志文件创建在其他地方, 由于日志文件在运行时会持续占用, 在 windows 中文件会被锁定,
// 导致测试框架无法正常清除测试所使用的临时文件夹.
val logsDir = Files.createTempDirectory("ammmmmm-logs-")
System.setProperty(AppPaths.PathConst.PROP_DATA_PATH, logsDir.toString())
assertEquals(logsDir.toString(), AppPaths.DATA_ROOT.path, "日志目录设定失败.")
KotlinLogging.logger("TEST").error { "日志占用.(无需理会), 日志目录: $logsDir" }
AppPaths.DATA_LOGS.file.listFiles { _, name -> name.endsWith(".log") }?.forEach {
it.deleteOnExit()
}
val fullInitializeDir = Files.createTempDirectory(testDir, "fullInitialize")
fullInitializeDir.deleteExisting()
System.setProperty(AppPaths.PathConst.PROP_DATA_PATH, fullInitializeDir.toString())
assertEquals(fullInitializeDir.toString(), AppPaths.DATA_ROOT.path, "测试路径设定失败.")
assertTrue(initialFiles(), "方法未能提醒用户编辑初始配置文件.")
for (path in AppPaths.entries) {
assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}")
if (path.file.isFile) {
assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}")
}
path.reset()
}
assertFalse(initialFiles(), "方法试图在配置已初始化的情况下提醒用户编辑初始配置文件.")
for (path in AppPaths.entries) {
assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}")
if (path.file.isFile) {
assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}")
}
path.reset()
}
assertTrue(AppPaths.CONFIG_APPLICATION.file.delete(), "config.json 删除失败.")
assertFalse(initialFiles(), "方法试图在部分配置已初始化的情况下提醒用户编辑初始配置文件.")
for (path in AppPaths.entries) {
assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}")
if (path.file.isFile) {
assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}")
}
path.reset()
}
assertTrue(AppPaths.CONFIG_BOT.file.delete(), "bot.json 删除失败.")
assertFalse(initialFiles(), "方法试图在部分配置已初始化的情况下提醒用户编辑初始配置文件.")
for (path in AppPaths.entries) {
assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}")
if (path.file.isFile) {
assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}")
}
path.reset()
}
assertTrue(AppPaths.CONFIG_APPLICATION.file.delete(), "config.json 删除失败.")
assertTrue(AppPaths.CONFIG_BOT.file.delete(), "bot.json 删除失败.")
assertTrue(
initialFiles(),
"在主要配置文件(config.json 和 bot.json)不存在的情况下初始化文件后, 方法未能提醒用户编辑初始配置文件."
)
for (path in AppPaths.entries) {
assertTrue(path.file.exists(), "文件未初始化成功: ${path.path}")
if (path.file.isFile) {
assertNotEquals(0, path.file.length(), "文件未初始化成功(大小为 0): ${path.path}")
}
}
AppPaths.CONFIG_APPLICATION.file.writeText("Test-APPLICATION")
AppPaths.CONFIG_BOT.file.writeText("Test-BOT")
assertFalse(initialFiles(), "方法试图在部分配置已初始化的情况下提醒用户编辑初始配置文件.")
assertEquals(
"Test-APPLICATION", AppPaths.CONFIG_APPLICATION.file.readText(),
"config.json 被覆盖. initialized 并未阻止重复初始化."
)
assertEquals(
"Test-BOT", AppPaths.CONFIG_BOT.file.readText(),
"bot.json 被覆盖. initialized 并未阻止重复初始化."
)
System.getProperties().remove(AppPaths.PathConst.PROP_DATA_PATH)
}
private fun AppPaths.reset() {
val method = AppPaths::class.java.getDeclaredMethod("reset")
method.isAccessible = true
method.invoke(this)
method.isAccessible = false
}
}

View File

@ -1,35 +0,0 @@
@file:Suppress("PackageDirectoryMismatch")
package net.lamgc.scalabot.util
import com.google.gson.JsonObject
import com.google.gson.JsonParseException
import com.google.gson.JsonPrimitive
import org.eclipse.aether.artifact.DefaultArtifact
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
internal class ArtifactSerializerTest {
@Test
fun badJsonType() {
assertFailsWith<JsonParseException> { ArtifactSerializer.deserialize(JsonObject(), null, null) }
}
@Test
fun serialize() {
val gav = "org.example.software:test:1.0.0-SNAPSHOT"
val expectArtifact = DefaultArtifact(gav)
val actualArtifact = DefaultArtifact(ArtifactSerializer.serialize(expectArtifact, null, null).asString)
assertEquals(expectArtifact, actualArtifact)
}
@Test
fun deserialize() {
val gav = "org.example.software:test:1.0.0-SNAPSHOT"
val expectArtifact = DefaultArtifact(gav)
val actualArtifact = ArtifactSerializer.deserialize(JsonPrimitive(gav), null, null)
assertEquals(expectArtifact, actualArtifact)
}
}

View File

@ -0,0 +1,40 @@
package net.lamgc.scalabot.util
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.spi.LoggingEvent
import ch.qos.logback.core.spi.FilterReply
import io.mockk.every
import io.mockk.mockk
import kotlin.test.Test
import kotlin.test.assertEquals
class StdOutFilterTest {
@Test
fun filterTest() {
val filter = StdOutFilter()
for (level in listOf(
Level.ALL,
Level.TRACE,
Level.DEBUG,
Level.INFO
)) {
val loggingEvent = mockk<LoggingEvent> {
every { this@mockk.level }.returns(level)
}
assertEquals(FilterReply.ACCEPT, filter.decide(loggingEvent))
}
for (level in listOf(
Level.WARN,
Level.ERROR
)) {
val loggingEvent = mockk<LoggingEvent> {
every { this@mockk.level }.returns(level)
}
assertEquals(FilterReply.DENY, filter.decide(loggingEvent))
}
}
}

View File

@ -0,0 +1,27 @@
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,9 +1,6 @@
package net.lamgc.scalabot.util package net.lamgc.scalabot.util
import io.mockk.every import io.mockk.*
import io.mockk.justRun
import io.mockk.mockk
import io.mockk.verify
import net.lamgc.scalabot.ExtensionPackageFinder import net.lamgc.scalabot.ExtensionPackageFinder
import net.lamgc.scalabot.FinderPriority import net.lamgc.scalabot.FinderPriority
import net.lamgc.scalabot.FinderRules import net.lamgc.scalabot.FinderRules
@ -13,6 +10,8 @@ import org.eclipse.aether.artifact.DefaultArtifact
import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import java.io.File import java.io.File
import java.io.FileFilter
import java.io.FilenameFilter
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import kotlin.test.* import kotlin.test.*
@ -116,4 +115,203 @@ internal class UtilsKtTest {
resourceSet.clear() resourceSet.clear()
} }
@Test
fun `Artifact equals`() {
val artifact = DefaultArtifact("org.example:artifact:jar:0.0.1")
assertFalse(artifact.isSnapshot, "Release artifact is snapshot.")
assertTrue(artifact.equalsArtifact(artifact))
assertTrue(artifact.setFile(File(".")).equalsArtifact(artifact.setFile(File("."))))
val snapshotArtifact = DefaultArtifact("org.example:artifact:jar:0.0.1-SNAPSHOT")
val snapshotTimestampArtifact = DefaultArtifact("org.example:artifact:jar:0.0.1-20220605.130047-1")
assertTrue(snapshotArtifact.isSnapshot, "SnapshotArtifact not snapshot.")
assertNotEquals(artifact.isSnapshot, snapshotArtifact.isSnapshot)
assertNotEquals(artifact.baseVersion, snapshotArtifact.baseVersion)
assertFalse(artifact.equalsArtifact(snapshotArtifact))
assertFalse(snapshotArtifact.equalsArtifact(snapshotTimestampArtifact))
assertFalse(artifact.equalsArtifact(DefaultArtifact("org.example:artifact:0.0.2")))
assertFalse(artifact.equalsArtifact(DefaultArtifact("org.example.test:artifact:0.0.1")))
assertFalse(artifact.equalsArtifact(DefaultArtifact("org.example:artifact-a:0.0.1")))
assertFalse(artifact.equalsArtifact(DefaultArtifact("org.example:artifact:war:0.0.1")))
assertFalse(artifact.equalsArtifact(DefaultArtifact("org.example:artifact:war:javadoc:0.0.1")))
assertFalse(artifact.equalsArtifact(DefaultArtifact("org.example:artifact:rar:source:0.0.1")))
assertFalse(
artifact.equalsArtifact(
DefaultArtifact("org.example:artifact:jar:0.0.1")
.setFile(File("./xxx01.jar"))
)
)
val artifactWithExtension = DefaultArtifact("org.example:artifact:jar:0.0.1")
assertFalse(artifactWithExtension.equalsArtifact(DefaultArtifact("org.example:artifact:war:0.0.1")))
assertTrue(artifact.equalsArtifact(artifact.setProperties(mapOf(Pair("a", "b"))), checkProperties = false))
assertFalse(artifact.equalsArtifact(artifact.setProperties(mapOf(Pair("a", "b"))), checkProperties = true))
assertTrue(
artifact.setProperties(mapOf(Pair("a", "b")))
.equalsArtifact(artifact.setProperties(mapOf(Pair("a", "b"))), checkProperties = true)
)
}
@Test
fun `deepListFile Test - Basics`() {
assertNull(mockk<File> {
every { listFiles() } returns null
}.deepListFiles())
assertNull(mockk<File> {
every { listFiles(ofType(FileFilter::class)) } returns null
}.deepListFiles(fileFilter = { true }))
assertNull(mockk<File> {
every { listFiles(ofType(FilenameFilter::class)) } returns null
}.deepListFiles(filenameFilter = { _, _ -> true }))
val listFileMock = mockk<File> {
every { listFiles() } returns arrayOf()
every { listFiles(ofType(FileFilter::class)) } returns arrayOf()
every { listFiles(ofType(FilenameFilter::class)) } returns arrayOf()
}
assertNotNull(listFileMock.deepListFiles())
verify(exactly = 1) { listFileMock.listFiles() }
verify(exactly = 0) { listFileMock.listFiles(ofType(FilenameFilter::class)) }
verify(exactly = 0) { listFileMock.listFiles(ofType(FileFilter::class)) }
clearMocks(listFileMock, answers = false)
assertNotNull(listFileMock.deepListFiles(filenameFilter = { _, _ -> true }))
verify(exactly = 0) { listFileMock.listFiles() }
verify(exactly = 1) { listFileMock.listFiles(ofType(FilenameFilter::class)) }
verify(exactly = 0) { listFileMock.listFiles(ofType(FileFilter::class)) }
clearMocks(listFileMock, answers = false)
assertNotNull(listFileMock.deepListFiles(fileFilter = { true }))
verify(exactly = 0) { listFileMock.listFiles() }
verify(exactly = 0) { listFileMock.listFiles(ofType(FilenameFilter::class)) }
verify(exactly = 1) { listFileMock.listFiles(ofType(FileFilter::class)) }
clearMocks(listFileMock, answers = false)
assertNotNull(listFileMock.deepListFiles(fileFilter = { true }, filenameFilter = { _, _ -> true }))
verify(exactly = 0) { listFileMock.listFiles() }
verify(exactly = 1) { listFileMock.listFiles(ofType(FileFilter::class)) }
verify(exactly = 0) { listFileMock.listFiles(ofType(FilenameFilter::class)) }
clearMocks(listFileMock, answers = false)
val addSelfResult = listFileMock.deepListFiles(addSelf = true)
assertNotNull(addSelfResult)
assertEquals(1, addSelfResult.size)
assertTrue(addSelfResult.contains(listFileMock))
verify(exactly = 1) { listFileMock.listFiles() }
verify(exactly = 0) { listFileMock.listFiles(ofType(FilenameFilter::class)) }
verify(exactly = 0) { listFileMock.listFiles(ofType(FileFilter::class)) }
val addSelfWithoutDirMock = createDirectory(
"root", arrayOf(
createDirectory(
"dir01", arrayOf(
createFile("test01")
)
),
createDirectory(
"dir02", arrayOf(
createFile("test02")
)
),
createDirectory(
"dir03", arrayOf(
createFile("test03")
)
)
)
)
val addSelfWithoutDirResult = addSelfWithoutDirMock.deepListFiles(addSelf = true, onlyFile = true)
assertNotNull(addSelfWithoutDirResult)
assertFalse(addSelfWithoutDirResult.isEmpty())
assertEquals(1, addSelfWithoutDirResult.filter { it.isDirectory }.size)
assertEquals(addSelfWithoutDirMock, addSelfWithoutDirResult.find { it.isDirectory })
}
@Test
fun `deepListFile Test - Complex`() {
val mock = createDirectory(
"root", arrayOf(
createFile("test"),
createFile("test02"),
createDirectory("dir01"),
createDirectory("dir02")
)
)
val withDirResult = mock.deepListFiles(onlyFile = false)
assertNotNull(withDirResult)
assertEquals(4, withDirResult.size)
assertEquals(2, withDirResult.filter { it.isFile }.size)
assertEquals(2, withDirResult.filter { it.isDirectory }.size)
val withoutDirResult = mock.deepListFiles(onlyFile = true)
assertNotNull(withoutDirResult)
assertEquals(2, withoutDirResult.filter { it.isFile }.size)
assertNull(withoutDirResult.find { it.isDirectory })
val subDirFailedMock = createDirectory(
"root", arrayOf(
mockk(name = "dir::cannotReadableDirectory") {
every { isFile } returns false
every { isDirectory } returns true
every { name } returns "cannotReadableDirectory"
every { listFiles() } returns null
every { listFiles(ofType(FileFilter::class)) } returns null
every { listFiles(ofType(FilenameFilter::class)) } returns null
},
createDirectory(
"dir2", arrayOf(
createFile("test")
)
)
)
)
val subDirFailedWithDirResult = subDirFailedMock.deepListFiles(onlyFile = false)
assertNotNull(subDirFailedWithDirResult)
assertEquals(3, subDirFailedWithDirResult.size)
assertNotNull(subDirFailedWithDirResult.find { it.isDirectory && it.name == "cannotReadableDirectory" })
assertNotNull(subDirFailedWithDirResult.find { it.isDirectory && it.name == "dir2" })
assertNotNull(subDirFailedWithDirResult.find { it.isFile && it.name == "test" })
val subDirFailedWithoutDirResult = subDirFailedMock.deepListFiles(onlyFile = true)
assertNotNull(subDirFailedWithoutDirResult)
assertEquals(1, subDirFailedWithoutDirResult.size)
assertEquals(0, subDirFailedWithoutDirResult.filter { it.isDirectory }.size)
assertNotNull(subDirFailedWithoutDirResult.find { it.isFile && it.name == "test" })
assertNull(subDirFailedWithoutDirResult.find { it.isDirectory && it.name == "cannotReadableDirectory" })
assertNull(subDirFailedWithoutDirResult.find { it.isDirectory && it.name == "dir2" })
}
private fun createFile(path: String): File {
val file = File(path)
return mockk(name = "file::$path") {
every { isFile } returns true
every { isDirectory } returns false
every { name } returns file.name
every { listFiles() } returns null
every { listFiles(ofType(FileFilter::class)) } returns null
every { listFiles(ofType(FilenameFilter::class)) } returns null
}
}
private fun createDirectory(path: String, subFiles: Array<File> = arrayOf()): File {
val file = File(path)
return mockk(name = "dir::$path") {
every { isFile } returns false
every { isDirectory } returns true
every { name } returns file.name
every { listFiles() } returns subFiles
every { listFiles(ofType(FileFilter::class)) } answers {
subFiles.filter { (firstArg() as FileFilter).accept(it) }.toTypedArray()
}
every { listFiles(ofType(FilenameFilter::class)) } answers {
subFiles.filter { (firstArg() as FilenameFilter).accept(file.parentFile, file.name) }.toTypedArray()
}
}
}
} }

View File

@ -2,17 +2,19 @@ plugins {
java java
} }
repositories {
mavenCentral()
}
dependencies { dependencies {
compileOnly(project(":scalabot-extension")) compileOnly(project(":scalabot-extension"))
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
} }
tasks.getByName<Test>("test") { tasks.getByName<Test>("test") {
useJUnitPlatform() useJUnitPlatform()
} }
tasks.withType<Javadoc> {
options {
encoding = "UTF-8"
}
}

View File

@ -1,8 +1,8 @@
package net.lamgc.scalabot.simple; package net.lamgc.scalabot.simple;
import org.telegram.abilitybots.api.bot.BaseAbilityBot; import org.telegram.telegrambots.abilitybots.api.bot.BaseAbilityBot;
import org.telegram.abilitybots.api.objects.*; import org.telegram.telegrambots.abilitybots.api.objects.*;
import org.telegram.abilitybots.api.util.AbilityExtension; import org.telegram.telegrambots.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().silent().send(msg, ctx.chatId()); ctx.bot().getSilent().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.db()) ReplyFlow botHello = ReplyFlow.builder(bot.getDb())
.enableStats("say_hello") .enableStats("say_hello")
.action((bot, upd) -> bot.silent().send("What is u name?", upd.getMessage().getChatId())) .action((bot, upd) -> bot.getSilent().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.silent() .next(Reply.of((bot, upd) -> bot.getSilent()
.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().silent().send("Hello!", ctx.chatId())) .action(ctx -> ctx.bot().getSilent().send("Hello!", ctx.chatId()))
.reply(botHello) .reply(botHello)
.build(); .build();
} }

View File

@ -1,15 +1,16 @@
package net.lamgc.scalabot.simple; package net.lamgc.scalabot.simple;
import net.lamgc.scalabot.extension.BotExtensionCreateOptions;
import net.lamgc.scalabot.extension.BotExtensionFactory; import net.lamgc.scalabot.extension.BotExtensionFactory;
import org.telegram.abilitybots.api.bot.BaseAbilityBot; import org.telegram.telegrambots.abilitybots.api.bot.BaseAbilityBot;
import org.telegram.abilitybots.api.util.AbilityExtension; import org.telegram.telegrambots.abilitybots.api.util.AbilityExtension;
import java.io.File; import java.io.File;
public class SimpleExtensionFactory implements BotExtensionFactory { public class SimpleExtensionFactory implements BotExtensionFactory {
@Override @Override
public AbilityExtension createExtensionInstance(BaseAbilityBot bot, File shareDataFolder) { public AbilityExtension createExtensionInstance(BaseAbilityBot bot, File shareDataFolder, BotExtensionCreateOptions options) {
return new SayHelloExtension(bot); return new SayHelloExtension(bot);
} }

View File

@ -1,18 +1,18 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") version "1.6.10" `java-library`
java jacoco
`maven-publish` `maven-publish`
signing signing
} }
dependencies { dependencies {
api("org.telegram:telegrambots-abilities:6.0.1") implementation("commons-codec:commons-codec:1.16.1")
api("org.slf4j:slf4j-api:1.7.36") api("org.telegram:telegrambots-abilities:8.0.0")
api(project(":scalabot-meta"))
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testImplementation("org.mockito:mockito-core:4.4.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,33 +25,30 @@ tasks.withType<Javadoc> {
java { java {
withJavadocJar() withJavadocJar()
withSourcesJar() withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_17
} }
tasks.test { tasks.test {
useJUnitPlatform() useJUnitPlatform()
finalizedBy(tasks.jacocoTestReport)
} }
tasks.withType<KotlinCompile> { tasks.jacocoTestReport {
kotlinOptions.jvmTarget = "11" dependsOn(tasks.test)
}
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
} }
publishing { publishing {
repositories { repositories {
if (project.version.toString().endsWith("-SNAPSHOT")) { maven("https://git.lamgc.me/api/packages/LamGC/maven") {
maven("https://repo.lamgc.moe/repository/maven-snapshots/") { credentials {
credentials { username = project.properties["repo.credentials.self-git.username"].toString()
username = project.properties["repo.credentials.private.username"].toString() password = project.properties["repo.credentials.self-git.password"].toString()
password = project.properties["repo.credentials.private.password"].toString()
}
}
} else {
maven("https://repo.lamgc.moe/repository/maven-releases/") {
credentials {
username = project.properties["repo.credentials.private.username"].toString()
password = project.properties["repo.credentials.private.password"].toString()
}
} }
} }
} }

View File

@ -0,0 +1,44 @@
package net.lamgc.scalabot.extension;
import net.lamgc.scalabot.config.ProxyConfig;
/**
* BotExtension 创建参数.
* <p>
* 通过该类可向 {@link BotExtensionFactory} 提供更多创建 BotExtension 时可用的参数.
*/
@SuppressWarnings("unused")
public class BotExtensionCreateOptions {
private final long botAccountId;
private final ProxyConfig proxy;
/**
* 构造新的 BotExtensionCreateOptions.
*
* @param botAccountId 创建扩展的 Bot 账户 Id.
* @param proxy Bot 所使用的代理配置.
*/
public BotExtensionCreateOptions(long botAccountId, ProxyConfig proxy) {
this.botAccountId = botAccountId;
this.proxy = proxy;
}
/**
* 获取 Bot 使用的代理信息.
*
* @return 返回 Bot TelegramClient 所使用的代理配置.
*/
public ProxyConfig getProxy() {
return proxy;
}
/**
* 获取 Bot 的账户 Id.
*
* @return 返回 Bot 的账户 Id.
*/
public long getBotAccountId() {
return botAccountId;
}
}

View File

@ -1,7 +1,8 @@
package net.lamgc.scalabot.extension; package net.lamgc.scalabot.extension;
import org.telegram.abilitybots.api.bot.BaseAbilityBot; import org.telegram.telegrambots.abilitybots.api.bot.BaseAbilityBot;
import org.telegram.abilitybots.api.util.AbilityExtension; import org.telegram.telegrambots.abilitybots.api.db.DBContext;
import org.telegram.telegrambots.abilitybots.api.util.AbilityExtension;
import java.io.File; import java.io.File;
@ -9,7 +10,7 @@ import java.io.File;
* 该接口用于为指定的 {@link BaseAbilityBot} 创建扩展. * 该接口用于为指定的 {@link BaseAbilityBot} 创建扩展.
* *
* <p> 由于 AbilityExtension 无法直接获取 {@link BaseAbilityBot} * <p> 由于 AbilityExtension 无法直接获取 {@link BaseAbilityBot}
* 数据库对象 {@link org.telegram.abilitybots.api.db.DBContext}, * 数据库对象 {@link DBContext},
* 所以将通过该接口工厂来创建扩展对象. * 所以将通过该接口工厂来创建扩展对象.
* *
* @author LamGC * @author LamGC
@ -20,7 +21,7 @@ public interface BotExtensionFactory {
/** /**
* 为给定的 {@link BaseAbilityBot} 对象创建扩展. * 为给定的 {@link BaseAbilityBot} 对象创建扩展.
* *
* <p> 如扩展无使用 {@link org.telegram.abilitybots.api.db.DBContext} 的话, * <p> 如扩展无使用 {@link DBContext} 的话,
* 也可以返回扩展单例, 因为 AbilityBot 本身并不禁止多个机器人共用一个扩展对象 * 也可以返回扩展单例, 因为 AbilityBot 本身并不禁止多个机器人共用一个扩展对象
* (AbilityBot 只是调用了扩展中的方法来创建 Ability 对象). * (AbilityBot 只是调用了扩展中的方法来创建 Ability 对象).
* *
@ -30,8 +31,10 @@ public interface BotExtensionFactory {
* <pre> $DATA_ROOT/data/extensions/{GroupId}/{ArtifactId}</pre> * <pre> $DATA_ROOT/data/extensions/{GroupId}/{ArtifactId}</pre>
* <b>同一个扩展包的 Factory</b> 接收到的共享数据目录<b>都是一样的</b>, * <b>同一个扩展包的 Factory</b> 接收到的共享数据目录<b>都是一样的</b>,
* 建议将数据存储在数据目录中, 便于数据的存储管理. * 建议将数据存储在数据目录中, 便于数据的存储管理.
* @param options 创建扩展时可用的参数.
* @return 返回为该 Bot 对象创建的扩展对象, 如果不希望为该机器人提供扩展, 可返回 {@code null}. * @return 返回为该 Bot 对象创建的扩展对象, 如果不希望为该机器人提供扩展, 可返回 {@code null}.
* @since 0.7.0
*/ */
AbilityExtension createExtensionInstance(BaseAbilityBot bot, File shareDataFolder); AbilityExtension createExtensionInstance(BaseAbilityBot bot, File shareDataFolder, BotExtensionCreateOptions options);
} }

View File

@ -1,38 +1,17 @@
package net.lamgc.scalabot.extension.util; package net.lamgc.scalabot.extension.util;
import org.telegram.abilitybots.api.bot.BaseAbilityBot; import org.telegram.telegrambots.abilitybots.api.bot.BaseAbilityBot;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AbilityBots { /**
* 一些开发扩展中可以用到的工具类.
private final static Pattern botTokenPattern = Pattern.compile("([1-9]\\d+):([A-Za-z\\d_-]{35,})"); */
public final class AbilityBots {
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 格式错误时抛出该异常.
*/
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));
}
/** /**
* 取消某一对话的状态机. * 取消某一对话的状态机.
* *
@ -41,7 +20,7 @@ public 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.db().getMap("user_state_replies"); Map<Long, Integer> stateMap = bot.getDb().getMap("user_state_replies");
if (!stateMap.containsKey(chatId)) { if (!stateMap.containsKey(chatId)) {
return false; return false;
} }

View File

@ -2,22 +2,35 @@ 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.abilitybots.api.bot.AbilityBot; import org.telegram.telegrambots.abilitybots.api.bot.AbilityBot;
import org.telegram.abilitybots.api.bot.BaseAbilityBot; import org.telegram.telegrambots.abilitybots.api.bot.BaseAbilityBot;
import org.telegram.abilitybots.api.db.MapDBContext; import org.telegram.telegrambots.abilitybots.api.db.MapDBContext;
import org.telegram.abilitybots.api.objects.*; import org.telegram.telegrambots.abilitybots.api.objects.*;
import org.telegram.abilitybots.api.sender.SilentSender; import org.telegram.telegrambots.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.*; import static org.junit.jupiter.api.Assertions.assertFalse;
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 = new User(1L, "first", false, "last", "username", null, false, false, false); public static final User USER = User.builder()
public static final User CREATOR = new User(1337L, "creatorFirst", false, "creatorLast", "creatorUsername", null, false, false, false); .userName("username")
.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);
@ -39,73 +52,67 @@ 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 = new User(10001L, "first", false, "last", "username", null, false, false, false); User userA = User.builder()
User userB = new User(10101L, "first", false, "last", "username", null, false, false, false); .id(10001L)
.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.onUpdateReceived(mockFullUpdate(bot, userA, "/set_reply")); bot.consume(mockFullUpdate(bot, userA, "/set_reply"));
verify(silent, times(1)).send("Reply set!", userA.getId()); verify(silent, times(1)).send("Reply set!", userA.getId());
bot.onUpdateReceived(mockFullUpdate(bot, userA, "reply_01")); bot.consume(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.onUpdateReceived(mockFullUpdate(bot, userA, "reply_02")); bot.consume(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.onUpdateReceived(mockFullUpdate(bot, userA, "/set_reply")); bot.consume(mockFullUpdate(bot, userA, "/set_reply"));
verify(silent, times(1)).send("Reply set!", userA.getId()); verify(silent, times(1)).send("Reply set!", userA.getId());
bot.onUpdateReceived(mockFullUpdate(bot, userA, "reply_01")); bot.consume(mockFullUpdate(bot, userA, "reply_01"));
verify(silent, times(1)).send("Reply 01", userA.getId()); verify(silent, times(1)).send("Reply 01", userA.getId());
bot.onUpdateReceived(mockFullUpdate(bot, userA, "reply_02")); bot.consume(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 botToken, String botUsername) { public TestingAbilityBot(String botUsername, SilentSender silentSender) {
super(botToken, botUsername, new MapDBContext(DBMaker.heapDB().make())); super(new NoOpTelegramClient(), 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;
} }
@SuppressWarnings("unused")
public Ability setReply() { public Ability setReply() {
return Ability.builder() return Ability.builder()
.name("set_reply") .name("set_reply")
.enableStats() .enableStats()
.locality(Locality.ALL) .locality(Locality.ALL)
.privacy(Privacy.PUBLIC) .privacy(Privacy.PUBLIC)
.action(ctx -> ctx.bot().silent().send("Reply set!", ctx.chatId())) .action(ctx -> ctx.bot().getSilent().send("Reply set!", ctx.chatId()))
.reply(ReplyFlow.builder(db()) .reply(ReplyFlow.builder(getDb())
.action((bot, upd) -> bot.silent().send("Reply 01", upd.getMessage().getChatId())) .action((bot, upd) -> bot.getSilent().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.silent().send("Reply 02", upd.getMessage().getChatId()), bot.getSilent().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

@ -0,0 +1,229 @@
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;
}
}

13
scalabot-meta/README.md Normal file
View File

@ -0,0 +1,13 @@
# scalabot-meta
本模块用于将 ScalaBot 的一些配置相关内容发布出去,以便于其他项目使用。
主要是配置类和相应的 Gson 序列化器(如果有,或者必要)。
## 关于序列化器
强烈建议使用序列化器!由于 Kotlin 与 Gson 之间的一些兼容性问题
(参见[本提交](https://github.com/LamGC/ScalaBot/commit/084280564af58d1af22db5b57c67577d93bd820e)
如果直接让 Gson 解析 Kotlin Data 类,将会出现一些潜在的问题(比如无法使用默认值)。
部分序列化器也可以帮助检查字段值是否合法,以防止因字段值不正确导致出现更多的问题
(例如 BotAccount 中,如果 `token` 的格式有误,那么获取 `id` 时将引发 `NumberFormatException` 异常)。

View File

@ -0,0 +1,201 @@
public final class net/lamgc/scalabot/config/AppConfig {
public fun <init> ()V
public fun <init> (Lnet/lamgc/scalabot/config/ProxyConfig;Lnet/lamgc/scalabot/config/MetricsConfig;Ljava/util/List;Ljava/lang/String;)V
public synthetic fun <init> (Lnet/lamgc/scalabot/config/ProxyConfig;Lnet/lamgc/scalabot/config/MetricsConfig;Ljava/util/List;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lnet/lamgc/scalabot/config/ProxyConfig;
public final fun component2 ()Lnet/lamgc/scalabot/config/MetricsConfig;
public final fun component3 ()Ljava/util/List;
public final fun component4 ()Ljava/lang/String;
public final fun copy (Lnet/lamgc/scalabot/config/ProxyConfig;Lnet/lamgc/scalabot/config/MetricsConfig;Ljava/util/List;Ljava/lang/String;)Lnet/lamgc/scalabot/config/AppConfig;
public static synthetic fun copy$default (Lnet/lamgc/scalabot/config/AppConfig;Lnet/lamgc/scalabot/config/ProxyConfig;Lnet/lamgc/scalabot/config/MetricsConfig;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lnet/lamgc/scalabot/config/AppConfig;
public fun equals (Ljava/lang/Object;)Z
public final fun getMavenLocalRepository ()Ljava/lang/String;
public final fun getMavenRepositories ()Ljava/util/List;
public final fun getMetrics ()Lnet/lamgc/scalabot/config/MetricsConfig;
public final fun getProxy ()Lnet/lamgc/scalabot/config/ProxyConfig;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class net/lamgc/scalabot/config/BotAccount {
public fun <init> (Ljava/lang/String;Ljava/lang/String;J)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()J
public final fun copy (Ljava/lang/String;Ljava/lang/String;J)Lnet/lamgc/scalabot/config/BotAccount;
public static synthetic fun copy$default (Lnet/lamgc/scalabot/config/BotAccount;Ljava/lang/String;Ljava/lang/String;JILjava/lang/Object;)Lnet/lamgc/scalabot/config/BotAccount;
public fun equals (Ljava/lang/Object;)Z
public final fun getCreatorId ()J
public final fun getId ()J
public final fun getName ()Ljava/lang/String;
public final fun getToken ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class net/lamgc/scalabot/config/BotConfig {
public fun <init> (ZLnet/lamgc/scalabot/config/BotAccount;ZZLjava/util/Set;Lnet/lamgc/scalabot/config/ProxyConfig;Ljava/lang/String;)V
public synthetic fun <init> (ZLnet/lamgc/scalabot/config/BotAccount;ZZLjava/util/Set;Lnet/lamgc/scalabot/config/ProxyConfig;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Z
public final fun component2 ()Lnet/lamgc/scalabot/config/BotAccount;
public final fun component3 ()Z
public final fun component4 ()Z
public final fun component5 ()Ljava/util/Set;
public final fun component6 ()Lnet/lamgc/scalabot/config/ProxyConfig;
public final fun component7 ()Ljava/lang/String;
public final fun copy (ZLnet/lamgc/scalabot/config/BotAccount;ZZLjava/util/Set;Lnet/lamgc/scalabot/config/ProxyConfig;Ljava/lang/String;)Lnet/lamgc/scalabot/config/BotConfig;
public static synthetic fun copy$default (Lnet/lamgc/scalabot/config/BotConfig;ZLnet/lamgc/scalabot/config/BotAccount;ZZLjava/util/Set;Lnet/lamgc/scalabot/config/ProxyConfig;Ljava/lang/String;ILjava/lang/Object;)Lnet/lamgc/scalabot/config/BotConfig;
public fun equals (Ljava/lang/Object;)Z
public final fun getAccount ()Lnet/lamgc/scalabot/config/BotAccount;
public final fun getAutoUpdateCommandList ()Z
public final fun getBaseApiTelegramUrl ()Lorg/telegram/telegrambots/meta/TelegramUrl;
public final fun getBaseApiUrl ()Ljava/lang/String;
public final fun getDisableBuiltInAbility ()Z
public final fun getEnabled ()Z
public final fun getExtensions ()Ljava/util/Set;
public final fun getProxy ()Lnet/lamgc/scalabot/config/ProxyConfig;
public fun hashCode ()I
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 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 final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/net/URL;
public final fun component3 ()Lorg/eclipse/aether/repository/Proxy;
public final fun component4 ()Ljava/lang/String;
public final fun component5 ()Z
public final fun component6 ()Z
public final fun component7 ()Lorg/eclipse/aether/repository/Authentication;
public final fun copy (Ljava/lang/String;Ljava/net/URL;Lorg/eclipse/aether/repository/Proxy;Ljava/lang/String;ZZLorg/eclipse/aether/repository/Authentication;)Lnet/lamgc/scalabot/config/MavenRepositoryConfig;
public static synthetic fun copy$default (Lnet/lamgc/scalabot/config/MavenRepositoryConfig;Ljava/lang/String;Ljava/net/URL;Lorg/eclipse/aether/repository/Proxy;Ljava/lang/String;ZZLorg/eclipse/aether/repository/Authentication;ILjava/lang/Object;)Lnet/lamgc/scalabot/config/MavenRepositoryConfig;
public fun equals (Ljava/lang/Object;)Z
public final fun getAuthentication ()Lorg/eclipse/aether/repository/Authentication;
public final fun getEnableReleases ()Z
public final fun getEnableSnapshots ()Z
public final fun getId ()Ljava/lang/String;
public final fun getLayout ()Ljava/lang/String;
public final fun getProxy ()Lorg/eclipse/aether/repository/Proxy;
public final fun getUrl ()Ljava/net/URL;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class net/lamgc/scalabot/config/MetricsConfig {
public fun <init> ()V
public fun <init> (ZILjava/lang/String;Lnet/lamgc/scalabot/config/UsernameAuthenticator;)V
public synthetic fun <init> (ZILjava/lang/String;Lnet/lamgc/scalabot/config/UsernameAuthenticator;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Z
public final fun component2 ()I
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Lnet/lamgc/scalabot/config/UsernameAuthenticator;
public final fun copy (ZILjava/lang/String;Lnet/lamgc/scalabot/config/UsernameAuthenticator;)Lnet/lamgc/scalabot/config/MetricsConfig;
public static synthetic fun copy$default (Lnet/lamgc/scalabot/config/MetricsConfig;ZILjava/lang/String;Lnet/lamgc/scalabot/config/UsernameAuthenticator;ILjava/lang/Object;)Lnet/lamgc/scalabot/config/MetricsConfig;
public fun equals (Ljava/lang/Object;)Z
public final fun getAuthenticator ()Lnet/lamgc/scalabot/config/UsernameAuthenticator;
public final fun getBindAddress ()Ljava/lang/String;
public final fun getEnable ()Z
public final fun getPort ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class net/lamgc/scalabot/config/ProxyConfig {
public fun <init> ()V
public fun <init> (Lnet/lamgc/scalabot/config/ProxyType;Ljava/lang/String;I)V
public synthetic fun <init> (Lnet/lamgc/scalabot/config/ProxyType;Ljava/lang/String;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lnet/lamgc/scalabot/config/ProxyType;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()I
public final fun copy (Lnet/lamgc/scalabot/config/ProxyType;Ljava/lang/String;I)Lnet/lamgc/scalabot/config/ProxyConfig;
public static synthetic fun copy$default (Lnet/lamgc/scalabot/config/ProxyConfig;Lnet/lamgc/scalabot/config/ProxyType;Ljava/lang/String;IILjava/lang/Object;)Lnet/lamgc/scalabot/config/ProxyConfig;
public fun equals (Ljava/lang/Object;)Z
public final fun getHost ()Ljava/lang/String;
public final fun getPort ()I
public final fun getType ()Lnet/lamgc/scalabot/config/ProxyType;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class net/lamgc/scalabot/config/ProxyType : java/lang/Enum {
public static final field HTTP Lnet/lamgc/scalabot/config/ProxyType;
public static final field HTTPS Lnet/lamgc/scalabot/config/ProxyType;
public static final field NO_PROXY Lnet/lamgc/scalabot/config/ProxyType;
public static final field SOCKS4 Lnet/lamgc/scalabot/config/ProxyType;
public static final field SOCKS5 Lnet/lamgc/scalabot/config/ProxyType;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lnet/lamgc/scalabot/config/ProxyType;
public static fun values ()[Lnet/lamgc/scalabot/config/ProxyType;
}
public final class net/lamgc/scalabot/config/UsernameAuthenticator : com/sun/net/httpserver/BasicAuthenticator {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public fun checkCredentials (Ljava/lang/String;Ljava/lang/String;)Z
public fun equals (Ljava/lang/Object;)Z
public fun hashCode ()I
public final fun toJsonObject ()Lcom/google/gson/JsonObject;
}
public final class net/lamgc/scalabot/config/serializer/ArtifactSerializer : com/google/gson/JsonDeserializer, com/google/gson/JsonSerializer {
public static final field INSTANCE Lnet/lamgc/scalabot/config/serializer/ArtifactSerializer;
public synthetic fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Ljava/lang/Object;
public fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lorg/eclipse/aether/artifact/Artifact;
public synthetic fun serialize (Ljava/lang/Object;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;
public fun serialize (Lorg/eclipse/aether/artifact/Artifact;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;
}
public final class net/lamgc/scalabot/config/serializer/AuthenticationSerializer : com/google/gson/JsonDeserializer {
public static final field INSTANCE Lnet/lamgc/scalabot/config/serializer/AuthenticationSerializer;
public synthetic fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Ljava/lang/Object;
public fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lorg/eclipse/aether/repository/Authentication;
}
public final class net/lamgc/scalabot/config/serializer/BotAccountSerializer : com/google/gson/JsonDeserializer {
public static final field INSTANCE Lnet/lamgc/scalabot/config/serializer/BotAccountSerializer;
public synthetic fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Ljava/lang/Object;
public fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/lamgc/scalabot/config/BotAccount;
}
public final class net/lamgc/scalabot/config/serializer/BotConfigSerializer : com/google/gson/JsonDeserializer, com/google/gson/JsonSerializer {
public static final field INSTANCE Lnet/lamgc/scalabot/config/serializer/BotConfigSerializer;
public synthetic fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Ljava/lang/Object;
public fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/lamgc/scalabot/config/BotConfig;
public synthetic fun serialize (Ljava/lang/Object;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;
public fun serialize (Lnet/lamgc/scalabot/config/BotConfig;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;
}
public final class net/lamgc/scalabot/config/serializer/MavenRepositoryConfigSerializer : com/google/gson/JsonDeserializer {
public static final field INSTANCE Lnet/lamgc/scalabot/config/serializer/MavenRepositoryConfigSerializer;
public synthetic fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Ljava/lang/Object;
public fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/lamgc/scalabot/config/MavenRepositoryConfig;
}
public final class net/lamgc/scalabot/config/serializer/ProxyConfigSerializer : com/google/gson/JsonDeserializer, com/google/gson/JsonSerializer {
public static final field INSTANCE Lnet/lamgc/scalabot/config/serializer/ProxyConfigSerializer;
public synthetic fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Ljava/lang/Object;
public fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/lamgc/scalabot/config/ProxyConfig;
public synthetic fun serialize (Ljava/lang/Object;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;
public fun serialize (Lnet/lamgc/scalabot/config/ProxyConfig;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;
}
public final class net/lamgc/scalabot/config/serializer/ProxyTypeSerializer : com/google/gson/JsonDeserializer, com/google/gson/JsonSerializer {
public static final field INSTANCE Lnet/lamgc/scalabot/config/serializer/ProxyTypeSerializer;
public synthetic fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Ljava/lang/Object;
public fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/lamgc/scalabot/config/ProxyType;
public synthetic fun serialize (Ljava/lang/Object;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;
public fun serialize (Lnet/lamgc/scalabot/config/ProxyType;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;
}
public final class net/lamgc/scalabot/config/serializer/UsernameAuthenticatorSerializer : com/google/gson/JsonDeserializer, com/google/gson/JsonSerializer {
public static final field INSTANCE Lnet/lamgc/scalabot/config/serializer/UsernameAuthenticatorSerializer;
public synthetic fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Ljava/lang/Object;
public fun deserialize (Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/lamgc/scalabot/config/UsernameAuthenticator;
public synthetic fun serialize (Ljava/lang/Object;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;
public fun serialize (Lnet/lamgc/scalabot/config/UsernameAuthenticator;Ljava/lang/reflect/Type;Lcom/google/gson/JsonSerializationContext;)Lcom/google/gson/JsonElement;
}

View File

@ -0,0 +1,107 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
id("org.jetbrains.kotlinx.kover")
id("org.jetbrains.dokka") version "1.9.20"
`maven-publish`
signing
id("org.jetbrains.kotlinx.binary-compatibility-validator")
}
dependencies {
val aetherVersion = "1.1.0"
api("org.eclipse.aether:aether-api:$aetherVersion")
implementation("org.eclipse.aether:aether-util:$aetherVersion")
implementation("org.telegram:telegrambots-meta:8.0.0")
api("com.google.code.gson:gson:2.11.0")
testImplementation(kotlin("test"))
testImplementation("io.mockk:mockk:1.13.13")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1")
dokkaHtmlPlugin("org.jetbrains.dokka:javadoc-plugin:2.0.0")
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions {
jvmTarget = JvmTarget.JVM_17
}
}
java {
withJavadocJar()
withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
tasks.getByName<Test>("test") {
useJUnitPlatform()
}
val javadocJar = tasks.named<Jar>("javadocJar") {
from(tasks.named("dokkaJavadoc"))
}
publishing {
repositories {
maven("https://git.lamgc.me/api/packages/LamGC/maven") {
credentials {
username = project.properties["repo.credentials.self-git.username"].toString()
password = project.properties["repo.credentials.self-git.password"].toString()
}
}
}
publications {
create<MavenPublication>("maven") {
from(components["kotlin"])
artifact(javadocJar)
artifact(tasks.named("sourcesJar"))
pom {
name.set("ScalaBot-meta")
description.set(
"Shared components used by scalabot (such as configuration classes)"
)
url.set("https://github.com/LamGC/ScalaBot")
licenses {
license {
name.set("The MIT License")
url.set("https://www.opensource.org/licenses/mit-license.php")
}
}
developers {
developer {
id.set("LamGC")
name.set("LamGC")
email.set("lam827@lamgc.net")
url.set("https://github.com/LamGC")
}
}
scm {
connection.set("scm:git:https://github.com/LamGC/ScalaBot.git")
developerConnection.set("scm:git:https://github.com/LamGC/ScalaBot.git")
url.set("https://github.com/LamGC/ScalaBot")
}
issueManagement {
url.set("https://github.com/LamGC/ScalaBot/issues")
system.set("Github Issues")
}
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications["maven"])
}

View File

@ -0,0 +1,175 @@
package net.lamgc.scalabot.config
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.repository.Authentication
import org.eclipse.aether.repository.Proxy
import org.telegram.telegrambots.meta.TelegramUrl
import java.net.URI
import java.net.URL
/**
* 机器人帐号信息.
* @property name 机器人名称, 建议与实际设定的名称相同.
* @property token 机器人 API Token.
* @property creatorId 机器人创建者, 管理机器人需要使用该信息.
* @property id 机器人账号 ID.
*/
data class BotAccount(
val name: String,
val token: String,
val creatorId: Long
) {
val id
// 不要想着每次获取都要从 token 里取出有性能损耗.
// 由于 Gson 解析方式, 如果不这么做, 会出现 token 设置前 id 初始化完成, 就只有"0"了,
// 虽然能过单元测试, 但实际使用过程是不能正常用的.
get() = token.substringBefore(":").toLong()
}
val defaultTelegramApiUrl: String = URL(
TelegramUrl.DEFAULT_URL.schema,
TelegramUrl.DEFAULT_URL.host,
TelegramUrl.DEFAULT_URL.port,
"/"
).toExternalForm()
/**
* 机器人配置.
*
* 使用 Gson 解析时, 请添加以下类型适配器:
* - [net.lamgc.scalabot.config.serializer.ProxyTypeSerializer]
* - [net.lamgc.scalabot.config.serializer.BotConfigSerializer]
* - [net.lamgc.scalabot.config.serializer.BotAccountSerializer]
* - [net.lamgc.scalabot.config.serializer.ArtifactSerializer]
*
* @property enabled 是否启用机器人.
* @property account 机器人帐号信息, 用于访问 API.
* @property disableBuiltInAbility 是否禁用 AbilityBot 自带命令.
* @property autoUpdateCommandList 是否自动更新机器人在 Telegram 的命令列表.
* @property extensions 该机器人启用的扩展.
* @property proxy 为该机器人单独设置的代理配置, 如无设置, 则使用 AppConfig 中的代理配置.
* @property baseApiUrl 机器人所使用的 API 地址, 适用于自建 Telegram Bot API 端点.
*/
data class BotConfig(
val enabled: Boolean = false,
val account: BotAccount,
val disableBuiltInAbility: Boolean = false,
val autoUpdateCommandList: Boolean = false,
/*
* 使用构件坐标来选择机器人所使用的扩展包.
* 这么做的原因是我暂时没找到一个合适的方法来让开发者方便地设定自己的扩展 Id,
* 而构件坐标(POM Reference 或者叫 GAV 坐标)是开发者创建 Maven/Gradle 项目时一定会设置的,
* 所以就直接用了. :P
*/
val extensions: Set<Artifact> = emptySet(),
val proxy: ProxyConfig = ProxyConfig(type = ProxyType.NO_PROXY),
val baseApiUrl: String = defaultTelegramApiUrl
) {
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()
}
}
}
}
/**
* 代理类型.
*/
enum class ProxyType {
NO_PROXY,
HTTP,
HTTPS,
SOCKS4,
SOCKS5
}
/**
* 代理配置.
* @property type 代理类型.
* @property host 代理服务端地址.
* @property port 代理服务端端口.
*/
data class ProxyConfig(
val type: ProxyType = ProxyType.NO_PROXY,
val host: String = "127.0.0.1",
val port: Int = 1080,
) {
override fun toString(): String {
return if (type != ProxyType.NO_PROXY) {
"$type://$host:$port"
} else {
"NO_PROXY"
}
}
}
/**
* ScalaBot 的运行指标公开配置.
*
* ScalaBot 内置了用于公开运行指标的服务端,
* 该指标遵循 Prometheus 的标准, 可以通过 Prometheus 的工具来查看.
*
* @property enable 是否启用运行指标服务端.
* @property port 运行指标服务端的端口.
* @property bindAddress 运行指标服务端的绑定地址, 绑定后只有该地址可以访问.
* @property authenticator 运行指标服务端的 HTTP 认证配置.
*/
data class MetricsConfig(
val enable: Boolean = false,
val port: Int = 9386,
val bindAddress: String? = "0.0.0.0",
val authenticator: UsernameAuthenticator? = null
)
/**
* Maven 远端仓库配置.
* @property id 远端仓库 ID, 如果该属性未配置 (null), 那么运行时将会自动分配一个 Id.
* @property url 仓库地址.
* @property proxy 访问仓库所使用的代理, 仅支持 http/https 代理.
* @property layout 仓库布局版本, Maven 2 及以上使用 `default`, Maven 1 使用 `legacy`.
* @property enableReleases 是否在该远端仓库获取发布版本.
* @property enableSnapshots 是否在该远端仓库获取快照版本.
* @property authentication 访问该远端仓库所使用的认证配置.
*/
data class MavenRepositoryConfig(
val id: String? = null,
val url: URL,
val proxy: Proxy? = null,
val layout: String = "default",
val enableReleases: Boolean = true,
val enableSnapshots: Boolean = true,
// 可能要设计个 type 来判断解析成什么类型的 Authentication.
val authentication: Authentication? = null
)
/**
* ScalaBot App 配置.
*
* App 配置信息与 BotConfig 分开, 分别存储在各自单独的文件中.
*
* 使用 Gson 解析时, 请添加以下类型适配器:
* - [net.lamgc.scalabot.config.serializer.ProxyTypeSerializer]
* - [net.lamgc.scalabot.config.serializer.MavenRepositoryConfigSerializer]
* - [net.lamgc.scalabot.config.serializer.AuthenticationSerializer]
* - [net.lamgc.scalabot.config.serializer.UsernameAuthenticatorSerializer]
*
* @property proxy Telegram API 代理配置.
* @property metrics 运行指标数据配置. 可通过时序数据库记录运行数据.
* @property mavenRepositories Maven 远端仓库配置.
* @property mavenLocalRepository Maven 本地仓库路径. 相对于运行目录 (而不是 DATA_ROOT 目录)
*/
data class AppConfig(
val proxy: ProxyConfig = ProxyConfig(),
val metrics: MetricsConfig = MetricsConfig(),
val mavenRepositories: List<MavenRepositoryConfig> = emptyList(),
val mavenLocalRepository: String? = null
)

View File

@ -0,0 +1,26 @@
package net.lamgc.scalabot.config
import com.google.gson.JsonObject
import com.sun.net.httpserver.BasicAuthenticator
class UsernameAuthenticator(private val username: String, private val password: String) :
BasicAuthenticator("metrics") {
override fun checkCredentials(username: String?, password: String?): Boolean =
this.username == username && this.password == password
fun toJsonObject(): JsonObject = JsonObject().apply {
addProperty("username", username)
addProperty("password", password)
}
override fun equals(other: Any?): Boolean {
return other is UsernameAuthenticator && this.username == other.username && this.password == other.password
}
override fun hashCode(): Int {
var result = username.hashCode()
result = 31 * result + password.hashCode()
return result
}
}

View File

@ -0,0 +1,300 @@
package net.lamgc.scalabot.config.serializer
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import net.lamgc.scalabot.config.*
import net.lamgc.scalabot.config.serializer.SerializeUtils.getPrimitiveValueOrThrow
import org.eclipse.aether.artifact.AbstractArtifact
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.artifact.DefaultArtifact
import org.eclipse.aether.repository.Authentication
import org.eclipse.aether.repository.Proxy
import org.eclipse.aether.util.repository.AuthenticationBuilder
import java.lang.reflect.Type
import java.net.MalformedURLException
import java.net.URL
import java.util.regex.Pattern
object ProxyTypeSerializer : JsonDeserializer<ProxyType>,
JsonSerializer<ProxyType> {
override fun deserialize(
json: JsonElement,
typeOfT: Type?,
context: JsonDeserializationContext?
): ProxyType {
if (json.isJsonNull) {
return ProxyType.NO_PROXY
}
if (!json.isJsonPrimitive) {
throw JsonParseException("Wrong configuration value type.")
}
val value = json.asString.trim()
try {
return ProxyType.valueOf(value.uppercase())
} catch (e: IllegalArgumentException) {
throw JsonParseException("Invalid value: $value")
}
}
override fun serialize(
src: ProxyType,
typeOfSrc: Type?,
context: JsonSerializationContext?
): JsonElement {
return JsonPrimitive(src.toString())
}
}
object ArtifactSerializer : JsonSerializer<Artifact>, JsonDeserializer<Artifact> {
override fun serialize(src: Artifact, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
return if (src is AbstractArtifact) {
JsonPrimitive(src.toString())
} else {
JsonPrimitive(
DefaultArtifact(
src.groupId,
src.artifactId,
src.classifier,
src.extension,
src.version
).toString()
)
}
}
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): Artifact {
if (!json.isJsonPrimitive) {
throw JsonParseException("Wrong configuration value type.")
}
val artifactStr = json.asString.trim()
try {
return DefaultArtifact(artifactStr)
} catch (e: IllegalArgumentException) {
throw JsonParseException("Invalid artifact format: `${artifactStr}`.")
}
}
}
object AuthenticationSerializer : JsonDeserializer<Authentication> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Authentication {
if (json !is JsonObject) {
throw JsonParseException("Unsupported JSON type.")
}
val username = json.getPrimitiveValueOrThrow("username").asString
val password = json.getPrimitiveValueOrThrow("password").asString
val builder = AuthenticationBuilder()
builder.addUsername(username)
builder.addPassword(password)
return builder.build()
}
}
internal object SerializeUtils {
fun JsonObject.getPrimitiveValueOrThrow(fieldName: String): JsonPrimitive {
val value = get(fieldName) ?: throw JsonParseException("Missing `$fieldName` field.")
if (value !is JsonPrimitive) {
throw JsonParseException("Invalid `account` field type.")
}
return value
}
}
object MavenRepositoryConfigSerializer
: JsonDeserializer<MavenRepositoryConfig> {
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): MavenRepositoryConfig {
return when (json) {
is JsonObject -> {
MavenRepositoryConfig(
id = json.get("id")?.asString,
url = URL(json.getPrimitiveValueOrThrow("url").asString),
proxy = if (json.has("proxy"))
context.deserialize<Proxy>(
json.get("proxy"), Proxy::class.java
) else null,
layout = json.get("layout")?.asString ?: "default",
enableReleases = json.get("enableReleases")?.asBoolean ?: true,
enableSnapshots = json.get("enableSnapshots")?.asBoolean ?: true,
authentication = if (json.has("authentication"))
context.deserialize<Authentication>(
json.get("authentication"), Authentication::class.java
) else null
)
}
is JsonPrimitive -> {
try {
return MavenRepositoryConfig(url = URL(json.asString))
} catch (e: MalformedURLException) {
throw JsonParseException("Invalid URL: ${json.asString}", e)
}
}
else -> {
throw JsonParseException("Unsupported Maven repository configuration type. (Only support JSON object or url string)")
}
}
}
}
object UsernameAuthenticatorSerializer : JsonSerializer<UsernameAuthenticator>,
JsonDeserializer<UsernameAuthenticator> {
override fun serialize(
src: UsernameAuthenticator,
typeOfSrc: Type?,
context: JsonSerializationContext?
): JsonElement {
return src.toJsonObject()
}
override fun deserialize(
json: JsonElement,
typeOfT: Type?,
context: JsonDeserializationContext?
): UsernameAuthenticator? {
if (json.isJsonNull) {
return null
} else if (!json.isJsonObject) {
throw JsonParseException("Invalid attribute value type.")
}
val jsonObj = json.asJsonObject
if (jsonObj["username"]?.isJsonPrimitive != true) {
throw JsonParseException("Invalid attribute value: username")
} else if (jsonObj["password"]?.isJsonPrimitive != true) {
throw JsonParseException("Invalid attribute value: password")
}
if (jsonObj["username"].asString.isEmpty() || jsonObj["password"].asString.isEmpty()) {
throw JsonParseException("`username` or `password` is empty.")
}
return UsernameAuthenticator(jsonObj["username"].asString, jsonObj["password"].asString)
}
}
object ProxyConfigSerializer : JsonSerializer<ProxyConfig>, JsonDeserializer<ProxyConfig> {
override fun serialize(src: ProxyConfig?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
if (src == null) {
return JsonNull.INSTANCE
}
return JsonObject().apply {
addProperty("type", src.type.name)
addProperty("host", src.host)
addProperty("port", src.port)
}
}
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): ProxyConfig {
if (json == null || json.isJsonNull) {
return ProxyConfig()
} else if (json !is JsonObject) {
throw JsonParseException("Invalid json type.")
}
val typeStr = json["type"]?.asString ?: return ProxyConfig()
val type = try {
ProxyType.valueOf(typeStr)
} catch (e: IllegalArgumentException) {
throw JsonParseException("Invalid proxy type: `$typeStr`")
}
if (!json.has("host") || !json.has("port")) {
throw JsonParseException("Missing `host` field or `port` field.")
}
return ProxyConfig(
type = type,
host = json["host"].asString,
port = json["port"].asInt
)
}
}
object BotConfigSerializer : JsonSerializer<BotConfig>, JsonDeserializer<BotConfig> {
private val defaultConfig = BotConfig(account = BotAccount("__Default__", "__Default__", 0))
override fun serialize(src: BotConfig, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonObject().apply {
addProperty("enabled", src.enabled)
add("account", context.serialize(src.account))
addProperty("disableBuiltInAbility", src.disableBuiltInAbility)
addProperty("autoUpdateCommandList", src.autoUpdateCommandList)
add("extensions", context.serialize(src.extensions))
add("proxy", ProxyConfigSerializer.serialize(src.proxy, ProxyConfig::class.java, context))
addProperty("baseApiUrl", src.baseApiUrl)
}
}
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): BotConfig {
if (json !is JsonObject) {
throw JsonParseException("Unsupported JSON type.")
}
if (!json.has("account")) {
throw JsonParseException("Missing `account` field.")
} else if (!json.get("account").isJsonObject) {
throw JsonParseException("Invalid `account` field type.")
}
// 从 json 反序列化 BotConfig使用构造函数
return BotConfig(
enabled = json.get("enabled")?.asBoolean ?: defaultConfig.enabled,
account = context.deserialize(json.get("account"), BotAccount::class.java)!!,
disableBuiltInAbility = json.get("disableBuiltInAbility")?.asBoolean ?: defaultConfig.disableBuiltInAbility,
autoUpdateCommandList = json.get("autoUpdateCommandList")?.asBoolean ?: defaultConfig.autoUpdateCommandList,
extensions = context.deserialize(json.get("extensions"), object : TypeToken<Set<Artifact>>() {}.type)
?: defaultConfig.extensions,
proxy = context.deserialize(json.get("proxy"), ProxyConfig::class.java) ?: defaultConfig.proxy,
baseApiUrl = json.get("baseApiUrl")?.asString ?: defaultConfig.baseApiUrl
)
}
}
object BotAccountSerializer : JsonDeserializer<BotAccount> {
private val tokenCheckRegex = Pattern.compile("\\d+:[a-zA-Z\\d_-]{35}")
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): BotAccount {
if (json == null || json.isJsonNull) {
throw JsonParseException("Missing `account` field.")
} else if (!json.isJsonObject) {
throw JsonParseException("Invalid `account` field type.")
}
val jsonObj = json.asJsonObject
val name = jsonObj.getPrimitiveValueOrThrow("name").asString
val token = jsonObj.getPrimitiveValueOrThrow("token").asString.let {
if (it.isEmpty()) {
throw JsonParseException("`token` cannot be empty.")
} else if (!tokenCheckRegex.matcher(it).matches()) {
throw JsonParseException("`token` is invalid.")
} else {
it
}
}
val creatorId = try {
jsonObj.getPrimitiveValueOrThrow("creatorId").asLong
} catch (e: NumberFormatException) {
throw JsonParseException("`creatorId` must be a number.")
}.apply {
if (this < 0) {
throw JsonParseException("`creatorId` must be a positive number.")
}
}
return BotAccount(name, token, creatorId)
}
}

View File

@ -0,0 +1,522 @@
package net.lamgc.scalabot.config
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import net.lamgc.scalabot.config.serializer.*
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.artifact.DefaultArtifact
import org.eclipse.aether.repository.Authentication
import org.eclipse.aether.repository.AuthenticationContext
import org.eclipse.aether.repository.Proxy
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Assertions
import java.net.URL
import java.util.*
import kotlin.math.abs
import kotlin.test.*
internal class BotAccountTest {
@Test
fun `id getter`() {
val accountId = abs(Random().nextInt()).toLong()
Assertions.assertEquals(accountId, BotAccount("Test", "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", 0).id)
}
private val gson = GsonBuilder()
.create()
@Test
fun deserializerTest() {
val accountId = abs(Random().nextInt()).toLong()
val creatorId = abs(Random().nextInt()).toLong()
val botAccountJsonObject = gson.fromJson(
"""
{
"name": "TestBot",
"token": "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10",
"creatorId": $creatorId
}
""".trimIndent(), JsonObject::class.java
)
val botAccount = Gson().fromJson(botAccountJsonObject, BotAccount::class.java)
assertEquals(accountId, botAccount.id)
assertEquals("TestBot", botAccount.name)
assertEquals(creatorId, botAccount.creatorId)
assertEquals("${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", botAccount.token)
}
@Test
fun serializerTest() {
val accountId = abs(Random().nextInt()).toLong()
val creatorId = abs(Random().nextInt()).toLong()
val botAccount = BotAccount("TestBot", "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", creatorId)
val botAccountJsonObject = gson.toJsonTree(botAccount)
assertTrue(botAccountJsonObject is JsonObject)
assertEquals(botAccount.name, botAccountJsonObject["name"].asString)
assertEquals(botAccount.token, botAccountJsonObject["token"].asString)
assertNull(botAccountJsonObject["id"])
Assertions.assertEquals(creatorId, botAccountJsonObject["creatorId"].asLong)
}
}
internal class BotConfigTest {
private val gson = GsonBuilder()
.registerTypeAdapter(BotConfig::class.java, BotConfigSerializer)
.registerTypeAdapter(Artifact::class.java, ArtifactSerializer)
.registerTypeAdapter(ProxyConfig::class.java, ProxyConfigSerializer)
.create()
@Test
fun `json serialize`() {
val minimumExpectConfig = BotConfig(
account = BotAccount(
name = "TestBot",
token = "123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10",
creatorId = 123456789L
),
)
val json = gson.toJsonTree(minimumExpectConfig)
assertTrue(json is JsonObject)
assertEquals(minimumExpectConfig.enabled, json.get("enabled").asBoolean)
assertEquals(minimumExpectConfig.account.name, json.get("account").asJsonObject.get("name").asString)
assertEquals(minimumExpectConfig.account.token, json.get("account").asJsonObject.get("token").asString)
assertEquals(minimumExpectConfig.account.creatorId, json.get("account").asJsonObject.get("creatorId").asLong)
assertNull(json.get("account").asJsonObject.get("id"))
assertEquals(minimumExpectConfig.proxy.host, json.get("proxy").asJsonObject.get("host").asString)
assertEquals(minimumExpectConfig.proxy.port, json.get("proxy").asJsonObject.get("port").asInt)
assertEquals(minimumExpectConfig.proxy.type.name, json.get("proxy").asJsonObject.get("type").asString)
assertEquals(minimumExpectConfig.disableBuiltInAbility, json.get("disableBuiltInAbility").asBoolean)
assertEquals(minimumExpectConfig.autoUpdateCommandList, json.get("autoUpdateCommandList").asBoolean)
assertNotNull(json.get("extensions"))
assertTrue(json.get("extensions").isJsonArray)
assertTrue(json.get("extensions").asJsonArray.isEmpty)
assertEquals(minimumExpectConfig.baseApiUrl, json.get("baseApiUrl").asString)
}
@Test
fun `json deserialize`() {
val expectExtensionArtifact = DefaultArtifact("org.example.test:test-extension:1.0.0")
@Language("JSON5") val looksGoodJson = """
{
"enabled": false,
"account": {
"name": "TestBot",
"token": "123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10",
"creatorId": 123456789
},
"proxy": {
"host": "localhost",
"port": 8080,
"type": "HTTP"
},
"disableBuiltInAbility": false,
"autoUpdateCommandList": true,
"extensions": [
"$expectExtensionArtifact"
],
"baseApiUrl": "http://localhost:8080"
}
""".trimIndent()
val actualConfig = gson.fromJson(looksGoodJson, BotConfig::class.java)
assertEquals(false, actualConfig.enabled)
assertEquals("TestBot", actualConfig.account.name)
assertEquals("123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", actualConfig.account.token)
assertEquals(123456789L, actualConfig.account.creatorId)
assertEquals("localhost", actualConfig.proxy.host)
assertEquals(8080, actualConfig.proxy.port)
assertEquals(ProxyType.HTTP, actualConfig.proxy.type)
assertEquals(false, actualConfig.disableBuiltInAbility)
assertEquals(true, actualConfig.autoUpdateCommandList)
assertEquals(1, actualConfig.extensions.size)
assertEquals(expectExtensionArtifact, actualConfig.extensions.first())
assertEquals("http://localhost:8080", actualConfig.baseApiUrl)
}
@Test
fun `json deserialize - minimum parameters`() {
@Language("JSON5") val minimumLooksGoodJson = """
{
"account": {
"name": "TestBot",
"token": "123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10",
"creatorId": 123456789
}
}
""".trimIndent()
val expectDefaultConfig = BotConfig(account = BotAccount("Test", "Test", 0))
val actualMinimumConfig = gson.fromJson(minimumLooksGoodJson, BotConfig::class.java)
assertNotNull(actualMinimumConfig)
assertEquals("TestBot", actualMinimumConfig.account.name)
assertEquals("123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", actualMinimumConfig.account.token)
assertEquals(123456789, actualMinimumConfig.account.creatorId)
assertEquals(expectDefaultConfig.enabled, actualMinimumConfig.enabled)
assertEquals(expectDefaultConfig.disableBuiltInAbility, actualMinimumConfig.disableBuiltInAbility)
assertEquals(expectDefaultConfig.autoUpdateCommandList, actualMinimumConfig.autoUpdateCommandList)
assertEquals(expectDefaultConfig.proxy, actualMinimumConfig.proxy)
assertEquals(expectDefaultConfig.baseApiUrl, actualMinimumConfig.baseApiUrl)
assertTrue(expectDefaultConfig.extensions.containsAll(actualMinimumConfig.extensions))
assertTrue(actualMinimumConfig.extensions.containsAll(expectDefaultConfig.extensions))
}
}
internal class ProxyConfigTest {
private val gson = GsonBuilder()
.registerTypeAdapter(ProxyConfig::class.java, ProxyConfigSerializer)
.registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer)
.create()
@Test
fun `json serialize`() {
val proxyConfig = ProxyConfig(
host = "localhost",
port = 8080,
type = ProxyType.HTTP
)
val json = gson.toJsonTree(proxyConfig)
assertTrue(json is JsonObject)
assertEquals(proxyConfig.host, json.get("host").asString)
assertEquals(proxyConfig.port, json.get("port").asInt)
assertEquals(proxyConfig.type.name, json.get("type").asString)
}
@Test
fun `json deserialize`() {
@Language("JSON5") val looksGoodJson = """
{
"host": "localhost",
"port": 8080,
"type": "HTTP"
}
""".trimIndent()
val actualConfig = gson.fromJson(looksGoodJson, ProxyConfig::class.java)
assertEquals("localhost", actualConfig.host)
assertEquals(8080, actualConfig.port)
assertEquals(ProxyType.HTTP, actualConfig.type)
}
@Test
fun `toString test`() {
assertEquals("NO_PROXY", ProxyConfig(ProxyType.NO_PROXY).toString())
assertEquals("HTTP://example.org:1008", ProxyConfig(ProxyType.HTTP, "example.org", 1008).toString())
}
}
internal class MetricsConfigTest {
private val gson = GsonBuilder()
.registerTypeAdapter(UsernameAuthenticator::class.java, UsernameAuthenticatorSerializer)
.create()
@Test
fun `json serializer`() {
val config = MetricsConfig(
enable = true,
port = 8800,
bindAddress = "127.0.0.1",
authenticator = UsernameAuthenticator(
username = "username",
password = "password"
)
)
val json = gson.toJsonTree(config).asJsonObject
assertEquals(config.enable, json.get("enable").asBoolean)
assertEquals(config.port, json.get("port").asInt)
assertEquals(config.bindAddress, json.get("bindAddress").asString)
assertNotNull(config.authenticator)
assertTrue(
config.authenticator!!.checkCredentials(
json.get("authenticator").asJsonObject.get("username").asString,
json.get("authenticator").asJsonObject.get("password").asString
)
)
val expectDefaultValueConfig = MetricsConfig()
val defaultValueJson = gson.toJsonTree(expectDefaultValueConfig).asJsonObject
assertEquals(expectDefaultValueConfig.enable, defaultValueJson.get("enable").asBoolean)
assertEquals(expectDefaultValueConfig.port, defaultValueJson.get("port").asInt)
assertEquals(expectDefaultValueConfig.bindAddress, defaultValueJson.get("bindAddress").asString)
assertNull(defaultValueJson.get("authenticator"))
}
@Test
fun `json deserializer`() {
val json = """
{
"enable": true,
"port": 8800,
"bindAddress": "127.0.0.1",
"authenticator": {
"username": "username",
"password": "password"
}
}
""".trimIndent()
val config = gson.fromJson(json, MetricsConfig::class.java)
assertEquals(true, config.enable)
assertEquals(8800, config.port)
assertEquals("127.0.0.1", config.bindAddress)
assertNotNull(config.authenticator)
assertTrue(config.authenticator!!.checkCredentials("username", "password"))
val defaultValueConfig = MetricsConfig()
val defaultValueJson = gson.toJsonTree(defaultValueConfig).asJsonObject
assertEquals(defaultValueConfig.enable, defaultValueJson.get("enable").asBoolean)
assertEquals(defaultValueConfig.port, defaultValueJson.get("port").asInt)
assertEquals(defaultValueConfig.bindAddress, defaultValueJson.get("bindAddress").asString)
assertNull(defaultValueJson.get("authenticator"))
}
@Test
fun `json deserializer - default value`() {
val actualConfig = gson.fromJson("{}", MetricsConfig::class.java)
val expectConfig = MetricsConfig()
assertEquals(expectConfig, actualConfig)
}
}
internal class MavenRepositoryConfigTest {
private val gson = GsonBuilder()
.registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer)
.registerTypeAdapter(Authentication::class.java, AuthenticationSerializer)
.create()
@Test
fun `json serializer`() {
val config = MavenRepositoryConfig(
id = "test",
url = URL("http://localhost:8080/repository"),
proxy = Proxy(
"http",
"localhost",
8080,
),
layout = "legacy",
enableReleases = false,
enableSnapshots = true,
authentication = null
)
val json = gson.toJsonTree(config).asJsonObject
assertEquals(config.id, json.get("id").asString)
assertEquals(config.url.toString(), json.get("url").asString)
assertEquals(config.proxy!!.host, json.get("proxy").asJsonObject.get("host").asString)
assertEquals(config.proxy!!.port, json.get("proxy").asJsonObject.get("port").asInt)
assertEquals(config.proxy!!.type, json.get("proxy").asJsonObject.get("type").asString)
assertEquals(config.layout, json.get("layout").asString)
assertEquals(config.enableReleases, json.get("enableReleases").asBoolean)
assertEquals(config.enableSnapshots, json.get("enableSnapshots").asBoolean)
assertNull(json.get("authentication"))
}
@Test
fun `json deserializer`() {
@Language("JSON5")
val json = """
{
"id": "test",
"url": "http://localhost:8080/repository",
"proxy": {
"host": "localhost",
"port": 8080,
"type": "HTTP"
},
"layout": "legacy",
"enableReleases": false,
"enableSnapshots": true,
"authentication": {
"username": "testUser",
"password": "testPassword"
}
}
""".trimIndent()
val config = gson.fromJson(json, MavenRepositoryConfig::class.java)
assertEquals("test", config.id)
assertEquals(URL("http://localhost:8080/repository"), config.url)
assertEquals(
Proxy(
"HTTP",
"localhost",
8080
), config.proxy
)
assertEquals("legacy", config.layout)
assertEquals(false, config.enableReleases)
assertEquals(true, config.enableSnapshots)
assertNotNull(config.authentication)
val authContext = mockk<AuthenticationContext> {
every { put(ofType(String::class), any()) } answers { }
}
config.authentication!!.fill(authContext, null, emptyMap())
verify {
authContext.put(any(), "testUser")
authContext.put(any(), "testPassword".toCharArray())
}
}
}
internal class AppConfigTest {
private val gson = GsonBuilder()
.registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer)
.registerTypeAdapter(ProxyConfig::class.java, ProxyConfigSerializer)
.registerTypeAdapter(UsernameAuthenticator::class.java, UsernameAuthenticatorSerializer)
.registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer)
.create()
@Test
fun `json serializer - default value`() {
val config = AppConfig()
val json = gson.toJsonTree(config).asJsonObject
assertEquals(config.proxy.type.name, json.get("proxy").asJsonObject.get("type").asString)
assertEquals(config.proxy.host, json.get("proxy").asJsonObject.get("host").asString)
assertEquals(config.proxy.port, json.get("proxy").asJsonObject.get("port").asInt)
assertEquals(config.metrics.enable, json.get("metrics").asJsonObject.get("enable").asBoolean)
assertEquals(config.metrics.port, json.get("metrics").asJsonObject.get("port").asInt)
assertEquals(config.metrics.bindAddress, json.get("metrics").asJsonObject.get("bindAddress").asString)
assertNull(json["metrics"].asJsonObject.get("authenticator"))
assertTrue(json["mavenRepositories"].asJsonArray.isEmpty)
assertNull(json["mavenLocalRepository"])
}
@Test
fun `json serializer - Provide values`() {
val config = AppConfig(
proxy = ProxyConfig(
type = ProxyType.HTTP,
host = "localhost",
port = 8080
),
metrics = MetricsConfig(
enable = true,
port = 8800,
bindAddress = "127.0.0.1",
authenticator = UsernameAuthenticator(
username = "username",
password = "password"
)
),
mavenRepositories = listOf(
MavenRepositoryConfig(
url = URL("https://repository.maven.apache.org/maven2/")
)
),
mavenLocalRepository = "file:///tmp/maven-local-repository"
)
val json = gson.toJsonTree(config).asJsonObject
assertEquals(config.proxy.type.name, json.get("proxy").asJsonObject.get("type").asString)
assertEquals(config.proxy.host, json.get("proxy").asJsonObject.get("host").asString)
assertEquals(config.proxy.port, json.get("proxy").asJsonObject.get("port").asInt)
assertEquals(config.metrics.enable, json.get("metrics").asJsonObject.get("enable").asBoolean)
assertEquals(config.metrics.port, json.get("metrics").asJsonObject.get("port").asInt)
assertEquals(config.metrics.bindAddress, json.get("metrics").asJsonObject.get("bindAddress").asString)
assertNotNull(config.metrics.authenticator)
assertTrue(
config.metrics.authenticator!!.checkCredentials(
json.get("metrics").asJsonObject.get("authenticator").asJsonObject.get("username").asString,
json.get("metrics").asJsonObject.get("authenticator").asJsonObject.get("password").asString
)
)
assertEquals(1, json["mavenRepositories"].asJsonArray.size())
assertEquals(
config.mavenRepositories[0].url.toString(),
json["mavenRepositories"].asJsonArray[0].asJsonObject.get("url").asString
)
assertEquals(config.mavenLocalRepository, json["mavenLocalRepository"].asString)
}
@Test
fun `json deserializer - complete`() {
val json = """
{
"proxy": {
"type": "HTTP",
"host": "localhost",
"port": 8080
},
"metrics": {
"enable": true,
"port": 8800,
"bindAddress": "127.0.0.1",
"authenticator": {
"username": "username",
"password": "password"
}
},
"mavenRepositories": [
{
"url": "https://repository.maven.apache.org/maven2/"
}
],
"mavenLocalRepository": "file:///tmp/maven-local-repository"
}
""".trimIndent()
val config = gson.fromJson(json, AppConfig::class.java)
assertEquals(ProxyType.HTTP, config.proxy.type)
assertEquals("localhost", config.proxy.host)
assertEquals(8080, config.proxy.port)
assertEquals(true, config.metrics.enable)
assertEquals(8800, config.metrics.port)
assertEquals("127.0.0.1", config.metrics.bindAddress)
assertNotNull(config.metrics.authenticator)
assertTrue(config.metrics.authenticator!!.checkCredentials("username", "password"))
assertEquals(1, config.mavenRepositories.size)
assertEquals(URL("https://repository.maven.apache.org/maven2/"), config.mavenRepositories[0].url)
assertEquals("file:///tmp/maven-local-repository", config.mavenLocalRepository)
}
@Test
fun `json deserializer - default value`() {
val actualConfig = gson.fromJson("{}", AppConfig::class.java)
val expectConfig = AppConfig()
assertEquals(expectConfig, actualConfig)
}
}

View File

@ -0,0 +1,38 @@
package net.lamgc.scalabot.config
import kotlin.test.*
internal class UsernameAuthenticatorTest {
@Test
fun checkCredentialsTest() {
val authenticator = UsernameAuthenticator("testUser", "testPassword")
assertTrue(authenticator.checkCredentials("testUser", "testPassword"))
assertFalse(authenticator.checkCredentials("falseUser", "testPassword"))
assertFalse(authenticator.checkCredentials("testUser", "falsePassword"))
assertFalse(authenticator.checkCredentials("falseUser", "falsePassword"))
}
@Test
fun toJsonObjectTest() {
val authenticator = UsernameAuthenticator("testUser", "testPassword")
val jsonObject = authenticator.toJsonObject()
assertEquals("testUser", jsonObject["username"]?.asString)
assertEquals("testPassword", jsonObject["password"]?.asString)
}
@Test
fun equalsTest() {
val authenticator = UsernameAuthenticator("testUser", "testPassword")
assertEquals(authenticator, UsernameAuthenticator("testUser", "testPassword"))
assertEquals(authenticator.hashCode(), UsernameAuthenticator("testUser", "testPassword").hashCode())
assertNotEquals(authenticator, UsernameAuthenticator("testUser", "falsePassword"))
assertNotEquals(authenticator.hashCode(), UsernameAuthenticator("testUser", "falsePassword").hashCode())
assertNotEquals(authenticator, UsernameAuthenticator("falseUser", "testPassword"))
assertNotEquals(authenticator.hashCode(), UsernameAuthenticator("falseUser", "testPassword").hashCode())
assertNotEquals(authenticator, UsernameAuthenticator("falseUser", "falsePassword"))
assertNotEquals(authenticator.hashCode(), UsernameAuthenticator("falseUser", "falsePassword").hashCode())
assertFalse(authenticator.equals(null))
}
}

View File

@ -0,0 +1,852 @@
package net.lamgc.scalabot.config.serializer
import com.google.gson.*
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import net.lamgc.scalabot.config.*
import net.lamgc.scalabot.config.serializer.SerializeUtils.getPrimitiveValueOrThrow
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.artifact.DefaultArtifact
import org.eclipse.aether.repository.Authentication
import org.eclipse.aether.repository.AuthenticationContext
import org.eclipse.aether.repository.Proxy
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Assertions.assertThrows
import java.lang.reflect.Type
import java.net.URL
import kotlin.test.*
internal class SerializeUtilsTest {
@Test
fun `getPrimitiveValueOrThrow test`() {
assertThrows(JsonParseException::class.java) {
JsonObject().getPrimitiveValueOrThrow("NOT_EXIST_KEY")
}
assertThrows(JsonParseException::class.java) {
JsonObject().apply {
add("testKey", JsonArray())
}.getPrimitiveValueOrThrow("testKey")
}
assertThrows(JsonParseException::class.java) {
JsonObject().apply {
add("testKey", JsonObject())
}.getPrimitiveValueOrThrow("testKey")
}
assertThrows(JsonParseException::class.java) {
JsonObject().apply {
add("testKey", JsonNull.INSTANCE)
}.getPrimitiveValueOrThrow("testKey")
}
val expectKey = "STRING_KEY"
val expectValue = JsonPrimitive("A STRING")
assertEquals(expectValue, JsonObject()
.apply { add(expectKey, expectValue) }
.getPrimitiveValueOrThrow(expectKey))
}
}
internal class ProxyTypeSerializerTest {
@Test
fun `serialize test`() {
for (type in ProxyType.entries) {
assertEquals(
JsonPrimitive(type.name), ProxyTypeSerializer.serialize(type, null, null),
"ProxyType 序列化结果与预期不符."
)
}
}
@Test
fun `deserialize test`() {
assertThrows(JsonParseException::class.java) {
ProxyTypeSerializer.deserialize(JsonObject(), null, null)
}
assertThrows(JsonParseException::class.java) {
ProxyTypeSerializer.deserialize(JsonArray(), null, null)
}
assertThrows(JsonParseException::class.java) {
ProxyTypeSerializer.deserialize(JsonPrimitive("NOT_IN_ENUM_VALUE"), null, null)
}
assertEquals(
ProxyType.NO_PROXY,
ProxyTypeSerializer.deserialize(JsonNull.INSTANCE, null, null)
)
for (type in ProxyType.entries) {
assertEquals(
type, ProxyTypeSerializer.deserialize(JsonPrimitive(type.name), null, null),
"ProxyType 反序列化结果与预期不符."
)
assertEquals(
type, ProxyTypeSerializer.deserialize(JsonPrimitive(" ${type.name} "), null, null),
"ProxyType 反序列化时未对 Json 字符串进行修剪(trim)."
)
}
}
}
internal class MavenRepositoryConfigSerializerTest {
@Test
fun `unsupported json type deserialize test`() {
assertThrows(JsonParseException::class.java) {
MavenRepositoryConfigSerializer.deserialize(
JsonArray(),
MavenRepositoryConfig::class.java,
TestJsonSerializationContext.default()
)
}
assertThrows(JsonParseException::class.java) {
MavenRepositoryConfigSerializer.deserialize(
JsonNull.INSTANCE,
MavenRepositoryConfig::class.java,
TestJsonSerializationContext.default()
)
}
}
@Test
fun `json primitive deserialize test`() {
assertThrows(JsonParseException::class.java) {
MavenRepositoryConfigSerializer.deserialize(
JsonPrimitive("NOT A URL."),
MavenRepositoryConfig::class.java,
TestJsonSerializationContext.default()
)
}
val expectRepoUrl = "https://repo.example.org/maven"
val config = MavenRepositoryConfigSerializer.deserialize(
JsonPrimitive(expectRepoUrl),
MavenRepositoryConfig::class.java,
TestJsonSerializationContext.default()
)
assertNull(config.id)
assertEquals(URL(expectRepoUrl), config.url)
assertNull(config.proxy, "Proxy 默认值不为 null.")
assertEquals("default", config.layout)
assertTrue(config.enableReleases)
assertTrue(config.enableSnapshots)
assertNull(config.authentication)
}
@Test
fun `json object default deserialize test`() {
val expectRepoUrl = "https://repo.example.org/maven"
val jsonObject = JsonObject()
jsonObject.addProperty("url", expectRepoUrl)
val config = MavenRepositoryConfigSerializer.deserialize(
jsonObject,
MavenRepositoryConfig::class.java,
TestJsonSerializationContext.default()
)
assertNull(config.id)
assertEquals(URL(expectRepoUrl), config.url)
assertNull(config.proxy, "Proxy 默认值不为 null.")
assertEquals("default", config.layout)
assertTrue(config.enableReleases)
assertTrue(config.enableSnapshots)
assertNull(config.authentication)
}
@Test
fun `json object deserialize test`() {
@Language("JSON5")
val looksGoodJsonString = """
{
"id": "test-repository",
"url": "https://repo.example.org/maven",
"proxy": {
"type": "http",
"host": "127.0.1.1",
"port": 10800
},
"layout": "default",
"enableReleases": false,
"enableSnapshots": true
}
""".trimIndent()
val jsonObject = Gson().fromJson(looksGoodJsonString, JsonObject::class.java)
var config = MavenRepositoryConfigSerializer.deserialize(
jsonObject,
MavenRepositoryConfig::class.java,
TestJsonSerializationContext.default()
)
assertEquals(jsonObject["id"].asString, config.id)
assertEquals(URL(jsonObject["url"].asString), config.url)
assertEquals(Proxy("http", "127.0.1.1", 10800), config.proxy)
assertEquals(jsonObject["layout"].asString, config.layout)
assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases)
assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots)
// ------------------------------------
jsonObject.add("proxy", JsonNull.INSTANCE)
jsonObject.remove("layout")
config = MavenRepositoryConfigSerializer.deserialize(
jsonObject,
MavenRepositoryConfig::class.java,
TestJsonSerializationContext.default()
)
assertEquals(jsonObject["id"].asString, config.id)
assertEquals(URL(jsonObject["url"].asString), config.url)
assertNull(config.proxy)
assertEquals("default", config.layout)
assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases)
assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots)
// ------------------------------------
jsonObject.add("layout", mockk<JsonPrimitive> {
every { asString }.returns(null)
})
config = MavenRepositoryConfigSerializer.deserialize(
jsonObject,
MavenRepositoryConfig::class.java,
TestJsonSerializationContext.default()
)
assertEquals(jsonObject["id"].asString, config.id)
assertEquals(URL(jsonObject["url"].asString), config.url)
assertNull(config.proxy)
assertEquals("default", config.layout)
assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases)
assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots)
assertNull(config.authentication)
// ------------------------------------
jsonObject.add("authentication", JsonObject().apply {
addProperty("username", "testUsername")
addProperty("password", "testPassword")
})
config = MavenRepositoryConfigSerializer.deserialize(
jsonObject,
MavenRepositoryConfig::class.java,
TestJsonSerializationContext.default()
)
assertEquals(jsonObject["id"].asString, config.id)
assertEquals(URL(jsonObject["url"].asString), config.url)
assertNull(config.proxy)
assertEquals("default", config.layout)
assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases)
assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots)
assertNotNull(config.authentication)
}
}
private class TestJsonSerializationContext(private val gson: Gson) : JsonDeserializationContext,
JsonSerializationContext {
override fun <T : Any?> deserialize(json: JsonElement?, typeOfT: Type): T {
return gson.fromJson(json, typeOfT)
}
companion object {
fun default(): TestJsonSerializationContext {
return TestJsonSerializationContext(
GsonBuilder()
.registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer)
.registerTypeAdapter(BotConfig::class.java, BotConfigSerializer)
.registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer)
.registerTypeAdapter(Artifact::class.java, ArtifactSerializer)
.registerTypeAdapter(Authentication::class.java, AuthenticationSerializer)
.registerTypeAdapter(UsernameAuthenticator::class.java, UsernameAuthenticatorSerializer)
.registerTypeAdapter(ProxyConfig::class.java, ProxyConfigSerializer)
.create()
)
}
}
override fun serialize(src: Any?): JsonElement {
return gson.toJsonTree(src)
}
override fun serialize(src: Any?, typeOfSrc: Type?): JsonElement {
return gson.toJsonTree(src, typeOfSrc)
}
}
internal class AuthenticationSerializerTest {
@Test
fun `deserialize test`() {
assertThrows(JsonParseException::class.java) {
AuthenticationSerializer.deserialize(
JsonNull.INSTANCE,
Authentication::class.java, TestJsonSerializationContext.default()
)
}
assertThrows(JsonParseException::class.java) {
AuthenticationSerializer.deserialize(
JsonArray(),
Authentication::class.java, TestJsonSerializationContext.default()
)
}
assertThrows(JsonParseException::class.java) {
AuthenticationSerializer.deserialize(
JsonPrimitive("A STRING"),
Authentication::class.java, TestJsonSerializationContext.default()
)
}
val expectJsonObject = JsonObject().apply {
addProperty("username", "testUsername")
addProperty("password", "testPassword")
}
val mockContext = mockk<AuthenticationContext> {
every { put(any(), any()) }.answers { }
}
val result = AuthenticationSerializer.deserialize(
expectJsonObject,
Authentication::class.java, TestJsonSerializationContext.default()
)
assertNotNull(result)
result.fill(mockContext, "username", null)
result.fill(mockContext, "password", null)
verify {
mockContext.put("username", "testUsername")
mockContext.put("password", "testPassword".toCharArray())
}
}
}
internal class BotConfigSerializerTest {
private val gson = GsonBuilder()
.registerTypeAdapter(BotConfig::class.java, BotConfigSerializer)
.registerTypeAdapter(Artifact::class.java, ArtifactSerializer)
.registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer)
.registerTypeAdapter(ProxyConfig::class.java, ProxyConfigSerializer)
.create()
@Test
fun `serializer test`() {
// 检查 BotConfig 的序列化
val botConfig = BotConfig(
account = BotAccount(
name = "test-bot",
token = "test-token",
creatorId = 10000
)
)
// 使用 gson 序列化 botConfig, 并检查序列化结果
val jsonObject = gson.toJsonTree(botConfig) as JsonObject
assertEquals("test-bot", jsonObject["account"].asJsonObject["name"].asString)
assertEquals("test-token", jsonObject["account"].asJsonObject["token"].asString)
assertEquals(10000, jsonObject["account"].asJsonObject["creatorId"].asInt)
assertEquals(botConfig.enabled, jsonObject["enabled"].asBoolean)
assertEquals(botConfig.proxy.host, jsonObject["proxy"].asJsonObject["host"].asString)
assertEquals(botConfig.proxy.port, jsonObject["proxy"].asJsonObject["port"].asInt)
assertEquals(botConfig.proxy.type.name, jsonObject["proxy"].asJsonObject["type"].asString)
assertEquals(botConfig.disableBuiltInAbility, jsonObject["disableBuiltInAbility"].asBoolean)
assertEquals(botConfig.autoUpdateCommandList, jsonObject["autoUpdateCommandList"].asBoolean)
assertEquals(botConfig.extensions.isEmpty(), jsonObject["extensions"].asJsonArray.isEmpty)
assertEquals(botConfig.baseApiUrl, jsonObject["baseApiUrl"].asString)
}
@Test
fun `deserialize test`() {
assertThrows(JsonParseException::class.java) {
BotConfigSerializer.deserialize(
JsonNull.INSTANCE,
BotConfig::class.java, TestJsonSerializationContext(gson)
)
}
assertThrows(JsonParseException::class.java) {
BotConfigSerializer.deserialize(
JsonArray(),
BotConfig::class.java, TestJsonSerializationContext(gson)
)
}
assertThrows(JsonParseException::class.java) {
BotConfigSerializer.deserialize(
JsonPrimitive("A STRING"),
BotConfig::class.java, TestJsonSerializationContext(gson)
)
}
// 检查 BotConfig 的反序列化中是否能正确判断 account 的类型
assertThrows(JsonParseException::class.java) {
BotConfigSerializer.deserialize(
JsonObject().apply {
addProperty("account", "A STRING")
},
BotConfig::class.java, TestJsonSerializationContext(gson)
)
}
assertThrows(JsonParseException::class.java) {
BotConfigSerializer.deserialize(
JsonObject().apply {
add("account", JsonNull.INSTANCE)
},
BotConfig::class.java, TestJsonSerializationContext(gson)
)
}
assertThrows(JsonParseException::class.java) {
BotConfigSerializer.deserialize(
JsonObject().apply {
add("account", JsonArray())
},
BotConfig::class.java, TestJsonSerializationContext(gson)
)
}
assertThrows(JsonParseException::class.java) {
BotConfigSerializer.deserialize(
JsonObject(),
BotConfig::class.java, TestJsonSerializationContext(gson)
)
}
val expectBotAccount = BotAccount(
name = "test-bot",
token = "test-token",
creatorId = 10000
)
val expectDefaultBotConfig = BotConfig(account = expectBotAccount)
val minimumJsonObject = JsonObject().apply {
add("account", gson.toJsonTree(expectBotAccount))
}
val actualMinimumBotConfig = BotConfigSerializer.deserialize(
minimumJsonObject, BotConfig::class.java, TestJsonSerializationContext(gson)
)
assertNotNull(actualMinimumBotConfig)
assertEquals(expectDefaultBotConfig, actualMinimumBotConfig)
val expectDefaultProxy = ProxyConfig(
type = ProxyType.HTTP,
host = "https://example.com",
port = 443
)
// -------------------------------------------------
val jsonObject = JsonObject().apply {
add(
"account", gson.toJsonTree(
BotAccount(
name = "test-bot",
token = "test-token",
creatorId = 10000
)
)
)
addProperty("enabled", true)
add("proxy", gson.toJsonTree(expectDefaultProxy))
addProperty("disableBuiltInAbility", true)
addProperty("autoUpdateCommandList", true)
addProperty("baseApiUrl", "https://test.com")
add("extensions", JsonArray().apply {
add("org.example:test:1.0.0-SNAPSHOT")
})
}
val botConfig = BotConfigSerializer.deserialize(
jsonObject,
BotConfig::class.java, TestJsonSerializationContext(gson)
)
assertEquals("test-bot", botConfig.account.name)
assertEquals("test-token", botConfig.account.token)
assertEquals(10000, botConfig.account.creatorId)
assertEquals(true, botConfig.enabled)
assertEquals(expectDefaultProxy, botConfig.proxy)
assertEquals(true, botConfig.disableBuiltInAbility)
assertEquals(true, botConfig.autoUpdateCommandList)
assertEquals("https://test.com", botConfig.baseApiUrl)
assertEquals(false, botConfig.extensions.isEmpty())
assertEquals(1, botConfig.extensions.size)
}
}
internal class ProxyConfigSerializerTest {
// 测试 ProxyConfig 的 Json 序列化
@Test
fun `serialize test`() {
assertEquals(JsonNull.INSTANCE, ProxyConfigSerializer.serialize(null, null, null))
val expectDefaultConfig = ProxyConfig()
val actualDefaultJson = ProxyConfigSerializer.serialize(expectDefaultConfig, null, null)
assertTrue(actualDefaultJson is JsonObject)
assertEquals(expectDefaultConfig.type.name, actualDefaultJson["type"].asString)
assertEquals(expectDefaultConfig.host, actualDefaultJson["host"].asString)
assertEquals(expectDefaultConfig.port, actualDefaultJson["port"].asInt)
}
@Test
fun `Bad type deserialize test`() {
val defaultConfig = ProxyConfig()
assertEquals(defaultConfig, ProxyConfigSerializer.deserialize(null, null, null))
assertEquals(defaultConfig, ProxyConfigSerializer.deserialize(JsonNull.INSTANCE, null, null))
}
@Test
fun `deserialize test - object`() {
val defaultConfig = ProxyConfig()
assertThrows(JsonParseException::class.java) {
ProxyConfigSerializer.deserialize(JsonArray(), null, null)
}
val jsonWithoutType = JsonObject().apply {
addProperty("host", "example.com")
addProperty("port", 8080)
}
assertEquals(defaultConfig, ProxyConfigSerializer.deserialize(jsonWithoutType, null, null))
val looksGoodJson = JsonObject().apply {
addProperty("type", "HTTP")
addProperty("host", "example.com")
addProperty("port", 8080)
}
assertEquals(
ProxyConfig(
type = ProxyType.HTTP,
host = "example.com",
port = 8080
), ProxyConfigSerializer.deserialize(looksGoodJson, null, null)
)
assertThrows(JsonParseException::class.java) {
ProxyConfigSerializer.deserialize(JsonObject().apply {
addProperty("type", "UNKNOWN")
addProperty("host", "example.com")
addProperty("port", 8080)
}, null, null)
}
assertThrows(JsonParseException::class.java) {
ProxyConfigSerializer.deserialize(JsonObject().apply {
addProperty("type", "HTTP")
addProperty("host", "example.com")
}, null, null)
}
assertThrows(JsonParseException::class.java) {
ProxyConfigSerializer.deserialize(JsonObject().apply {
addProperty("type", "HTTP")
addProperty("port", 8080)
}, null, null)
}
}
}
internal class ArtifactSerializerTest {
@Test
fun badJsonType() {
assertFailsWith<JsonParseException> { ArtifactSerializer.deserialize(JsonObject(), null, null) }
assertFailsWith<JsonParseException> { ArtifactSerializer.deserialize(JsonArray(), null, null) }
assertFailsWith<JsonParseException> { ArtifactSerializer.deserialize(JsonPrimitive("A STRING"), null, null) }
}
@Test
fun `Basic format serialization`() {
val gav = "org.example.software:test:1.0.0-SNAPSHOT"
val expectArtifact = DefaultArtifact(gav)
val actualArtifact = DefaultArtifact(ArtifactSerializer.serialize(expectArtifact, null, null).asString)
assertEquals(expectArtifact, actualArtifact)
}
@Test
fun `Full format serialization`() {
val gav = "org.example.software:test:war:javadoc:1.0.0-SNAPSHOT"
val expectArtifact = DefaultArtifact(gav)
val actualArtifact = DefaultArtifact(ArtifactSerializer.serialize(expectArtifact, null, null).asString)
assertEquals(expectArtifact, actualArtifact)
}
@Test
fun `Bad format serialization`() {
assertFailsWith<JsonParseException> {
ArtifactSerializer.deserialize(JsonPrimitive("org.example~test"), null, null)
}
}
@Test
fun `Other artifact implementation serialization`() {
val gav = "org.example.software:test:war:javadoc:1.0.0-SNAPSHOT"
val expectArtifact = DefaultArtifact(gav)
val otherArtifactImpl = mockk<Artifact> {
every { groupId } returns expectArtifact.groupId
every { artifactId } returns expectArtifact.artifactId
every { version } returns expectArtifact.version
every { classifier } returns expectArtifact.classifier
every { extension } returns expectArtifact.extension
}
val json = ArtifactSerializer.serialize(otherArtifactImpl, null, null)
assertTrue(json is JsonPrimitive)
assertEquals(expectArtifact.toString(), json.asString)
}
@Test
fun deserialize() {
val gav = "org.example.software:test:war:javadoc:1.0.0-SNAPSHOT"
val expectArtifact = DefaultArtifact(gav)
val actualArtifact = ArtifactSerializer.deserialize(JsonPrimitive(gav), null, null)
assertEquals(expectArtifact, actualArtifact)
}
}
internal class UsernameAuthenticatorSerializerTest {
@Test
fun serializeTest() {
val authenticator = UsernameAuthenticator("testUser", "testPassword")
val jsonElement = UsernameAuthenticatorSerializer.serialize(authenticator, null, null)
assertTrue(jsonElement.isJsonObject)
val jsonObject = jsonElement.asJsonObject
assertEquals("testUser", jsonObject["username"]?.asString)
assertEquals("testPassword", jsonObject["password"]?.asString)
}
@Test
fun deserializeTest() {
org.junit.jupiter.api.assertThrows<JsonParseException> {
UsernameAuthenticatorSerializer.deserialize(JsonArray(), null, null)
}
org.junit.jupiter.api.assertThrows<JsonParseException> {
UsernameAuthenticatorSerializer.deserialize(JsonPrimitive(""), null, null)
}
assertNull(UsernameAuthenticatorSerializer.deserialize(JsonNull.INSTANCE, null, null))
org.junit.jupiter.api.assertThrows<JsonParseException> {
UsernameAuthenticatorSerializer.deserialize(JsonObject().apply {
addProperty("username", "testUser")
}, null, null)
}
org.junit.jupiter.api.assertThrows<JsonParseException> {
UsernameAuthenticatorSerializer.deserialize(JsonObject().apply {
addProperty("username", "testUser")
add("password", JsonArray())
}, null, null)
}
org.junit.jupiter.api.assertThrows<JsonParseException> {
UsernameAuthenticatorSerializer.deserialize(JsonObject().apply {
addProperty("password", "testPassword")
}, null, null)
}
org.junit.jupiter.api.assertThrows<JsonParseException> {
UsernameAuthenticatorSerializer.deserialize(JsonObject().apply {
add("username", JsonArray())
addProperty("password", "testPassword")
}, null, null)
}
org.junit.jupiter.api.assertThrows<JsonParseException> {
UsernameAuthenticatorSerializer.deserialize(JsonObject().apply {
addProperty("username", "")
addProperty("password", "")
}, null, null)
}
org.junit.jupiter.api.assertThrows<JsonParseException> {
UsernameAuthenticatorSerializer.deserialize(JsonObject().apply {
addProperty("username", "testUser")
addProperty("password", "")
}, null, null)
}
org.junit.jupiter.api.assertThrows<JsonParseException> {
UsernameAuthenticatorSerializer.deserialize(JsonObject().apply {
addProperty("username", "")
addProperty("password", "testPassword")
}, null, null)
}
val authenticator = UsernameAuthenticatorSerializer.deserialize(JsonObject().apply {
addProperty("username", "testUser")
addProperty("password", "testPassword")
}, null, null)
assertNotNull(authenticator)
assertTrue(authenticator.checkCredentials("testUser", "testPassword"))
assertFalse(authenticator.checkCredentials("falseUser", "testPassword"))
assertFalse(authenticator.checkCredentials("testUser", "falsePassword"))
}
}
internal class BotAccountSerializerTest {
private val expectToken = "123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10"
private val gson = GsonBuilder()
.registerTypeAdapter(BotAccount::class.java, BotAccountSerializer)
.create()
@Test
fun `Invalid json type check test`() {
assertThrows(JsonParseException::class.java) {
BotAccountSerializer.deserialize(null, null, null)
}
assertThrows(JsonParseException::class.java) {
BotAccountSerializer.deserialize(JsonNull.INSTANCE, null, null)
}
assertThrows(JsonParseException::class.java) {
BotAccountSerializer.deserialize(JsonPrimitive("A STRING"), null, null)
}
assertThrows(JsonParseException::class.java) {
BotAccountSerializer.deserialize(JsonArray(), null, null)
}
}
@Test
fun `Field missing test`() {
assertThrows(JsonParseException::class.java) {
BotAccountSerializer.deserialize(JsonObject(), null, null)
}
assertThrows(JsonParseException::class.java) {
BotAccountSerializer.deserialize(JsonObject().apply {
addProperty("token", expectToken)
addProperty("creatorId", 1)
}, null, null)
}
assertThrows(JsonParseException::class.java) {
BotAccountSerializer.deserialize(JsonObject().apply {
addProperty("name", "testUser")
addProperty("creatorId", 1)
}, null, null)
}
assertThrows(JsonParseException::class.java) {
BotAccountSerializer.deserialize(JsonObject().apply {
addProperty("name", "testUser")
addProperty("token", expectToken)
}, null, null)
}
val account = BotAccountSerializer.deserialize(JsonObject().apply {
addProperty("name", "testUser")
addProperty("token", expectToken)
addProperty("creatorId", 1)
}, null, null)
assertNotNull(account)
assertEquals("testUser", account.name)
assertEquals(expectToken, account.token)
assertEquals(1, account.creatorId)
}
@Test
fun `'token' check test`() {
val jsonObject = JsonObject().apply {
addProperty("name", "testUser")
addProperty("token", expectToken)
addProperty("creatorId", 123456789123456789)
}
val looksGoodAccount = BotAccountSerializer.deserialize(jsonObject, null, null)
assertNotNull(looksGoodAccount)
assertEquals("testUser", looksGoodAccount.name)
assertEquals(expectToken, looksGoodAccount.token)
assertEquals(123456789123456789, looksGoodAccount.creatorId)
try {
BotAccountSerializer.deserialize(jsonObject.deepCopy().apply {
addProperty("token", "")
}, null, null)
fail("Token 为空,但是没有抛出异常。")
} catch (e: JsonParseException) {
assertEquals("`token` cannot be empty.", e.message)
}
try {
BotAccountSerializer.deserialize(jsonObject.deepCopy().apply {
addProperty("token", "abcdefghijklmnopqrstuvwxyz")
}, null, null)
fail("Token 格式错误(基本格式错误),但是没有抛出异常。")
} catch (e: JsonParseException) {
assertEquals("`token` is invalid.", e.message)
}
try {
BotAccountSerializer.deserialize(jsonObject.deepCopy().apply {
addProperty("token", "abcdefgh:ijklmnopqrstuvwxyz-1234567890_abcde")
}, null, null)
fail("Token 格式错误ID 不为数字),但是没有抛出异常。")
} catch (e: JsonParseException) {
assertEquals("`token` is invalid.", e.message)
}
try {
BotAccountSerializer.deserialize(jsonObject.deepCopy().apply {
addProperty("token", "0123456789:ijklmnopqrstu-vwxyz_123456")
}, null, null)
fail("Token 格式错误(授权令牌长度错误),但是没有抛出异常。")
} catch (e: JsonParseException) {
assertEquals("`token` is invalid.", e.message)
}
}
@Test
fun `'creatorId' check test`() {
val jsonObject = JsonObject().apply {
addProperty("name", "testUser")
addProperty("token", expectToken)
addProperty("creatorId", 1)
}
val looksGoodAccount = gson.fromJson(jsonObject, BotAccount::class.java)
assertEquals(1, looksGoodAccount.creatorId)
try {
BotAccountSerializer.deserialize(jsonObject.deepCopy().apply {
addProperty("creatorId", "A STRING")
}, null, null)
fail("creatorId 不是一个数字,但是没有抛出异常。")
} catch (e: JsonParseException) {
assertEquals("`creatorId` must be a number.", e.message)
}
try {
BotAccountSerializer.deserialize(jsonObject.deepCopy().apply {
addProperty("creatorId", -1)
}, null, null)
fail("creatorId 不能为负数,但是没有抛出异常。")
} catch (e: JsonParseException) {
assertEquals("`creatorId` must be a positive number.", e.message)
}
}
@Test
fun `json deserialize test`() {
val jsonObject = JsonObject().apply {
addProperty("name", "testUser")
addProperty("token", expectToken)
addProperty("creatorId", 1)
}
val looksGoodAccount = gson.fromJson(jsonObject, BotAccount::class.java)
assertNotNull(looksGoodAccount)
assertEquals("testUser", looksGoodAccount.name)
assertEquals(expectToken, looksGoodAccount.token)
assertEquals(1, looksGoodAccount.creatorId)
}
}

View File

@ -4,3 +4,4 @@ rootProject.name = "scalabot"
include(":scalabot-app") include(":scalabot-app")
include(":scalabot-extension") include(":scalabot-extension")
include("scalabot-ext-example") include("scalabot-ext-example")
include("scalabot-meta")