From f9966d6c8c555b7b8f81a12dbe507bb82bab7e49 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Tue, 15 Oct 2024 13:54:49 +0800 Subject: [PATCH 01/28] Update Kotlin from 2.0.10 to 2.0.20 --- gradle/libs.versions.toml | 6 +- kotlin-js-store/yarn.lock | 191 ++++++++---------- .../build.gradle.kts | 13 +- .../api/simbot-component-qq-guild-core.api | 14 +- .../build.gradle.kts | 17 +- .../simbot/component/qguild/message/QGArk.kt | 1 + .../component/qguild/message/QGEmbed.kt | 1 + .../component/qguild/message/QGMarkdown.kt | 1 + .../build.gradle.kts | 8 +- 9 files changed, 101 insertions(+), 151 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b68fd2db..93cb7531 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.0.10" +kotlin = "2.0.20" kotlinx-coroutines = "1.9.0" kotlinx-serialization = "1.7.1" kotlinx-datetime = "0.6.1" @@ -9,10 +9,10 @@ openjdk-jmh = "1.37" log4j = "2.23.1" # simbot simbot = "4.6.0" -suspendTransform = "0.9.0" +suspendTransform = "2.0.20-0.9.3" gradleCommon = "0.6.0" # ksp -ksp = "2.0.10-1.0.24" +ksp = "2.0.20-1.0.25" # https://square.github.io/kotlinpoet/ kotlinPoet = "1.18.1" # https://detekt.dev/docs/intro diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 68db82cd..57605d0a 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -14,10 +14,10 @@ abort-controller@3.0.0: dependencies: event-target-shim "^5.0.0" -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://mirrors.cloud.tencent.com/npm/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-regex@^5.0.1: version "5.0.1" @@ -68,9 +68,9 @@ braces@~3.0.2: dependencies: fill-range "^7.0.1" -browser-stdout@1.3.1: +browser-stdout@^1.3.1: version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + resolved "https://mirrors.cloud.tencent.com/npm/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== buffer-from@^1.0.0: @@ -91,10 +91,10 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== +chokidar@^3.5.3: + version "3.6.0" + resolved "https://mirrors.cloud.tencent.com/npm/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -127,22 +127,22 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -debug@4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== +debug@^4.3.5: + version "4.3.7" + resolved "https://mirrors.cloud.tencent.com/npm/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: - ms "2.1.2" + ms "^2.1.3" decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +diff@^5.2.0: + version "5.2.0" + resolved "https://mirrors.cloud.tencent.com/npm/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== emoji-regex@^8.0.0: version "8.0.0" @@ -154,9 +154,9 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-string-regexp@4.0.0: +escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://mirrors.cloud.tencent.com/npm/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== event-target-shim@^5.0.0: @@ -171,9 +171,9 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-up@5.0.0: +find-up@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://mirrors.cloud.tencent.com/npm/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" @@ -211,9 +211,9 @@ glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob@8.1.0: +glob@^8.1.0: version "8.1.0" - resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + resolved "https://mirrors.cloud.tencent.com/npm/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== dependencies: fs.realpath "^1.0.0" @@ -227,9 +227,9 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -he@1.2.0: +he@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + resolved "https://mirrors.cloud.tencent.com/npm/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== inflight@^1.0.4: @@ -284,9 +284,9 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -js-yaml@4.1.0: +js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://mirrors.cloud.tencent.com/npm/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" @@ -298,62 +298,50 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -log-symbols@4.1.0: +log-symbols@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + resolved "https://mirrors.cloud.tencent.com/npm/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: chalk "^4.1.0" is-unicode-supported "^0.1.0" -minimatch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^5.0.1: +minimatch@^5.0.1, minimatch@^5.1.6: version "5.1.6" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" -mocha@10.3.0: - version "10.3.0" - resolved "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz#0e185c49e6dccf582035c05fa91084a4ff6e3fe9" - integrity sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg== - dependencies: - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.4" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "8.1.0" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "5.0.1" - ms "2.1.3" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - workerpool "6.2.1" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: +mocha@10.7.0: + version "10.7.0" + resolved "https://mirrors.cloud.tencent.com/npm/mocha/-/mocha-10.7.0.tgz#9e5cbed8fa9b37537a25bd1f7fb4f6fc45458b9a" + integrity sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA== + dependencies: + ansi-colors "^4.1.3" + browser-stdout "^1.3.1" + chokidar "^3.5.3" + debug "^4.3.5" + diff "^5.2.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^8.1.0" + he "^1.2.0" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^5.1.6" + ms "^2.1.3" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^6.5.1" + yargs "^16.2.0" + yargs-parser "^20.2.9" + yargs-unparser "^2.0.0" + +ms@^2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://mirrors.cloud.tencent.com/npm/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== node-fetch@2.6.7: @@ -423,10 +411,10 @@ safe-buffer@^5.1.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -serialize-javascript@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://mirrors.cloud.tencent.com/npm/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" @@ -459,18 +447,11 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-json-comments@3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://mirrors.cloud.tencent.com/npm/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -478,6 +459,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.1.1: + version "8.1.1" + resolved "https://mirrors.cloud.tencent.com/npm/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -490,10 +478,10 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -typescript@5.4.3: - version "5.4.3" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" - integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +typescript@5.5.4: + version "5.5.4" + resolved "https://mirrors.cloud.tencent.com/npm/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== webidl-conversions@^3.0.0: version "3.0.1" @@ -508,10 +496,10 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -workerpool@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" - integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +workerpool@^6.5.1: + version "6.5.1" + resolved "https://mirrors.cloud.tencent.com/npm/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== wrap-ansi@^7.0.0: version "7.0.0" @@ -537,19 +525,14 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: +yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + resolved "https://mirrors.cloud.tencent.com/npm/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-unparser@2.0.0: +yargs-unparser@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + resolved "https://mirrors.cloud.tencent.com/npm/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== dependencies: camelcase "^6.0.0" @@ -557,9 +540,9 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@16.2.0: +yargs@^16.2.0: version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + resolved "https://mirrors.cloud.tencent.com/npm/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: cliui "^7.0.2" diff --git a/simbot-component-qq-guild-api/build.gradle.kts b/simbot-component-qq-guild-api/build.gradle.kts index 08407864..24423f72 100644 --- a/simbot-component-qq-guild-api/build.gradle.kts +++ b/simbot-component-qq-guild-api/build.gradle.kts @@ -75,7 +75,7 @@ kotlin { api(libs.simbot.common.apidefinition) api(libs.simbot.common.suspend) api(libs.simbot.common.core) - compileOnly(libs.simbot.common.annotations) + implementation(libs.simbot.common.annotations) api(libs.ktor.client.core) api(libs.ktor.client.contentNegotiation) @@ -90,26 +90,15 @@ kotlin { implementation(libs.ktor.client.mock) } - jvmMain.dependencies { -// compileOnly(libs.simbot.api) // use @Api4J annotation - } - jvmTest.dependencies { implementation(libs.ktor.client.cio) implementation(libs.log4j.api) implementation(libs.log4j.core) implementation(libs.log4j.slf4j2) -// implementation(libs.kotlinx.coroutines.reactor) -// implementation(libs.reactor.core) } jsMain.dependencies { api(libs.ktor.client.js) - implementation(libs.simbot.common.annotations) - } - - nativeMain.dependencies { - implementation(libs.simbot.common.annotations) } mingwTest.dependencies { diff --git a/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api b/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api index 67bb20df..623670b9 100644 --- a/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api +++ b/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api @@ -1522,11 +1522,11 @@ public abstract interface class love/forte/simbot/component/qguild/guild/QGMembe public fun getName ()Ljava/lang/String; public fun getNick ()Ljava/lang/String; public fun getRoleIds ()Ljava/util/List; - public abstract fun mute (JLjava/util/concurrent/TimeUnit;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract synthetic fun mute (JLjava/util/concurrent/TimeUnit;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract synthetic fun mute-VtjQ1oo (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun muteAsync (JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/CompletableFuture; - public abstract fun muteBlocking (JLjava/util/concurrent/TimeUnit;)V - public abstract fun muteReserve (JLjava/util/concurrent/TimeUnit;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; + public fun muteAsync (JLjava/util/concurrent/TimeUnit;)Ljava/util/concurrent/CompletableFuture; + public fun muteBlocking (JLjava/util/concurrent/TimeUnit;)V + public fun muteReserve (JLjava/util/concurrent/TimeUnit;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; public abstract synthetic fun send (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract synthetic fun send (Llove/forte/simbot/message/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract synthetic fun send (Llove/forte/simbot/message/MessageContent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -1612,8 +1612,6 @@ public final class love/forte/simbot/component/qguild/message/QGArk : love/forte public static final fun byArk (Llove/forte/simbot/qguild/model/Message$Ark;)Llove/forte/simbot/component/qguild/message/QGArk; public final fun component1 ()Llove/forte/simbot/common/id/ID; public final fun component2 ()Ljava/util/List; - public final fun copy (Llove/forte/simbot/common/id/ID;Ljava/util/List;)Llove/forte/simbot/component/qguild/message/QGArk; - public static synthetic fun copy$default (Llove/forte/simbot/component/qguild/message/QGArk;Llove/forte/simbot/common/id/ID;Ljava/util/List;ILjava/lang/Object;)Llove/forte/simbot/component/qguild/message/QGArk; public static final fun create (Llove/forte/simbot/common/id/ID;Ljava/util/List;)Llove/forte/simbot/component/qguild/message/QGArk; public fun equals (Ljava/lang/Object;)Z public final fun getKvs ()Ljava/util/List; @@ -1766,8 +1764,6 @@ public final class love/forte/simbot/component/qguild/message/QGEmbed : love/for public static final field Companion Llove/forte/simbot/component/qguild/message/QGEmbed$Companion; public static final fun byEmbed (Llove/forte/simbot/qguild/model/Message$Embed;)Llove/forte/simbot/component/qguild/message/QGEmbed; public final fun component1 ()Llove/forte/simbot/qguild/model/Message$Embed; - public final fun copy (Llove/forte/simbot/qguild/model/Message$Embed;)Llove/forte/simbot/component/qguild/message/QGEmbed; - public static synthetic fun copy$default (Llove/forte/simbot/component/qguild/message/QGEmbed;Llove/forte/simbot/qguild/model/Message$Embed;ILjava/lang/Object;)Llove/forte/simbot/component/qguild/message/QGEmbed; public fun equals (Ljava/lang/Object;)Z public final fun getEmbed ()Llove/forte/simbot/qguild/model/Message$Embed; public fun hashCode ()I @@ -1806,8 +1802,6 @@ public final class love/forte/simbot/component/qguild/message/QGMarkdown : love/ public static final field Companion Llove/forte/simbot/component/qguild/message/QGMarkdown$Companion; public static final fun byMarkdown (Llove/forte/simbot/qguild/model/Message$Markdown;)Llove/forte/simbot/component/qguild/message/QGMarkdown; public final fun component1 ()Llove/forte/simbot/qguild/model/Message$Markdown; - public final fun copy (Llove/forte/simbot/qguild/model/Message$Markdown;)Llove/forte/simbot/component/qguild/message/QGMarkdown; - public static synthetic fun copy$default (Llove/forte/simbot/component/qguild/message/QGMarkdown;Llove/forte/simbot/qguild/model/Message$Markdown;ILjava/lang/Object;)Llove/forte/simbot/component/qguild/message/QGMarkdown; public static final fun create (Ljava/lang/String;)Llove/forte/simbot/component/qguild/message/QGMarkdown; public static final fun createByCustomTemplateId (Ljava/lang/String;)Llove/forte/simbot/component/qguild/message/QGMarkdown; public static final fun createByCustomTemplateId (Ljava/lang/String;Llove/forte/simbot/qguild/model/Message$Markdown$Params;)Llove/forte/simbot/component/qguild/message/QGMarkdown; diff --git a/simbot-component-qq-guild-core/build.gradle.kts b/simbot-component-qq-guild-core/build.gradle.kts index aa34e1e9..5c9e7688 100644 --- a/simbot-component-qq-guild-core/build.gradle.kts +++ b/simbot-component-qq-guild-core/build.gradle.kts @@ -57,9 +57,9 @@ kotlin { sourceSets { commonMain.dependencies { - compileOnly(libs.simbot.api) api(project(":simbot-component-qq-guild-stdlib")) - compileOnly(libs.simbot.common.annotations) + implementation(libs.simbot.api) + implementation(libs.simbot.common.annotations) // ktor api(libs.ktor.client.contentNegotiation) api(libs.ktor.serialization.kotlinxJson) @@ -78,26 +78,13 @@ kotlin { } jvmTest.dependencies { -// implementation(libs.ktor.client.cio) implementation(libs.ktor.client.java) -// runtimeOnly(libs.kotlinx.coroutines.reactor) -// implementation(libs.reactor.core) implementation(libs.log4j.api) implementation(libs.log4j.core) implementation(libs.log4j.slf4j2) } - jsMain.dependencies { - implementation(libs.simbot.api) - api(libs.simbot.common.annotations) - } - - nativeMain.dependencies { - implementation(libs.simbot.api) - api(libs.simbot.common.annotations) - } - mingwTest.dependencies { implementation(libs.ktor.client.winhttp) } diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGArk.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGArk.kt index abe1b205..b357ee40 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGArk.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGArk.kt @@ -37,6 +37,7 @@ import love.forte.simbot.message.Message as SimbotMessage */ @SerialName("qg.ark") @Serializable +@ConsistentCopyVisibility public data class QGArk internal constructor( @SerialName("template_id") public val templateId: ID, diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGEmbed.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGEmbed.kt index d84f8030..8e7e75d3 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGEmbed.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGEmbed.kt @@ -47,6 +47,7 @@ import kotlin.jvm.JvmStatic */ @SerialName("qg.embed") @Serializable +@ConsistentCopyVisibility public data class QGEmbed internal constructor(public val embed: Message.Embed) : QGMessageElement { public companion object { diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGMarkdown.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGMarkdown.kt index 3a18f19c..9cd79b7e 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGMarkdown.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/message/QGMarkdown.kt @@ -32,6 +32,7 @@ import kotlin.jvm.JvmStatic * @author ForteScarlet */ @Serializable +@ConsistentCopyVisibility public data class QGMarkdown internal constructor( public val markdown: Message.Markdown ) : QGMessageElement { diff --git a/simbot-component-qq-guild-stdlib/build.gradle.kts b/simbot-component-qq-guild-stdlib/build.gradle.kts index cfd790c6..2e407b72 100644 --- a/simbot-component-qq-guild-stdlib/build.gradle.kts +++ b/simbot-component-qq-guild-stdlib/build.gradle.kts @@ -61,7 +61,7 @@ kotlin { api(libs.simbot.common.loop) api(libs.simbot.common.atomic) api(libs.simbot.common.core) - compileOnly(libs.simbot.common.annotations) + implementation(libs.simbot.common.annotations) // ktor api(libs.ktor.client.contentNegotiation) api(libs.ktor.serialization.kotlinxJson) @@ -75,7 +75,6 @@ kotlin { } jvmTest.dependencies { -// implementation(libs.ktor.client.cio) implementation(libs.ktor.client.java) implementation(libs.log4j.api) implementation(libs.log4j.core) @@ -83,14 +82,9 @@ kotlin { } jsMain.dependencies { - api(libs.simbot.common.annotations) api(libs.ktor.client.js) } - nativeMain.dependencies { - api(libs.simbot.common.annotations) - } - mingwTest.dependencies { implementation(libs.ktor.client.winhttp) } From b14f67d12c1ce95f5fb565f73fbfe2d362cac28d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 05:58:27 +0000 Subject: [PATCH 02/28] build(deps): bump log4j from 2.23.1 to 2.24.1 Bumps `log4j` from 2.23.1 to 2.24.1. Updates `org.apache.logging.log4j:log4j-api` from 2.23.1 to 2.24.1 Updates `org.apache.logging.log4j:log4j-core` from 2.23.1 to 2.24.1 Updates `org.apache.logging.log4j:log4j-slf4j2-impl` from 2.23.1 to 2.24.1 --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-api dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.apache.logging.log4j:log4j-core dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.apache.logging.log4j:log4j-slf4j2-impl dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b68fd2db..7cb25f5b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ kotlinx-datetime = "0.6.1" dokka = "1.9.20" ktor = "2.3.12" openjdk-jmh = "1.37" -log4j = "2.23.1" +log4j = "2.24.1" # simbot simbot = "4.6.0" suspendTransform = "0.9.0" From ae19c8cc30f26a714983fd8a8d6583cbf38cd9de Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Tue, 15 Oct 2024 14:09:08 +0800 Subject: [PATCH 03/28] Update Kotlinx serialization from 1.7.1 to 1.7.3 --- buildSrc/src/main/kotlin/P.kt | 2 +- gradle/libs.versions.toml | 2 +- .../love/forte/simbot/qguild/api/message/MessageSendApi.kt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/P.kt b/buildSrc/src/main/kotlin/P.kt index 1fef4329..8670c734 100644 --- a/buildSrc/src/main/kotlin/P.kt +++ b/buildSrc/src/main/kotlin/P.kt @@ -57,7 +57,7 @@ object P { const val VERSION = "4.0.2" - const val NEXT_VERSION = "4.0.3" + const val NEXT_VERSION = "4.1.0" override val snapshotVersion = "$NEXT_VERSION-SNAPSHOT" override val version = if (isSnapshot()) snapshotVersion else VERSION diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 93cb7531..4949909b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.0.20" kotlinx-coroutines = "1.9.0" -kotlinx-serialization = "1.7.1" +kotlinx-serialization = "1.7.3" kotlinx-datetime = "0.6.1" dokka = "1.9.20" ktor = "2.3.12" diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/message/MessageSendApi.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/message/MessageSendApi.kt index c78549c6..44134188 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/message/MessageSendApi.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/api/message/MessageSendApi.kt @@ -171,6 +171,7 @@ public class MessageSendApi private constructor( * */ @Serializable + @ConsistentCopyVisibility public data class Body internal constructor( /** * 选填,消息内容,文本内容,支持[内嵌格式](https://bot.q.qq.com/wiki/develop/api/openapi/message/message_format.html) From fbf4c3f791ede3d66389bc7856f0bfbdb64f69ff Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Tue, 15 Oct 2024 14:15:25 +0800 Subject: [PATCH 04/28] Apply apiDump --- .../api/simbot-component-qq-guild-api.api | 2 -- 1 file changed, 2 deletions(-) diff --git a/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api b/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api index abef67c5..f8b6bb27 100644 --- a/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api +++ b/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api @@ -1592,8 +1592,6 @@ public final class love/forte/simbot/qguild/api/message/MessageSendApi$Body { public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; public final fun component8 ()Llove/forte/simbot/qguild/model/Message$Markdown; - public final fun copy (Ljava/lang/String;Llove/forte/simbot/qguild/model/Message$Embed;Llove/forte/simbot/qguild/model/Message$Ark;Llove/forte/simbot/qguild/model/Message$Reference;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Llove/forte/simbot/qguild/model/Message$Markdown;)Llove/forte/simbot/qguild/api/message/MessageSendApi$Body; - public static synthetic fun copy$default (Llove/forte/simbot/qguild/api/message/MessageSendApi$Body;Ljava/lang/String;Llove/forte/simbot/qguild/model/Message$Embed;Llove/forte/simbot/qguild/model/Message$Ark;Llove/forte/simbot/qguild/model/Message$Reference;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Llove/forte/simbot/qguild/model/Message$Markdown;ILjava/lang/Object;)Llove/forte/simbot/qguild/api/message/MessageSendApi$Body; public fun equals (Ljava/lang/Object;)Z public final fun getArk ()Llove/forte/simbot/qguild/model/Message$Ark; public final fun getContent ()Ljava/lang/String; From c088fdc5357eefbf7714862b7866d36443eff8ec Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Wed, 16 Oct 2024 14:41:09 +0800 Subject: [PATCH 05/28] Support for webhook --- .../love/forte/simbot/qguild/event/Opcode.kt | 29 ++- .../love/forte/simbot/qguild/event/Signal.kt | 49 +++++ .../love/forte/simbot/qguild/stdlib/Bot.kt | 91 ++++++++- .../simbot/qguild/stdlib/BotConfiguration.kt | 28 +-- .../stdlib/ConfigurableBotConfiguration.kt | 4 + .../simbot/qguild/stdlib/internal/BotImpl.kt | 176 +++++++++++++++--- .../qguild/stdlib/internal/BotStates.kt | 111 ++++------- 7 files changed, 353 insertions(+), 135 deletions(-) diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Opcode.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Opcode.kt index 02d9acec..c5b0854f 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Opcode.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Opcode.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023. ForteScarlet. + * Copyright (c) 2022-2024. ForteScarlet. * * This file is part of simbot-component-qq-guild. * @@ -17,9 +17,13 @@ package love.forte.simbot.qguild.event -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder /** @@ -40,6 +44,7 @@ public sealed interface SendAble * [`opcode`](https://bot.q.qq.com/wiki/develop/api/gateway/opcode.html) 常量类。 * */ +@Suppress("ConstPropertyName") public object Opcodes { /** 服务端进行消息推送 */ public const val Dispatch: Int = 0 @@ -63,6 +68,13 @@ public object Opcodes { /** 当发送心跳成功之后,就会收到该消息 */ public const val HeartbeatACK: Int = 11 + + /** + * 回调地址验证。开放平台对机器人服务端进行验证 + * + * 参考 [官方webhook文档](https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/event-emit.html) + */ + public const val CallbackVerify: Int = 13 } /** @@ -101,6 +113,14 @@ public sealed class Opcode(public val code: Int) { // 11 public object HeartbeatACK : Opcode(Opcodes.HeartbeatACK), ReceiveAble + /** + * Callback地址验证 + * + * 参考 [官方webhook文档](https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/event-emit.html) + * + * CODE: 13 + */ + public object CallbackVerify : Opcode(Opcodes.CallbackVerify), ReceiveAble public object SerializerByCode : KSerializer { override fun deserialize(decoder: Decoder): Opcode { @@ -113,6 +133,7 @@ public sealed class Opcode(public val code: Int) { Opcodes.InvalidSession -> InvalidSession Opcodes.Hello -> Hello Opcodes.HeartbeatACK -> HeartbeatACK + Opcodes.CallbackVerify -> CallbackVerify else -> throw NoSuchElementException("opcode: $code") } } diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt index b2922a1c..4d86a60f 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt @@ -221,6 +221,50 @@ public sealed class Signal(@Serializable(Opcode.SerializerByCode::class) publ ) : Dispatch() } + + /** + * 回调地址验证。开放平台对机器人服务端进行验证 + * + * @see Opcode.CallbackVerify + */ + @Serializable + public data class CallbackVerify( + @SerialName("d") override val data: Data + ) : Signal(Opcode.CallbackVerify) { + + + /** + * 请求结构(`Payload.d`) + * + * | 字段 | 描述 | + * | --- | --- | + * | plain_token | 需要计算签名的字符串 | + * | event_ts | 计算签名使用时间戳 | + */ + @Serializable + public data class Data( + @SerialName("plain_token") + val plainToken: String, + @SerialName("event_ts") + val eventTs: String + ) + + /** + * 回调地址验证返回结果 + * + * | 字段 | 描述 | + * | --- | --- | + * | plain_token | 需要计算签名的字符串 | + * | signature | 签名 | + * + */ + @Serializable + public data class Verified( + @SerialName("plain_token") + val plainToken: String, + val signature: String, + ) + } } @@ -230,6 +274,11 @@ public sealed class Signal(@Serializable(Opcode.SerializerByCode::class) publ */ public fun JsonElement.getOpcode(): Int? = jsonObject["op"]?.jsonPrimitive?.int +@QGInternalApi +public fun JsonElement.tryGetId(): String? = kotlin.runCatching { + jsonObject["id"]?.jsonPrimitive?.contentOrNull +}.getOrNull() + /** * [Shared](https://bot.q.qq.com/wiki/develop/api/gateway/shard.html) * diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt index 6b3ebecf..92e738a8 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt @@ -30,6 +30,7 @@ import love.forte.simbot.qguild.model.User import love.forte.simbot.suspendrunner.ST import love.forte.simbot.suspendrunner.STP import kotlin.coroutines.CoroutineContext +import kotlin.jvm.JvmStatic /** * 一个 QQ频道Bot。 @@ -165,6 +166,35 @@ public interface Bot : CoroutineScope { @ST public suspend fun start(gateway: GatewayInfo) + /** + * 主动推送一个事件原文。 + * 可用于在 webhook 模式下推送事件。 + * + * @param payload 接收到的事件推送的JSON格式正文字符串。 + * @param options 额外提供的属性或配置。默认为 `null`。 + * + * @throws IllegalArgumentException 参考: + * - [EmitEventOptions.ignoreUnknownOpcode] + * - [EmitEventOptions.ignoreMissingOpcode] + * + * @since 4.1.0 + */ + @ST + public suspend fun emitEvent(payload: String, options: EmitEventOptions? = null) + + /** + * 主动推送一个事件原文。 + * 可用于在 webhook 模式下推送事件。 + * + * @param payload 接收到的事件推送的JSON格式正文字符串。 + * + * @since 4.1.0 + */ + @ST + public suspend fun emitEvent(payload: String) { + emitEvent(payload, null) + } + /** * 终止当前BOT。 */ @@ -195,7 +225,8 @@ public interface Bot : CoroutineScope { * 如果当前处于重连、重启的状态,得到的 [client] 中 [client.isActive][Client.isActive] 可能为 `false`, * [client] 本身也可能不存在。 * - * @return 当前bot持有的连接。如果当前正处于连接中、重连中或尚未启动,则可能得到null + * @return 当前bot持有的连接。如果当前正处于连接中、重连中、尚未启动或未启用ws连接, + * 则可能得到null */ public val client: Client? @@ -244,3 +275,61 @@ public enum class SubscribeSequence { */ NORMAL; } + +/** + * 使用 [Bot.emitEvent] 推送一个外部事件,并且在 [block] 中配置 [EmitEventOptions]。 + * @see Bot.emitEvent + * @since 4.1.0 + */ +public suspend inline fun Bot.emitEvent(payload: String, block: EmitEventOptions.() -> Unit) { + emitEvent(payload, EmitEventOptions().apply(block)) +} + +/** + * 在使用 [Bot.emitEvent] 时可选的一些额外属性或选项信息。 + * @since 4.1.0 + */ +public class EmitEventOptions { + /** + * 如果为 `true`, + * 则 payload 中解析出 [0, 13] 以外的 `op` 值不会抛出异常。 + * 默认为 `false` + */ + public var ignoreUnknownOpcode: Boolean = false + + /** + * 如果为 `true`, + * 则 payload 中不存在 `op` 值时不会抛出异常。 + * 默认为 `false` + */ + public var ignoreMissingOpcode: Boolean = false + + /** + * 如果需要对此回调事件进行签名校验,则通过 [signatureValue] 配置校验所需的、来自请求头透传的值。 + * + * - `X-Signature-Ed25519` + * - `X-Signature-Timestamp` + * + * 更多参考 [官方文档](https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html) + * + * [signatureValue] 默认为 `null`, 即不进行校验。 + */ + public var signatureValue: SignatureValue? = null +} + +/** + * 回调校验用的参数 + * 更多参考 [官方文档](https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html) + * + * @since 4.1.0 + */ +public class SignatureValue private constructor( + public val ed25519: String, + public val timestamp: String +) { + public companion object { + @JvmStatic + public fun create(ed25519: String, timestamp: String): SignatureValue = + SignatureValue(ed25519 = ed25519, timestamp = timestamp) + } +} diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/BotConfiguration.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/BotConfiguration.kt index c154105f..1983984f 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/BotConfiguration.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/BotConfiguration.kt @@ -139,6 +139,12 @@ public interface BotConfiguration { */ public val wsClientEngineFactory: HttpClientEngineFactory<*>? + /** + * 是否禁用 ws 连接。如果你打算使用 webhook,则设置为 `true`, + * 届时在启动 bot 时不会再连接 ws 服务。 + */ + public val disableWs: Boolean + /** * 用于API请求结果反序列化的 [Json]. * @@ -152,28 +158,6 @@ public interface BotConfiguration { * */ public val apiDecoder: Json -// -// /** -// * BOT内事件冲区的容量。 -// * -// * 缓冲区中堆积的事件如果已满则后续推送的事件会挂起等待缓冲区内元素的消费。 -// * -// * @see DEFAULT_EVENT_BUFFER_CAPACITY -// * -// */ -// public val eventBufferCapacity: Int -// -// -// public companion object { -// -// /** -// * 事件缓冲区的默认容量: `64` -// * -// * _此默认值没什么特殊含义,一拍脑袋想的。_ -// * -// */ -// public const val DEFAULT_EVENT_BUFFER_CAPACITY: Int = 64 -// } } diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/ConfigurableBotConfiguration.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/ConfigurableBotConfiguration.kt index bf0069a1..9770ec9a 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/ConfigurableBotConfiguration.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/ConfigurableBotConfiguration.kt @@ -171,6 +171,8 @@ public class ConfigurableBotConfiguration : BotConfiguration { */ override var wsClientEngineFactory: HttpClientEngineFactory<*>? = null + override val disableWs: Boolean = false + /** * 用于API请求结果反序列化的 [Json]. * @@ -194,6 +196,7 @@ public class ConfigurableBotConfiguration : BotConfiguration { apiHttpSocketTimeoutMillis = apiHttpSocketTimeoutMillis, wsClientEngine = wsClientEngine, wsClientEngineFactory = wsClientEngineFactory, + disableWs = disableWs, apiDecoder = apiDecoder, ) @@ -214,5 +217,6 @@ private class BotConfigurationImpl( override val apiHttpSocketTimeoutMillis: Long?, override val wsClientEngine: HttpClientEngine?, override val wsClientEngineFactory: HttpClientEngineFactory<*>?, + override val disableWs: Boolean, override val apiDecoder: Json, ) : BotConfiguration diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt index f172f0a5..bddd5a85 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt @@ -28,6 +28,8 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import love.forte.simbot.common.atomic.AtomicLong import love.forte.simbot.common.atomic.atomic import love.forte.simbot.common.collection.ConcurrentQueue @@ -38,6 +40,7 @@ import love.forte.simbot.common.weak.WeakRef import love.forte.simbot.common.weak.weakRef import love.forte.simbot.logger.Logger import love.forte.simbot.logger.LoggerFactory +import love.forte.simbot.logger.isDebugEnabled import love.forte.simbot.qguild.QQGuildResultSerializationException import love.forte.simbot.qguild.api.GatewayApis import love.forte.simbot.qguild.api.GatewayInfo @@ -45,9 +48,7 @@ import love.forte.simbot.qguild.api.app.AppAccessToken import love.forte.simbot.qguild.api.app.GetAppAccessTokenApi import love.forte.simbot.qguild.api.requestData import love.forte.simbot.qguild.api.user.GetBotInfoApi -import love.forte.simbot.qguild.event.Ready -import love.forte.simbot.qguild.event.Shard -import love.forte.simbot.qguild.event.Signal +import love.forte.simbot.qguild.event.* import love.forte.simbot.qguild.model.User import love.forte.simbot.qguild.stdlib.* import love.forte.simbot.qguild.stdlib.DisposableHandle @@ -87,7 +88,9 @@ internal class BotImpl( logger.warn("Bot(appId={}) intents value is ZERO", ticket.appId) } - checkTicketSecret() + if (!configuration.disableWs) { + checkTicketSecret() + } } private fun checkTicketSecret() { @@ -101,12 +104,14 @@ internal class BotImpl( } } - internal val wsDecoder = Signal.Dispatch.dispatchJson { + internal val eventDecoder = Signal.Dispatch.dispatchJson { isLenient = true ignoreUnknownKeys = true } - internal val wsClient: HttpClient = configuration.let { + private val wsClient: HttpClient? = configuration.let { + if (it.disableWs) return@let null + val wsClientEngine = it.wsClientEngine val wsClientEngineFactory = it.wsClientEngineFactory @@ -125,7 +130,6 @@ internal class BotImpl( } } - private fun HttpClientConfig<*>.configWsHttpClient() { WebSockets { } @@ -296,37 +300,44 @@ internal class BotImpl( flushAccessTokenJob = initFlushAccessTokenJob() } - val gateway = gatewayFactory() + if (!configuration.disableWs) { + val gateway = gatewayFactory() - logger.debug("Request gateway {} by shard {}", gateway, shard) + logger.debug("Request gateway {} by shard {}", gateway, shard) - val state = Connect(this, shard, gateway) + this.stageLoopJob = launchWsLoop(gateway) + } - logger.debug("Create state loop {}", state) + } + } - var st: State? = state - do { - logger.debug("Current state: {}", st) - st = st?.invoke() - } while (st != null && st !is ReceiveEvent) + private suspend fun launchWsLoop(gateway: GatewayInfo): Job { + val state = Connect(this, shard, gateway, wsClient!!) - if (st == null) { - // 当前状态为空且尚未进入事件接收状态 - throw IllegalStateException("The current state is null and not yet in the event receiving state") - } + logger.debug("Create state loop {}", state) - val stageLoopJob: Job = launch { - st.loop() - } + var st: State? = state + do { + logger.debug("Current state: {}", st) + st = st?.invoke() + } while (st != null && st !is ReceiveEvent) - stageLoopJob.invokeOnCompletion { reason -> - reason?.also { - logger.debug("StageLoopJob is on completion: {}", it.message, it) - } - } + if (st == null) { + // 当前状态为空且尚未进入事件接收状态 + throw IllegalStateException("The current state is null and not yet in the event receiving state") + } - this.stageLoopJob = stageLoopJob + val stageLoopJob: Job = launch { + st.loop() + } + + stageLoopJob.invokeOnCompletion { reason -> + reason?.also { + logger.debug("StageLoopJob is on completion: {}", it.message, it) + } } + + return stageLoopJob } private suspend fun initFlushAccessTokenJob(): Job { @@ -387,6 +398,68 @@ internal class BotImpl( } } + override suspend fun emitEvent(payload: String, options: EmitEventOptions?) { + // CODE 名称 客户端行为 描述 + // 0 Dispatch Receive 服务端进行消息推送 + // 13 回调地址验证 Receive 开放平台对机器人服务端进行验证 + logger.debug("Emit raw event with payload: {}", payload) + val json = eventDecoder.parseToJsonElement(payload) + + fun verifyIfNecessary() { + val signatureValue = options?.signatureValue ?: return + + verifyEventEd25519(signatureValue.ed25519, signatureValue.timestamp, payload) + } + + + when (val opcode = json.getOpcode()) { + null -> { + if (options?.ignoreMissingOpcode == true) return + + throw IllegalArgumentException("Required attribute `$.op` is missing") + } + Opcodes.Dispatch -> { + verifyIfNecessary() + + val dispatch = try { + eventDecoder.decodeFromJsonElement(Signal.Dispatch.serializer(), json) + } catch (serEx: SerializationException) { + val disSeq = -1L + val id = json.tryGetId() + + Signal.Dispatch.Unknown(id, disSeq, json, payload).also { + val t = + kotlin.runCatching { json.jsonObject[Signal.Dispatch.DISPATCH_CLASS_DISCRIMINATOR]?.jsonPrimitive?.content } + .getOrNull() + if (tryCheckIsPolymorphicException(serEx)) { + logger.warn("Unknown event type {}, decode it as Unknown event: {}", t, it) + } else { + logger.warn("Unknown event type {}, decode it as Unknown event: {}", t, it, serEx) + } + } + } + + emitEvent(dispatch, payload) + } + Opcodes.CallbackVerify -> { + verifyIfNecessary() + + TODO("CallbackVerify") + } + else -> { + if (options?.ignoreUnknownOpcode == true) return + + throw IllegalArgumentException("Unknown opcode: $opcode, emitEvent can only support opcode in [0, 13]") + } + } + + + + + TODO("Not yet implemented") + + } + override fun cancel(reason: Throwable?) { if (!job.isActive) return @@ -460,3 +533,48 @@ internal data class SessionInfo( val readyData: Ready.Data, ) +@OptIn(ExperimentalSimbotCollectionApi::class) +internal suspend fun BotImpl.emitEvent(dispatch: Signal.Dispatch, raw: String) { + // 先顺序地使用 preProcessor 处理 + preProcessorQueue.forEach { processor -> + runCatching { + processor.doInvoke(dispatch, raw) + }.onFailure { e -> + if (logger.isDebugEnabled) { + logger.debug( + "Event pre-precess failure. raw: {}, event: {}", raw, dispatch + ) + } + logger.error("Event pre-precess failure.", e) + } + } + + // 然后异步地正常处理 + // TODO 同步异步 configurable? + // bot launch or session launch? + launch { + processorQueue.forEach { processor -> + runCatching { + processor.doInvoke(dispatch, raw) + }.onFailure { e -> + if (logger.isDebugEnabled) { + logger.debug( + "Event precess failure. raw: {}, event: {}", raw, dispatch + ) + } + logger.error("Event precess failure.", e) + } + } + } +} + +/** + * see https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html + */ +private fun verifyEventEd25519( + xSignatureEd25519: String, + xSignatureTimestamp: String, + body: String, +) { + TODO("verifyEvent via ed25519") +} diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotStates.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotStates.kt index 2be79583..f7a22766 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotStates.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotStates.kt @@ -25,7 +25,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.sync.withLock import kotlinx.serialization.SerializationException import kotlinx.serialization.json.* -import love.forte.simbot.common.collection.ExperimentalSimbotCollectionApi import love.forte.simbot.logger.Logger import love.forte.simbot.logger.isDebugEnabled import love.forte.simbot.logger.isTraceEnabled @@ -33,7 +32,6 @@ import love.forte.simbot.qguild.api.GatewayApis import love.forte.simbot.qguild.api.GatewayInfo import love.forte.simbot.qguild.err import love.forte.simbot.qguild.event.* -import love.forte.simbot.qguild.stdlib.doInvoke import love.forte.simbot.qguild.stdlib.internal.BotImpl.ClientImpl import love.forte.simbot.qguild.stdlib.requestDataBy import kotlin.math.max @@ -80,6 +78,7 @@ internal class Connect( override val bot: BotImpl, private val shard: Shard, private val gateway: GatewayInfo, + private val wsClient: HttpClient, ) : State() { override suspend fun invoke(): State { val intents = bot.configuration.intents @@ -108,11 +107,11 @@ internal class Connect( ) bot.logger.debug("Connect to ws with gateway {}", gateway) - val session = bot.wsClient.ws { gateway } + val session = wsClient.ws { gateway } // next: receive Hello return WaitingHello(bot, session) { hello -> - WaitingReadyEvent(bot, identify, hello.data, session) + WaitingReadyEvent(bot, identify, hello.data, session, wsClient) } } } @@ -128,9 +127,9 @@ internal class WaitingHello( val frame = session.incoming.receive() as? Frame.Text ?: continue val text = frame.readText() logger.debug("Waiting hello : received frame {}", text) - val json = bot.wsDecoder.parseToJsonElement(text) + val json = bot.eventDecoder.parseToJsonElement(text) if (json.jsonObject["op"]?.jsonPrimitive?.int == Opcodes.Hello) { - hello = bot.wsDecoder.decodeFromJsonElement(Signal.Hello.serializer(), json) + hello = bot.eventDecoder.decodeFromJsonElement(Signal.Hello.serializer(), json) break } } @@ -157,26 +156,16 @@ internal class WaitingReadyEvent( override val bot: BotImpl, private val identify: Signal.Identify, private val hello: Signal.Hello.Data, - private val session: DefaultClientWebSocketSession + private val session: DefaultClientWebSocketSession, + private val wsClient: HttpClient, ) : State() { override suspend fun invoke(): State { // 发送 identify - val identifyJson = bot.wsDecoder.encodeToString(Signal.Identify.serializer(), identify) + val identifyJson = bot.eventDecoder.encodeToString(Signal.Identify.serializer(), identify) logger.debug("Send identify {}, JSON: {}", identify, identifyJson) session.send(identifyJson) logger.debug("Send identify Successfully.") -// HeartbeatJob( -// bot, hello, -// Ready.Data( -// "", -// "", -// User("", ""), -// Shard.FULL -// ), session -// ).invoke() - - // 等待ready var ready: Ready? = null logger.debug("Waiting for Signal ready...") @@ -191,9 +180,9 @@ internal class WaitingReadyEvent( val frame = frameResult.getOrThrow() as? Frame.Text ?: continue val text = frame.readText() logger.debug("Waiting ready event : received frame {}", text) - val json = bot.wsDecoder.parseToJsonElement(text) + val json = bot.eventDecoder.parseToJsonElement(text) if (json.jsonObject["op"]?.jsonPrimitive?.int == Opcodes.Dispatch) { - val dispatch = bot.wsDecoder.decodeFromJsonElement(Signal.Dispatch.serializer(), json) + val dispatch = bot.eventDecoder.decodeFromJsonElement(Signal.Dispatch.serializer(), json) if (dispatch is Ready) { ready = dispatch break @@ -210,7 +199,7 @@ internal class WaitingReadyEvent( logger.debug("Received Ready event: {}", r) // next: session - return HeartbeatJob(bot, hello, r.data, session) + return HeartbeatJob(bot, hello, r.data, session, wsClient) } } @@ -219,7 +208,8 @@ internal class HeartbeatJob( override val bot: BotImpl, private val hello: Signal.Hello.Data, private val readyData: Ready.Data, - private val session: DefaultClientWebSocketSession + private val session: DefaultClientWebSocketSession, + private val wsClient: HttpClient, ) : State() { override suspend fun invoke(): State { val seq = AtomicLongRef(-1L) @@ -230,7 +220,7 @@ internal class HeartbeatJob( val sessionInfo = SessionInfo(session, seq, heartbeatJob, logger, readyData) // next: process event - return CreateClient(bot, sessionInfo, session) + return CreateClient(bot, sessionInfo, session, wsClient) } @@ -252,7 +242,7 @@ internal class HeartbeatJob( } delay(timeMillis) val hb = Signal.Heartbeat(seq.value.takeIf { it >= 0 }) - session.send(bot.wsDecoder.encodeToString(serializer, hb)) + session.send(bot.eventDecoder.encodeToString(serializer, hb)) } } } @@ -265,7 +255,8 @@ internal class HeartbeatJob( internal class CreateClient( override val bot: BotImpl, private val botClientSession: SessionInfo, - private val session: DefaultClientWebSocketSession + private val session: DefaultClientWebSocketSession, + private val wsClient: HttpClient, ) : State() { override suspend fun invoke(): State { val client = bot.ClientImpl( @@ -280,7 +271,7 @@ internal class CreateClient( bot.updateClient(client) // next: receive events - return ReceiveEvent(bot, client) + return ReceiveEvent(bot, client, wsClient) } } @@ -290,6 +281,7 @@ internal class CreateClient( internal class ReceiveEvent( override val bot: BotImpl, private val client: ClientImpl, + private val wsClient: HttpClient, ) : State() { override suspend fun invoke(): State? { val session = client.wsSession @@ -297,7 +289,7 @@ internal class ReceiveEvent( if (!session.isActive) { val reason = session.closeReason.await() logger.error("Session is closed. reason: {}. Try to resume", reason) - return Resume(bot, client) + return Resume(bot, client, wsClient) } suspend fun onCatchErr(e: Throwable?): State? { @@ -305,13 +297,13 @@ internal class ReceiveEvent( if (reason == null) { logger.debug("Session closed and reason is null, try to resume", e) // try resume - return Resume(bot, client) + return Resume(bot, client, wsClient) } suspend fun doIdentify(): State { val gatewayInfo: GatewayInfo = GatewayApis.Normal.requestDataBy(bot) logger.debug("Reconnect gateway {} by shard {}", gatewayInfo, bot.shard) - return Connect(bot, bot.shard, gatewayInfo) + return Connect(bot, bot.shard, gatewayInfo, wsClient) } val reasonCode = reason.code @@ -319,7 +311,7 @@ internal class ReceiveEvent( canBeResumed(reasonCode) -> { logger.debug("Session closed({}), try to resume", reason, e) // try resume - return Resume(bot, client) + return Resume(bot, client, wsClient) } canBeIdentified(reasonCode) -> { @@ -376,12 +368,12 @@ internal class ReceiveEvent( return this } logger.debug("Received text frame raw: {}", raw) - val json = bot.wsDecoder.parseToJsonElement(raw) + val json = bot.eventDecoder.parseToJsonElement(raw) when (val opcode = json.getOpcode()) { Opcodes.Dispatch -> { // event val dispatch = try { - bot.wsDecoder.decodeFromJsonElement(Signal.Dispatch.serializer(), json) + bot.eventDecoder.decodeFromJsonElement(Signal.Dispatch.serializer(), json) } catch (serEx: SerializationException) { // if (tryCheckIsPolymorphicException(serEx)) { // 未知的事件类型 @@ -403,17 +395,12 @@ internal class ReceiveEvent( logger.warn("Unknown event type {}, decode it as Unknown event: {}", t, it, serEx) } } -// } else { -// // throw out -// throw serEx -// } } logger.debug("Received dispatch: {}", dispatch) val dispatchSeq = dispatch.seq // 推送事件 - emitEvent(bot, dispatch, raw) -// eventSharedFlow.emit(EventData(raw, dispatch)) + bot.emitEvent(dispatch, raw) // seq留下最大值 val currentSeq = seq.updateAndGet { pref -> max(pref, dispatchSeq) } @@ -423,7 +410,7 @@ internal class ReceiveEvent( Opcodes.Reconnect -> { // 重新连接 logger.debug("Received reconnect signal. Do Resume.") - return Resume(bot, client) + return Resume(bot, client, wsClient) } else -> { @@ -441,7 +428,7 @@ internal class ReceiveEvent( } } -private fun tryCheckIsPolymorphicException(exception: SerializationException): Boolean { +internal fun tryCheckIsPolymorphicException(exception: SerializationException): Boolean { // 似乎是某个版本的错误提示,但是至少在 JSON v1.6.3 已经不适用了 if (exception.message?.startsWith("Polymorphic serializer was not found for") == true) { return true @@ -455,41 +442,6 @@ private fun tryCheckIsPolymorphicException(exception: SerializationException): B return false } -@OptIn(ExperimentalSimbotCollectionApi::class) -private suspend fun emitEvent(bot: BotImpl, dispatch: Signal.Dispatch, raw: String) { - val logger = bot.logger - // 先顺序地使用 preProcessor 处理 - bot.preProcessorQueue.forEach { processor -> - runCatching { - processor.doInvoke(dispatch, raw) - }.onFailure { e -> - if (logger.isDebugEnabled) { - logger.debug( - "Event pre-precess failure. raw: {}, event: {}", raw, dispatch - ) - } - logger.error("Event pre-precess failure.", e) - } - } - - // 然后异步地正常处理 - // TODO 同步异步 configurable? - // bot launch or session launch? - bot.launch { - bot.processorQueue.forEach { processor -> - runCatching { - processor.doInvoke(dispatch, raw) - }.onFailure { e -> - if (logger.isDebugEnabled) { - logger.debug( - "Event precess failure. raw: {}, event: {}", raw, dispatch - ) - } - logger.error("Event precess failure.", e) - } - } - } -} /** @@ -502,23 +454,24 @@ private suspend fun emitEvent(bot: BotImpl, dispatch: Signal.Dispatch, raw: Stri internal class Resume( override val bot: BotImpl, private val client: ClientImpl, + private val wsClient: HttpClient, ) : State() { override suspend fun invoke(): State { val newSession = bot.startLock.withLock { // 关闭当前连接 client.cancelAndJoin() val gateway = GatewayApis.Normal.requestDataBy(bot) - bot.wsClient.ws { gateway }.apply { + wsClient.ws { gateway }.apply { // 发送 Opcode6 val resumeSignal = Signal.Resume(Signal.Resume.Data(bot.qqBotToken, client.readyData.sessionId, client.seq)) - send(bot.wsDecoder.encodeToString(Signal.Resume.serializer(), resumeSignal)) + send(bot.eventDecoder.encodeToString(Signal.Resume.serializer(), resumeSignal)) } } return WaitingHello(bot, newSession) { hello -> // 跳过 wait ready, 直接HeartbeatJob - HeartbeatJob(bot, hello.data, client.readyData, session) + HeartbeatJob(bot, hello.data, client.readyData, session, wsClient) } } } From 1f44b083586ada2917a331c06a7d1a7f50c17d90 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Wed, 16 Oct 2024 14:42:03 +0800 Subject: [PATCH 06/28] Support for webhook --- .../simbot/qguild/stdlib/internal/BotImpl.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt index bddd5a85..8c73cf9e 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt @@ -95,12 +95,14 @@ internal class BotImpl( private fun checkTicketSecret() { if (ticket.secret.isEmpty()) { - logger.error("The `ticket.secret` is empty. " + - "Since v4.0.0-beta6, the authentication logic within component " + - "has been migrated to new logic that requires the use of `secret`. " + - "If you do not configure the `ticket.secret`, " + - "it will most likely fail to start and throw an exception. " + - "See also: https://github.com/simple-robot/simbot-component-qq-guild/pull/163") + logger.error( + "The `ticket.secret` is empty. " + + "Since v4.0.0-beta6, the authentication logic within component " + + "has been migrated to new logic that requires the use of `secret`. " + + "If you do not configure the `ticket.secret`, " + + "it will most likely fail to start and throw an exception. " + + "See also: https://github.com/simple-robot/simbot-component-qq-guild/pull/163" + ) } } @@ -418,6 +420,7 @@ internal class BotImpl( throw IllegalArgumentException("Required attribute `$.op` is missing") } + Opcodes.Dispatch -> { verifyIfNecessary() @@ -441,11 +444,13 @@ internal class BotImpl( emitEvent(dispatch, payload) } + Opcodes.CallbackVerify -> { verifyIfNecessary() TODO("CallbackVerify") } + else -> { if (options?.ignoreUnknownOpcode == true) return From 09c839fb6798d7036422a9875f20fdba90176dc4 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Wed, 16 Oct 2024 14:42:03 +0800 Subject: [PATCH 07/28] Support for webhook --- .../simbot/qguild/stdlib/internal/BotImpl.kt | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt index bddd5a85..fc8c61f9 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt @@ -95,12 +95,14 @@ internal class BotImpl( private fun checkTicketSecret() { if (ticket.secret.isEmpty()) { - logger.error("The `ticket.secret` is empty. " + - "Since v4.0.0-beta6, the authentication logic within component " + - "has been migrated to new logic that requires the use of `secret`. " + - "If you do not configure the `ticket.secret`, " + - "it will most likely fail to start and throw an exception. " + - "See also: https://github.com/simple-robot/simbot-component-qq-guild/pull/163") + logger.error( + "The `ticket.secret` is empty. " + + "Since v4.0.0-beta6, the authentication logic within component " + + "has been migrated to new logic that requires the use of `secret`. " + + "If you do not configure the `ticket.secret`, " + + "it will most likely fail to start and throw an exception. " + + "See also: https://github.com/simple-robot/simbot-component-qq-guild/pull/163" + ) } } @@ -418,6 +420,7 @@ internal class BotImpl( throw IllegalArgumentException("Required attribute `$.op` is missing") } + Opcodes.Dispatch -> { verifyIfNecessary() @@ -441,23 +444,19 @@ internal class BotImpl( emitEvent(dispatch, payload) } + Opcodes.CallbackVerify -> { verifyIfNecessary() TODO("CallbackVerify") } + else -> { if (options?.ignoreUnknownOpcode == true) return throw IllegalArgumentException("Unknown opcode: $opcode, emitEvent can only support opcode in [0, 13]") } } - - - - - TODO("Not yet implemented") - } override fun cancel(reason: Throwable?) { From b119e4456ed495cfb4f3cafab060e0cb5d205867 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Wed, 16 Oct 2024 14:47:08 +0800 Subject: [PATCH 08/28] mark TODO --- .../kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt index fc8c61f9..e4725904 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt @@ -448,6 +448,8 @@ internal class BotImpl( Opcodes.CallbackVerify -> { verifyIfNecessary() + // TODO return sign + TODO("CallbackVerify") } From ec7f3a37122ec1c340d3a988fc554e647e29a945 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Wed, 16 Oct 2024 17:32:48 +0800 Subject: [PATCH 09/28] ED25519 ? --- buildSrc/src/main/kotlin/JVMConstants.kt | 5 +- .../api-reader/build.gradle.kts | 4 +- .../intents-processor/build.gradle.kts | 4 +- .../build.gradle.kts | 9 ++ .../love/forte/simbot/qguild/stdlib/Bot.kt | 40 ++++---- .../simbot/qguild/stdlib/internal/BotImpl.kt | 21 +---- .../src/commonTest/kotlin/Ed25519Tests.kt | 43 +++++++++ .../src/jvmTest/kotlin/Ed25519Tests.kt | 91 +++++++++++++++++++ 8 files changed, 177 insertions(+), 40 deletions(-) create mode 100644 simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt create mode 100644 simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt diff --git a/buildSrc/src/main/kotlin/JVMConstants.kt b/buildSrc/src/main/kotlin/JVMConstants.kt index a1daba86..9eada42a 100644 --- a/buildSrc/src/main/kotlin/JVMConstants.kt +++ b/buildSrc/src/main/kotlin/JVMConstants.kt @@ -16,6 +16,7 @@ */ object JVMConstants { - const val KT_JVM_TARGET_VALUE = 11 - const val KT_JVM_TARGET = "11" + // TODO + const val KT_JVM_TARGET_VALUE = 17 + const val KT_JVM_TARGET = "17" } diff --git a/internal-processors/api-reader/build.gradle.kts b/internal-processors/api-reader/build.gradle.kts index 5f97a9cb..defe66ae 100644 --- a/internal-processors/api-reader/build.gradle.kts +++ b/internal-processors/api-reader/build.gradle.kts @@ -26,10 +26,10 @@ repositories { } kotlin { - jvmToolchain(11) + jvmToolchain(JVMConstants.KT_JVM_TARGET_VALUE) compilerOptions { javaParameters = true - jvmTarget.set(JvmTarget.JVM_11) + jvmTarget.set(JvmTarget.fromTarget(JVMConstants.KT_JVM_TARGET_VALUE.toString())) } } diff --git a/internal-processors/intents-processor/build.gradle.kts b/internal-processors/intents-processor/build.gradle.kts index bc6c72cd..3e032d1e 100644 --- a/internal-processors/intents-processor/build.gradle.kts +++ b/internal-processors/intents-processor/build.gradle.kts @@ -26,10 +26,10 @@ repositories { } kotlin { - jvmToolchain(11) + jvmToolchain(JVMConstants.KT_JVM_TARGET_VALUE) compilerOptions { javaParameters = true - jvmTarget.set(JvmTarget.JVM_11) + jvmTarget.set(JvmTarget.fromTarget(JVMConstants.KT_JVM_TARGET_VALUE.toString())) } } diff --git a/simbot-component-qq-guild-stdlib/build.gradle.kts b/simbot-component-qq-guild-stdlib/build.gradle.kts index 2e407b72..f1ac8b5a 100644 --- a/simbot-component-qq-guild-stdlib/build.gradle.kts +++ b/simbot-component-qq-guild-stdlib/build.gradle.kts @@ -72,6 +72,8 @@ kotlin { implementation(kotlin("test")) implementation(libs.kotlinx.coroutines.test) implementation(libs.ktor.client.mock) + // https://github.com/andreypfau/curve25519-kotlin + // implementation("com.diglol.crypto:pkc:0.2.0") } jvmTest.dependencies { @@ -79,6 +81,13 @@ kotlin { implementation(libs.log4j.api) implementation(libs.log4j.core) implementation(libs.log4j.slf4j2) + +// implementation("dev.whyoleg.cryptography:cryptography-core:0.4.0") +// implementation(kotlincrypto.core.digest) +// implementation(kotlincrypto.core.mac) +// implementation(kotlincrypto.core.xof) +// implementation(kotlincrypto.macs.hmac.sha1) +// implementation(kotlincrypto.macs.kmac) } jsMain.dependencies { diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt index 92e738a8..c3f5a66d 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt @@ -30,7 +30,6 @@ import love.forte.simbot.qguild.model.User import love.forte.simbot.suspendrunner.ST import love.forte.simbot.suspendrunner.STP import kotlin.coroutines.CoroutineContext -import kotlin.jvm.JvmStatic /** * 一个 QQ频道Bot。 @@ -305,31 +304,36 @@ public class EmitEventOptions { public var ignoreMissingOpcode: Boolean = false /** - * 如果需要对此回调事件进行签名校验,则通过 [signatureValue] 配置校验所需的、来自请求头透传的值。 + * 如果需要对此回调事件进行签名校验, + * 则通过 [signatureVerifier] 配置校验器。 * - * - `X-Signature-Ed25519` - * - `X-Signature-Timestamp` + * 校验器提供一个基于 [Bot.Ticket.secret] 进行校验的函数, + * 如果出现任何不匹配的错误结果,直接抛出一个运行时异常即可。 * - * 更多参考 [官方文档](https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html) + * 如果你想要以自己的逻辑提前校验则可设为 `null`, + * 如果为 `null` 则不会进行校验。 + * + * 默认为 `null`。 * - * [signatureValue] 默认为 `null`, 即不进行校验。 + * 更多参考 [官方文档](https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html) */ - public var signatureValue: SignatureValue? = null + public var signatureVerifier: SignatureVerifier? = null } /** - * 回调校验用的参数 - * 更多参考 [官方文档](https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html) + * 一个使用 [Bot.Ticket.secret] 进行校验的签名校验器。 + * + * secret 是敏感信息,请只使用可信任的实现以确保机密信息不被泄露。 * * @since 4.1.0 */ -public class SignatureValue private constructor( - public val ed25519: String, - public val timestamp: String -) { - public companion object { - @JvmStatic - public fun create(ed25519: String, timestamp: String): SignatureValue = - SignatureValue(ed25519 = ed25519, timestamp = timestamp) - } +public interface SignatureVerifier { + /** + * 根据 bot 的 [secret] 进行校验。 + * 如果校验不通过,则直接抛出所需的异常,未出现异常即视为校验成功。 + * + * @param payload 推送事件的JSON体正文 + * @param secret bot 配置的 [Bot.Ticket.secret] + */ + public fun verify(payload: String, secret: String) } diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt index e4725904..1e65da52 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt @@ -407,10 +407,10 @@ internal class BotImpl( logger.debug("Emit raw event with payload: {}", payload) val json = eventDecoder.parseToJsonElement(payload) - fun verifyIfNecessary() { - val signatureValue = options?.signatureValue ?: return + fun signatureVerifyIfNecessary() { + val signatureValidator = options?.signatureVerifier ?: return - verifyEventEd25519(signatureValue.ed25519, signatureValue.timestamp, payload) + signatureValidator.verify(payload, ticket.secret) } @@ -422,7 +422,7 @@ internal class BotImpl( } Opcodes.Dispatch -> { - verifyIfNecessary() + signatureVerifyIfNecessary() val dispatch = try { eventDecoder.decodeFromJsonElement(Signal.Dispatch.serializer(), json) @@ -446,7 +446,7 @@ internal class BotImpl( } Opcodes.CallbackVerify -> { - verifyIfNecessary() + signatureVerifyIfNecessary() // TODO return sign @@ -568,14 +568,3 @@ internal suspend fun BotImpl.emitEvent(dispatch: Signal.Dispatch, raw: String) { } } } - -/** - * see https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html - */ -private fun verifyEventEd25519( - xSignatureEd25519: String, - xSignatureTimestamp: String, - body: String, -) { - TODO("verifyEvent via ed25519") -} diff --git a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt new file mode 100644 index 00000000..6099b199 --- /dev/null +++ b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt @@ -0,0 +1,43 @@ +//import diglol.crypto.Ed25519 +//import io.ktor.utils.io.core.* +//import kotlinx.coroutines.test.runTest +//import kotlin.test.Test +//import kotlin.test.assertContentEquals +// +// +///** +// * +// * @author ForteScarlet +// */ +//class Ed25519Tests { +// +// /** +// * https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html +// * +// * 验证签名过程: +// * 根据开发者平台的 Bot Secret 值进行repeat操作得到签名32字节的 seed , +// * 根据 seed 调用 Ed25519 算法生成32字节公钥 +// */ +// @OptIn(ExperimentalStdlibApi::class) +// // @Test +// fun ed25519Test() = runTest { +// val secret = "naOC0ocQE3shWLAfffVLB1rhYPG7" +// val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" +// val pair = Ed25519.generateKeyPair(seed.toByteArray()) +// println("publicKey: " + pair.publicKey.joinToString { it.toUByte().toString() }) +// println("privateKey: " + pair.privateKey.joinToString { it.toUByte().toString() }) +// +// assertContentEquals( +// "d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" +// .hexToByteArray(), +// pair.publicKey, +// ) +// +// assertContentEquals( +// "6e614f43306f635145337368574c41666666564c42317268595047376e614f43d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" +// .hexToByteArray(), +// pair.privateKey, +// ) +// } +// +//} diff --git a/simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt b/simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt new file mode 100644 index 00000000..f9c6f017 --- /dev/null +++ b/simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt @@ -0,0 +1,91 @@ +import java.security.KeyPairGenerator +import java.security.SecureRandom +import java.security.Signature +import java.security.spec.NamedParameterSpec +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertTrue + + +/** + * + * @author ForteScarlet + */ +class Ed25519Tests4J { + @OptIn(ExperimentalStdlibApi::class) + @Test + fun a() { + "110 97 79 67 48 111 99 81 69 51 115 104 87 76 65 102 102 102 86 76 66 49 114 104 89 80 71 55 110 97 79 67 215 195 98 254 120 174 248 31 242 50 135 180 147 98 139 93 176 42 60 79 227 11 33 94 77 25 96 155 93 118 103 58" + .split(" ") + .map { it.toUByte() } + .map { it.toByte() } + .toByteArray() + .toHexString().also { + println(it) + } + } + + + // https://openjdk.org/jeps/339 + // https://howtodoinjava.com/java15/java-eddsa-example/ + @OptIn(ExperimentalStdlibApi::class) + // @Test + fun ed25519KeyGenTest() { + val secret = "naOC0ocQE3shWLAfffVLB1rhYPG7" + val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" + + println(System.getProperty("java.version")) + + val kpg = KeyPairGenerator.getInstance("Ed25519") + kpg.initialize(NamedParameterSpec.ED25519, SecureRandom(seed.toByteArray())) + + val kp = kpg.generateKeyPair() + + assertContentEquals( + ASSERT_PUBLIC_HEX.hexToByteArray(), + kp.public.encoded, + ) + + assertContentEquals( + ASSERT_PRIVATE_HEX.hexToByteArray(), + kp.private.encoded, + ) + } + + @OptIn(ExperimentalStdlibApi::class) + // @Test TODO + fun ed25519VerifyTest() { + val secret = "naOC0ocQE3shWLAfffVLB1rhYPG7" + val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" + val body = """{"op":0,"d":{},"t":"GATEWAY_EVENT_NAME"}""" + val timestamp = "1725442341" + val sig = "865ad13a61752ca65e26bde6676459cd36cf1be609375b37bd62af366e1dc25a8dc789ba7f14e017ada3d554c671a911bfdf075ba54835b23391d509579ed002" + + // 获取 HTTP Header 中 X-Signature-Ed25519 的值进行 hec (十六进制解码)操作后的得到 Signature 并进行校验 + val kpg = KeyPairGenerator.getInstance("Ed25519") + kpg.initialize(NamedParameterSpec.ED25519, SecureRandom(seed.toByteArray())) + val kp = kpg.generateKeyPair() + // 获取 HTTP Header 中 X-Signature-Timestamp 的和 HTTP Body 的值按照 timestamp+body 顺序进行组合成签名体msg + + // algorithm is pure Ed25519 + val signature = Signature.getInstance("Ed25519") + signature.initVerify(kp.public) + signature.update("$timestamp$body".toByteArray()) + assertTrue(signature.verify(sig.hexToByteArray())) +// signature.initSign(kp.private) +// signature.initSign(kp.private) +// signature.update("$timestamp$body".toByteArray()) +// val s: ByteArray = signature.sign() +// s.toHexString() + + + // 根据公钥、Signature、签名体调用 Ed25519 算法进行验证 + + + } + +} + +private const val ASSERT_PUBLIC_HEX = "d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" +private const val ASSERT_PRIVATE_HEX = + "6e614f43306f635145337368574c41666666564c42317268595047376e614f43d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" From 3ff23f5c6d5669ea53eb88a6b103abc70fd2b2b6 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Wed, 16 Oct 2024 22:46:18 +0800 Subject: [PATCH 10/28] Support for webhook --- buildSrc/src/main/kotlin/P.kt | 2 +- .../forte/simbot/qguild/event/EventIntents.kt | 4 +- .../love/forte/simbot/qguild/event/Signal.kt | 6 +- .../simbot/qguild/event/c2cManagements.kt | 8 +- .../forte/simbot/qguild/event/c2cMessages.kt | 4 +- .../forte/simbot/qguild/event/channels.kt | 6 +- .../love/forte/simbot/qguild/event/forums.kt | 16 +- .../simbot/qguild/event/groupManagements.kt | 8 +- .../love/forte/simbot/qguild/event/guilds.kt | 6 +- .../love/forte/simbot/qguild/event/members.kt | 6 +- .../forte/simbot/qguild/event/messages.kt | 14 +- .../forte/simbot/qguild/event/openForums.kt | 14 +- .../simbot/component/qguild/bot/QGBot.kt | 62 +++++++- .../component/qguild/bot/QQGuildBotManager.kt | 7 + .../bot/config/QGBotFileConfiguration.kt | 10 +- .../qguild/internal/bot/QGBotImpl.kt | 1 + .../build.gradle.kts | 7 +- .../love/forte/simbot/qguild/stdlib/Bot.kt | 51 +++--- .../simbot/qguild/stdlib/BotConfiguration.kt | 2 + .../stdlib/ConfigurableBotConfiguration.kt | 2 +- .../simbot/qguild/stdlib/internal/BotImpl.kt | 106 ++++++++++--- .../src/commonTest/kotlin/Ed25519Tests.kt | 148 +++++++++++++----- .../src/jvmTest/kotlin/Ed25519Tests.kt | 22 +-- 23 files changed, 366 insertions(+), 146 deletions(-) diff --git a/buildSrc/src/main/kotlin/P.kt b/buildSrc/src/main/kotlin/P.kt index 8670c734..d024e69d 100644 --- a/buildSrc/src/main/kotlin/P.kt +++ b/buildSrc/src/main/kotlin/P.kt @@ -56,7 +56,7 @@ object P { override val homepage: String get() = HOMEPAGE - const val VERSION = "4.0.2" + const val VERSION = "4.1.0" const val NEXT_VERSION = "4.1.0" override val snapshotVersion = "$NEXT_VERSION-SNAPSHOT" diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt index 276c6fe2..7e0a3724 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt @@ -624,7 +624,7 @@ public val EventIntentsInstances: Array @SerialName(READY_TYPE) public data class Ready( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Data ) : Signal.Dispatch() { /** @@ -649,7 +649,7 @@ public data class Ready( @SerialName(RESUMED_TYPE) public data class Resumed( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: String ) : Signal.Dispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt index 4d86a60f..771891fe 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt @@ -163,14 +163,14 @@ public sealed class Signal(@Serializable(Opcode.SerializerByCode::class) publ /** * 事件序列 */ - protected abstract val s: Long + protected abstract val s: Long? public abstract val id: String? /** * 事件序列 */ - public val seq: Long get() = s + public val seq: Long get() = s ?: -1 /** * 此事件的实际本体 @@ -215,7 +215,7 @@ public sealed class Signal(@Serializable(Opcode.SerializerByCode::class) publ */ public data class Unknown @QGInternalApi constructor( override val id: String? = null, - override val s: Long, + override val s: Long? = null, override val data: JsonElement, val raw: String ) : Dispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt index 16353f19..b0a42878 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt @@ -49,7 +49,7 @@ public sealed class C2CManagementDispatch : Signal.Dispatch() { @SerialName(EventIntents.GroupAndC2CEvent.FRIEND_ADD_TYPE) public data class FriendAdd( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: C2CManagementData ) : C2CManagementDispatch() @@ -63,7 +63,7 @@ public data class FriendAdd( @SerialName(EventIntents.GroupAndC2CEvent.FRIEND_DEL_TYPE) public data class FriendDel( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: C2CManagementData ) : C2CManagementDispatch() @@ -77,7 +77,7 @@ public data class FriendDel( @SerialName(EventIntents.GroupAndC2CEvent.C2C_MSG_REJECT_TYPE) public data class C2CMsgReject( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: C2CManagementData ) : C2CManagementDispatch() @@ -91,7 +91,7 @@ public data class C2CMsgReject( @SerialName(EventIntents.GroupAndC2CEvent.C2C_MSG_RECEIVE_TYPE) public data class C2CMsgReceive( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: C2CManagementData ) : C2CManagementDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt index fff4af9a..e98026c6 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt @@ -30,7 +30,7 @@ import love.forte.simbot.qguild.model.Message @SerialName(EventIntents.GroupAndC2CEvent.C2C_MESSAGE_CREATE_TYPE) public data class C2CMessageCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Data, ) : Signal.Dispatch() { @@ -74,7 +74,7 @@ public data class C2CMessageCreate( @SerialName(EventIntents.GroupAndC2CEvent.GROUP_AT_MESSAGE_CREATE_TYPE) public data class GroupAtMessageCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Data, ) : Signal.Dispatch() { diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt index 8a1894e1..514b1d7c 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt @@ -45,7 +45,7 @@ public sealed class ChannelDispatch : Signal.Dispatch() { @SerialName(EventIntents.Guilds.CHANNEL_CREATE_TYPE) public data class ChannelCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: EventChannel ) : ChannelDispatch() @@ -59,7 +59,7 @@ public data class ChannelCreate( @SerialName(EventIntents.Guilds.CHANNEL_UPDATE_TYPE) public data class ChannelUpdate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: EventChannel ) : ChannelDispatch() @@ -73,7 +73,7 @@ public data class ChannelUpdate( @SerialName(EventIntents.Guilds.CHANNEL_DELETE_TYPE) public data class ChannelDelete( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: EventChannel ) : ChannelDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt index b3bda947..216192e4 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt @@ -94,7 +94,7 @@ public sealed class ForumThreadDispatch : ForumDispatch() { @SerialName(EventIntents.ForumsEvent.FORUM_THREAD_CREATE_TYPE) public data class ForumThreadCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Thread ) : ForumThreadDispatch() @@ -108,7 +108,7 @@ public data class ForumThreadCreate( @SerialName(EventIntents.ForumsEvent.FORUM_THREAD_UPDATE_TYPE) public data class ForumThreadUpdate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Thread ) : ForumThreadDispatch() @@ -122,7 +122,7 @@ public data class ForumThreadUpdate( @SerialName(EventIntents.ForumsEvent.FORUM_THREAD_DELETE_TYPE) public data class ForumThreadDelete( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Thread ) : ForumThreadDispatch() @@ -149,7 +149,7 @@ public sealed class ForumPostDispatch : ForumDispatch() { @SerialName(EventIntents.ForumsEvent.FORUM_POST_CREATE_TYPE) public data class ForumPostCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Post ) : ForumPostDispatch() @@ -163,7 +163,7 @@ public data class ForumPostCreate( @SerialName(EventIntents.ForumsEvent.FORUM_POST_DELETE_TYPE) public data class ForumPostDelete( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Post ) : ForumPostDispatch() @@ -190,7 +190,7 @@ public sealed class ForumReplyDispatch : ForumDispatch() { @SerialName(EventIntents.ForumsEvent.FORUM_REPLY_CREATE_TYPE) public data class ForumReplyCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Reply ) : ForumReplyDispatch() @@ -204,7 +204,7 @@ public data class ForumReplyCreate( @SerialName(EventIntents.ForumsEvent.FORUM_REPLY_DELETE_TYPE) public data class ForumReplyDelete( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Reply ) : ForumReplyDispatch() @@ -217,6 +217,6 @@ public data class ForumReplyDelete( @SerialName(EventIntents.ForumsEvent.FORUM_PUBLISH_AUDIT_RESULT_TYPE) public data class ForumPublishAuditResult( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: AuditResult ) : ForumDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt index 54ee95e7..51b85280 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt @@ -51,7 +51,7 @@ public sealed class GroupRobotManagementDispatch : Signal.Dispatch() { @SerialName(EventIntents.GroupAndC2CEvent.GROUP_ADD_ROBOT_TYPE) public data class GroupAddRobot( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: GroupRobotManagementData ) : GroupRobotManagementDispatch() @@ -65,7 +65,7 @@ public data class GroupAddRobot( @SerialName(EventIntents.GroupAndC2CEvent.GROUP_DEL_ROBOT_TYPE) public data class GroupDelRobot( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: GroupRobotManagementData ) : GroupRobotManagementDispatch() @@ -79,7 +79,7 @@ public data class GroupDelRobot( @SerialName(EventIntents.GroupAndC2CEvent.GROUP_MSG_REJECT_TYPE) public data class GroupMsgReject( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: GroupRobotManagementData ) : GroupRobotManagementDispatch() @@ -93,7 +93,7 @@ public data class GroupMsgReject( @SerialName(EventIntents.GroupAndC2CEvent.GROUP_MSG_RECEIVE_TYPE) public data class GroupMsgReceive( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: GroupRobotManagementData ) : GroupRobotManagementDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt index 3caeb9ee..faec97ca 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt @@ -43,7 +43,7 @@ public sealed class EventGuildDispatch : Signal.Dispatch() { @SerialName(EventIntents.Guilds.GUILD_CREATE_TYPE) public data class GuildCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: EventGuild ) : EventGuildDispatch() @@ -59,7 +59,7 @@ public data class GuildCreate( @SerialName(EventIntents.Guilds.GUILD_UPDATE_TYPE) public data class GuildUpdate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: EventGuild ) : EventGuildDispatch() @@ -76,7 +76,7 @@ public data class GuildUpdate( @SerialName(EventIntents.Guilds.GUILD_DELETE_TYPE) public data class GuildDelete( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: EventGuild ) : EventGuildDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt index 4114a455..76c244b9 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt @@ -33,7 +33,7 @@ import love.forte.simbot.qguild.time.ZERO_ISO_INSTANT @SerialName(EventIntents.GuildMembers.GUILD_MEMBER_ADD_TYPE) public data class GuildMemberAdd( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: EventMember ) : Signal.Dispatch() @@ -46,7 +46,7 @@ public data class GuildMemberAdd( @SerialName(EventIntents.GuildMembers.GUILD_MEMBER_UPDATE_TYPE) public data class GuildMemberUpdate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: EventMember ) : Signal.Dispatch() @@ -59,7 +59,7 @@ public data class GuildMemberUpdate( @SerialName(EventIntents.GuildMembers.GUILD_MEMBER_REMOVE_TYPE) public data class GuildMemberRemove( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: EventMember ) : Signal.Dispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt index 38888677..613ffe2c 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt @@ -46,7 +46,7 @@ public sealed class MessageDispatch : Signal.Dispatch() { @SerialName(EventIntents.PublicGuildMessages.AT_MESSAGE_CREATE_TYPE) public data class AtMessageCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Message ) : MessageDispatch() @@ -59,7 +59,7 @@ public data class AtMessageCreate( @SerialName(EventIntents.PublicGuildMessages.PUBLIC_MESSAGE_DELETE_TYPE) public data class PublicMessageDeleteCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Unit /* TODO 文档没找到描述。 */ ) : Signal.Dispatch() @@ -76,7 +76,7 @@ public data class PublicMessageDeleteCreate( @SerialName(EventIntents.DirectMessage.DIRECT_MESSAGE_CREATE_TYPE) public data class DirectMessageCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Message ) : MessageDispatch() @@ -97,7 +97,7 @@ public sealed class MessageAuditedDispatch : Signal.Dispatch() { @SerialName(EventIntents.GuildMessages.MESSAGE_CREATE_TYPE) public data class MessageCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Message ) : MessageDispatch() @@ -108,7 +108,7 @@ public data class MessageCreate( @SerialName(EventIntents.GuildMessages.MESSAGE_DELETE_TYPE) public data class MessageDelete( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: Unit /* TODO 文档没找到描述。 */ ) : Signal.Dispatch() @@ -125,7 +125,7 @@ public data class MessageDelete( @SerialName(EventIntents.MessageAudit.MESSAGE_AUDIT_PASS_TYPE) public data class MessageAuditPass( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: MessageAudited ) : MessageAuditedDispatch() @@ -141,6 +141,6 @@ public data class MessageAuditPass( @SerialName(EventIntents.MessageAudit.MESSAGE_AUDIT_REJECT_TYPE) public data class MessageAuditReject( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: MessageAudited ) : MessageAuditedDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt index 7ddd3527..5e6b49fa 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt @@ -120,7 +120,7 @@ public sealed class OpenForumThreadDispatch : OpenForumDispatch() { @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_CREATE_TYPE) public data class OpenForumThreadCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: OpenForumThreadData ) : OpenForumThreadDispatch() @@ -134,7 +134,7 @@ public data class OpenForumThreadCreate( @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_UPDATE_TYPE) public data class OpenForumThreadUpdate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: OpenForumThreadData ) : OpenForumThreadDispatch() @@ -148,7 +148,7 @@ public data class OpenForumThreadUpdate( @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_DELETE_TYPE) public data class OpenForumThreadDelete( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: OpenForumThreadData ) : OpenForumThreadDispatch() @@ -188,7 +188,7 @@ public sealed class OpenForumPostDispatch : OpenForumDispatch() { @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_POST_CREATE_TYPE) public data class OpenForumPostCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: OpenForumPostData ) : OpenForumPostDispatch() @@ -202,7 +202,7 @@ public data class OpenForumPostCreate( @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_POST_DELETE_TYPE) public data class OpenForumPostDelete( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: OpenForumPostData ) : OpenForumPostDispatch() @@ -241,7 +241,7 @@ public sealed class OpenForumReplyDispatch : OpenForumDispatch() { @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_REPLY_CREATE_TYPE) public data class OpenForumReplyCreate( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: OpenForumReplyData ) : OpenForumReplyDispatch() @@ -255,7 +255,7 @@ public data class OpenForumReplyCreate( @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_REPLY_DELETE_TYPE) public data class OpenForumReplyDelete( override val id: String? = null, - override val s: Long, + override val s: Long? = null, @SerialName("d") override val data: OpenForumReplyData ) : OpenForumReplyDispatch() diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt index 76986b67..08bb683e 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt @@ -23,6 +23,7 @@ import io.ktor.client.statement.* import love.forte.simbot.ability.EventMentionAware import love.forte.simbot.bot.Bot import love.forte.simbot.bot.ContactRelation +import love.forte.simbot.common.function.Action import love.forte.simbot.common.id.ID import love.forte.simbot.common.id.StringID.Companion.ID import love.forte.simbot.common.id.literal @@ -48,9 +49,9 @@ import love.forte.simbot.qguild.api.QQGuildApi import love.forte.simbot.qguild.api.files.UploadGroupFilesApi import love.forte.simbot.qguild.api.files.UploadUserFilesApi import love.forte.simbot.qguild.api.message.GetMessageApi -import love.forte.simbot.qguild.stdlib.requestBy -import love.forte.simbot.qguild.stdlib.requestDataBy -import love.forte.simbot.qguild.stdlib.requestTextBy +import love.forte.simbot.qguild.event.Opcode +import love.forte.simbot.qguild.event.Signal +import love.forte.simbot.qguild.stdlib.* import love.forte.simbot.suspendrunner.ST import love.forte.simbot.suspendrunner.STP import kotlin.jvm.JvmSynthetic @@ -422,4 +423,59 @@ public interface QGBot : Bot, EventMentionAware { val data = executeData(api) return QGMessageContentImpl(this, data) } + + + /** + * 主动推送一个事件原文。 + * 可用于在 webhook 模式下推送事件。 + * + * @param payload 接收到的事件推送的JSON格式正文字符串。 + * @param options 额外提供的属性或配置。默认为 `null`。 + * @param onVerified 如果事件是验证事件 + * ([Opcode.CallbackVerify]), + * 且验证成功后,则通过 [onVerified] 回调结果。 + * + * @throws IllegalArgumentException 参考: + * - [EmitEventOptions.ignoreUnknownOpcode] + * - [EmitEventOptions.ignoreMissingOpcode] + * + * @see QGSourceBot.emitEvent + * + * @since 4.1.0 + */ + @ST + public suspend fun emitEvent( + payload: String, + options: EmitEventOptions? = null, + onVerified: Action? = null + ) { + source.emitEvent(payload, options, onVerified) + } + + /** + * 主动推送一个事件原文。 + * 可用于在 webhook 模式下推送事件。 + * + * @param payload 接收到的事件推送的JSON格式正文字符串。 + * + * @see QGSourceBot.emitEvent + * @since 4.1.0 + */ + @ST + public suspend fun emitEvent(payload: String) { + source.emitEvent(payload) + } +} + +/** + * 使用 [QGBot.emitEvent] 推送一个外部事件,并且在 [block] 中配置 [EmitEventOptions]。 + * @see QGBot.emitEvent + * @since 4.1.0 + */ +public suspend inline fun QGBot.emitEvent( + payload: String, + onVerified: Action? = null, + block: EmitEventOptions.() -> Unit +) { + source.emitEvent(payload, onVerified, block) } diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QQGuildBotManager.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QQGuildBotManager.kt index 02358d9b..384697b1 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QQGuildBotManager.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QQGuildBotManager.kt @@ -26,6 +26,7 @@ import love.forte.simbot.bot.UnsupportedBotConfigurationException import love.forte.simbot.common.coroutines.linkTo import love.forte.simbot.common.function.ConfigurerFunction import love.forte.simbot.common.function.invokeBy +import love.forte.simbot.common.id.ID import love.forte.simbot.component.NoSuchComponentException import love.forte.simbot.component.find import love.forte.simbot.component.qguild.QQGuildComponent @@ -58,6 +59,12 @@ public interface QQGuildBotManager : BotManager { public val eventDispatcher: EventDispatcher public val configuration: QQGuildBotManagerConfiguration + override fun get(id: ID): QGBot + + override fun all(): Sequence + + override fun all(id: ID): Sequence + @OptIn(ExperimentalContracts::class) private fun checkConfig(configuration: SerializableBotConfiguration): Boolean { contract { diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/config/QGBotFileConfiguration.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/config/QGBotFileConfiguration.kt index 3840f8fc..e73df2f2 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/config/QGBotFileConfiguration.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/config/QGBotFileConfiguration.kt @@ -18,7 +18,6 @@ package love.forte.simbot.component.qguild.bot.config import io.ktor.http.* -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import love.forte.simbot.bot.SerializableBotConfiguration @@ -107,7 +106,6 @@ public data class QGBotFileConfiguration( * ``` * */ - @OptIn(ExperimentalSerializationApi::class) @Serializable @UsedOnlyForConfigSerialization public data class Config( @@ -241,7 +239,14 @@ public data class QGBotFileConfiguration( * @see DispatcherConfiguration */ @SerialName("dispatcher") public val dispatcherConfiguration: DispatcherConfiguration? = null, + ) { + /** + * 是否禁用 ws + * @since 4.1.0 + */ + public var disableWs: Boolean? = null + public companion object { internal const val SERVER_URL_SANDBOX_VALUE: String = "SANDBOX" private val DEFAULT = QGBotComponentConfiguration() @@ -302,6 +307,7 @@ public data class QGBotFileConfiguration( configuration.coroutineContext += dispatcher } + disableWs?.also { disableWs -> configuration.disableWs = disableWs } } } } diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.kt index 7f3eecae..dcff66ef 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/internal/bot/QGBotImpl.kt @@ -408,6 +408,7 @@ internal class QGBotImpl( source.start().also { // set everytime. + logger.debug("Initial @me for {}", source.ticket.appId) botSelf = me().also { me -> logger.debug("bot own information: {}", me) } diff --git a/simbot-component-qq-guild-stdlib/build.gradle.kts b/simbot-component-qq-guild-stdlib/build.gradle.kts index f1ac8b5a..6be59c1a 100644 --- a/simbot-component-qq-guild-stdlib/build.gradle.kts +++ b/simbot-component-qq-guild-stdlib/build.gradle.kts @@ -66,14 +66,17 @@ kotlin { api(libs.ktor.client.contentNegotiation) api(libs.ktor.serialization.kotlinxJson) api(libs.ktor.client.ws) + + // https://github.com/andreypfau/curve25519-kotlin + implementation("io.github.andreypfau:curve25519-kotlin:0.0.8") } commonTest.dependencies { implementation(kotlin("test")) implementation(libs.kotlinx.coroutines.test) implementation(libs.ktor.client.mock) - // https://github.com/andreypfau/curve25519-kotlin - // implementation("com.diglol.crypto:pkc:0.2.0") + // https://github.com/diglol/crypto +// implementation("com.diglol.crypto:pkc:0.2.0") } jvmTest.dependencies { diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt index c3f5a66d..345c5b82 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt @@ -23,9 +23,12 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.serialization.json.Json +import love.forte.simbot.common.function.Action import love.forte.simbot.qguild.api.GatewayInfo import love.forte.simbot.qguild.api.user.GetBotInfoApi +import love.forte.simbot.qguild.event.Opcode import love.forte.simbot.qguild.event.Shard +import love.forte.simbot.qguild.event.Signal import love.forte.simbot.qguild.model.User import love.forte.simbot.suspendrunner.ST import love.forte.simbot.suspendrunner.STP @@ -171,6 +174,9 @@ public interface Bot : CoroutineScope { * * @param payload 接收到的事件推送的JSON格式正文字符串。 * @param options 额外提供的属性或配置。默认为 `null`。 + * @param onVerified 如果事件是验证事件 + * ([Opcode.CallbackVerify]), + * 且验证成功后,则通过 [onVerified] 回调结果。 * * @throws IllegalArgumentException 参考: * - [EmitEventOptions.ignoreUnknownOpcode] @@ -179,7 +185,11 @@ public interface Bot : CoroutineScope { * @since 4.1.0 */ @ST - public suspend fun emitEvent(payload: String, options: EmitEventOptions? = null) + public suspend fun emitEvent( + payload: String, + options: EmitEventOptions? = null, + onVerified: Action? = null + ) /** * 主动推送一个事件原文。 @@ -191,7 +201,7 @@ public interface Bot : CoroutineScope { */ @ST public suspend fun emitEvent(payload: String) { - emitEvent(payload, null) + emitEvent(payload, null, null) } /** @@ -280,8 +290,12 @@ public enum class SubscribeSequence { * @see Bot.emitEvent * @since 4.1.0 */ -public suspend inline fun Bot.emitEvent(payload: String, block: EmitEventOptions.() -> Unit) { - emitEvent(payload, EmitEventOptions().apply(block)) +public suspend inline fun Bot.emitEvent( + payload: String, + onVerified: Action? = null, + block: EmitEventOptions.() -> Unit +) { + emitEvent(payload, EmitEventOptions().apply(block), onVerified) } /** @@ -304,11 +318,8 @@ public class EmitEventOptions { public var ignoreMissingOpcode: Boolean = false /** - * 如果需要对此回调事件进行签名校验, - * 则通过 [signatureVerifier] 配置校验器。 - * - * 校验器提供一个基于 [Bot.Ticket.secret] 进行校验的函数, - * 如果出现任何不匹配的错误结果,直接抛出一个运行时异常即可。 + * 如果需要对此回调事件进行 ed25519 签名校验, + * 则配置它所需的请求头透传参数。 * * 如果你想要以自己的逻辑提前校验则可设为 `null`, * 如果为 `null` 则不会进行校验。 @@ -317,23 +328,23 @@ public class EmitEventOptions { * * 更多参考 [官方文档](https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html) */ - public var signatureVerifier: SignatureVerifier? = null + public var ed25519SignatureVerification: Ed25519SignatureVerification? = null } /** - * 一个使用 [Bot.Ticket.secret] 进行校验的签名校验器。 + * 进行 Ed25519 签名校验所需的参数。 * - * secret 是敏感信息,请只使用可信任的实现以确保机密信息不被泄露。 + * 更多参考 [官方文档](https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html) * * @since 4.1.0 */ -public interface SignatureVerifier { +public data class Ed25519SignatureVerification( /** - * 根据 bot 的 [secret] 进行校验。 - * 如果校验不通过,则直接抛出所需的异常,未出现异常即视为校验成功。 - * - * @param payload 推送事件的JSON体正文 - * @param secret bot 配置的 [Bot.Ticket.secret] + * 来自请求头中的 `X-Signature-Ed25519`. */ - public fun verify(payload: String, secret: String) -} + val signatureEd25519: String, + /** + * 来自请求头中的 `X-Signature-Timestamp`. + */ + val signatureTimestamp: String, +) diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/BotConfiguration.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/BotConfiguration.kt index 1983984f..884f2f84 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/BotConfiguration.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/BotConfiguration.kt @@ -142,6 +142,8 @@ public interface BotConfiguration { /** * 是否禁用 ws 连接。如果你打算使用 webhook,则设置为 `true`, * 届时在启动 bot 时不会再连接 ws 服务。 + * + * @since 4.1.0 */ public val disableWs: Boolean diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/ConfigurableBotConfiguration.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/ConfigurableBotConfiguration.kt index 9770ec9a..d5c3805f 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/ConfigurableBotConfiguration.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/ConfigurableBotConfiguration.kt @@ -171,7 +171,7 @@ public class ConfigurableBotConfiguration : BotConfiguration { */ override var wsClientEngineFactory: HttpClientEngineFactory<*>? = null - override val disableWs: Boolean = false + override var disableWs: Boolean = false /** * 用于API请求结果反序列化的 [Json]. diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt index 1e65da52..4d7ae3b4 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt @@ -17,12 +17,14 @@ package love.forte.simbot.qguild.stdlib.internal +import io.github.andreypfau.curve25519.ed25519.Ed25519 import io.ktor.client.* import io.ktor.client.plugins.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.websocket.* import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* +import io.ktor.utils.io.core.* import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -35,6 +37,7 @@ import love.forte.simbot.common.atomic.atomic import love.forte.simbot.common.collection.ConcurrentQueue import love.forte.simbot.common.collection.ExperimentalSimbotCollectionApi import love.forte.simbot.common.collection.createConcurrentQueue +import love.forte.simbot.common.function.Action import love.forte.simbot.common.stageloop.loop import love.forte.simbot.common.weak.WeakRef import love.forte.simbot.common.weak.weakRef @@ -88,9 +91,7 @@ internal class BotImpl( logger.warn("Bot(appId={}) intents value is ZERO", ticket.appId) } - if (!configuration.disableWs) { - checkTicketSecret() - } + checkTicketSecret() } private fun checkTicketSecret() { @@ -106,6 +107,11 @@ internal class BotImpl( } } + private val ed25519PrivateKey = + Ed25519.keyFromSeed(ticket.secret.paddingEd25519Seed().toByteArray()) + + private val ed25519PublicKey = ed25519PrivateKey.publicKey() + internal val eventDecoder = Signal.Dispatch.dispatchJson { isLenient = true ignoreUnknownKeys = true @@ -302,19 +308,20 @@ internal class BotImpl( flushAccessTokenJob = initFlushAccessTokenJob() } - if (!configuration.disableWs) { + if (wsClient != null) { val gateway = gatewayFactory() logger.debug("Request gateway {} by shard {}", gateway, shard) - this.stageLoopJob = launchWsLoop(gateway) + this.stageLoopJob = launchWsLoop(wsClient, gateway) + } else { + logger.debug("WsClient is null, will not connect to ws server.") } - } } - private suspend fun launchWsLoop(gateway: GatewayInfo): Job { - val state = Connect(this, shard, gateway, wsClient!!) + private suspend fun launchWsLoop(wsClient: HttpClient, gateway: GatewayInfo): Job { + val state = Connect(this, shard, gateway, wsClient) logger.debug("Create state loop {}", state) @@ -400,17 +407,55 @@ internal class BotImpl( } } - override suspend fun emitEvent(payload: String, options: EmitEventOptions?) { + @OptIn(ExperimentalStdlibApi::class) + override suspend fun emitEvent( + payload: String, + options: EmitEventOptions?, + onVerified: Action? + ) { // CODE 名称 客户端行为 描述 // 0 Dispatch Receive 服务端进行消息推送 // 13 回调地址验证 Receive 开放平台对机器人服务端进行验证 logger.debug("Emit raw event with payload: {}", payload) val json = eventDecoder.parseToJsonElement(payload) - fun signatureVerifyIfNecessary() { - val signatureValidator = options?.signatureVerifier ?: return + fun verifyIfNecessary() { + val (signature, signatureTimestamp) = options?.ed25519SignatureVerification ?: return + logger.debug("`ed25519SignatureVerification` exists, verity the payload") + + val signatureBytes = signature.hexToByteArray() + + check(Ed25519.SIGNATURE_SIZE_BYTES == signatureBytes.size) { + "Invalid signature hex size, " + + "expect ${Ed25519.SIGNATURE_SIZE_BYTES}, " + + "actual ${signatureBytes.size}" + } + + val and: UByte = signatureBytes[63].toUByte().and(224u) + + check(and == 0u.toUByte()) { + "Invalid signature hex, " + + "expect signatureBytes[63] && 224 == 0, " + + "actual $and (0x${and.toHexString()})" + } + + val msg = "$signatureTimestamp$payload" + + check(ed25519PublicKey.verify(msg.toByteArray(), signatureBytes)) { + "Ed25519 verify failed" + } + } + + fun signatureIfNecessary(verify: Signal.CallbackVerify) { + verifyIfNecessary() // verify itself first. + val callback = onVerified ?: return - signatureValidator.verify(payload, ticket.secret) + val (plainToken, eventTs) = verify.data + val msg = "$eventTs$plainToken" + + val signature = ed25519PrivateKey.sign(msg.toByteArray()) + + callback.invoke(Signal.CallbackVerify.Verified(plainToken, signature.toHexString())) } @@ -422,7 +467,7 @@ internal class BotImpl( } Opcodes.Dispatch -> { - signatureVerifyIfNecessary() + verifyIfNecessary() val dispatch = try { eventDecoder.decodeFromJsonElement(Signal.Dispatch.serializer(), json) @@ -435,7 +480,7 @@ internal class BotImpl( kotlin.runCatching { json.jsonObject[Signal.Dispatch.DISPATCH_CLASS_DISCRIMINATOR]?.jsonPrimitive?.content } .getOrNull() if (tryCheckIsPolymorphicException(serEx)) { - logger.warn("Unknown event type {}, decode it as Unknown event: {}", t, it) + logger.warn("Unknown event type {} in polymorphic, decode it as Unknown event: {}", t, it) } else { logger.warn("Unknown event type {}, decode it as Unknown event: {}", t, it, serEx) } @@ -446,11 +491,9 @@ internal class BotImpl( } Opcodes.CallbackVerify -> { - signatureVerifyIfNecessary() - - // TODO return sign - - TODO("CallbackVerify") + val verify = eventDecoder.decodeFromJsonElement(Signal.CallbackVerify.serializer(), json) + logger.debug("On Signal.CallbackVerify: {}", verify) + signatureIfNecessary(verify) } else -> { @@ -536,6 +579,7 @@ internal data class SessionInfo( @OptIn(ExperimentalSimbotCollectionApi::class) internal suspend fun BotImpl.emitEvent(dispatch: Signal.Dispatch, raw: String) { + logger.debug("Emit event {} from raw {}", dispatch, raw) // 先顺序地使用 preProcessor 处理 preProcessorQueue.forEach { processor -> runCatching { @@ -568,3 +612,27 @@ internal suspend fun BotImpl.emitEvent(dispatch: Signal.Dispatch, raw: String) { } } } + + +/** + * Repeat to `length` == 32 + */ +private fun String.paddingEd25519Seed(): String { + return when { + length == 32 -> this + length > 32 -> substring(32) + else -> { + buildString(32) { + append(this@paddingEd25519Seed) + while (length < 32) { + append( + this@paddingEd25519Seed, + 0, + kotlin.math.min(32 - length, this@paddingEd25519Seed.length) + ) + } + } + + } + } +} diff --git a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt index 6099b199..327ac1f6 100644 --- a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt +++ b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt @@ -1,43 +1,105 @@ -//import diglol.crypto.Ed25519 -//import io.ktor.utils.io.core.* -//import kotlinx.coroutines.test.runTest -//import kotlin.test.Test -//import kotlin.test.assertContentEquals -// -// -///** -// * -// * @author ForteScarlet -// */ -//class Ed25519Tests { -// -// /** -// * https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html -// * -// * 验证签名过程: -// * 根据开发者平台的 Bot Secret 值进行repeat操作得到签名32字节的 seed , -// * 根据 seed 调用 Ed25519 算法生成32字节公钥 -// */ -// @OptIn(ExperimentalStdlibApi::class) -// // @Test -// fun ed25519Test() = runTest { -// val secret = "naOC0ocQE3shWLAfffVLB1rhYPG7" -// val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" -// val pair = Ed25519.generateKeyPair(seed.toByteArray()) -// println("publicKey: " + pair.publicKey.joinToString { it.toUByte().toString() }) -// println("privateKey: " + pair.privateKey.joinToString { it.toUByte().toString() }) -// -// assertContentEquals( -// "d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" -// .hexToByteArray(), -// pair.publicKey, -// ) -// -// assertContentEquals( -// "6e614f43306f635145337368574c41666666564c42317268595047376e614f43d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" -// .hexToByteArray(), -// pair.privateKey, -// ) -// } -// -//} +import io.github.andreypfau.curve25519.ed25519.Ed25519 +import io.ktor.utils.io.core.* +import love.forte.simbot.qguild.stdlib.paddingEd25519Seed +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * + * @author ForteScarlet + */ +class Ed25519TestsCommon { + + /** + * https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html + * + * 验证签名过程: + * 根据开发者平台的 Bot Secret 值进行repeat操作得到签名32字节的 seed , + * 根据 seed 调用 Ed25519 算法生成32字节公钥 + */ + @OptIn(ExperimentalStdlibApi::class) + @Test + fun ed25519KeyGenTest() { + // val secret = "naOC0ocQE3shWLAfffVLB1rhYPG7" + val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" + + val pk = Ed25519.keyFromSeed(seed.toByteArray()) + + assertContentEquals( + ASSERT_PUBLIC_HEX.hexToByteArray(), + pk.publicKey().toByteArray() + ) + + assertContentEquals( + ASSERT_PRIVATE_HEX.hexToByteArray(), + pk.toByteArray() + ) + } + + @Test + fun ed25519SeedPadding() { + assertEquals( + "DG5g3B4j9X2KOErGDG5g3B4j9X2KOErG", + "DG5g3B4j9X2KOErG".paddingEd25519Seed() + ) + assertEquals( + "DG5g3B4j9X2KOErGDG5g3B4j9X2KOErG", + "DG5g3B4j9X2KOErGDG5g3B4j9X2KOErGDG5g3B4j9X2KOErGDG5g3B4j9X2KOErG".paddingEd25519Seed() + ) + } + + // is OK + @OptIn(ExperimentalStdlibApi::class) + private fun a() { + val secret = "DG5g3B4j9X2KOErG" + val seed = secret.paddingEd25519Seed() + val body = """{"d":{"plain_token":"Arq0D5A61EgUu4OxUvOp","event_ts":"1725442341"},"op":13}""" + val timestamp = "1725442341" + val sig = + "865ad13a61752ca65e26bde6676459cd36cf1be609375b37bd62af366e1dc25a8dc789ba7f14e017ada3d554c671a911bfdf075ba54835b23391d509579ed002" + + + val sigBytes = sig.hexToByteArray() + + assertEquals(Ed25519.SIGNATURE_SIZE_BYTES, sigBytes.size) + assertEquals(0u, sigBytes[63].toUByte().and(224u)) + + val pk = Ed25519.keyFromSeed(seed.toByteArray()) + val msg = "$timestamp$body" + + println(pk.sign(msg.toByteArray()).toHexString()) + println(pk.sign(msg.toByteArray()).toHexString()) + + assertTrue { pk.publicKey().verify(msg.toByteArray(), sigBytes) } + } + + @OptIn(ExperimentalStdlibApi::class) + @Test + fun ed25519VerifyTest() { + // val appId = "11111111" + val secret = "DG5g3B4j9X2KOErG" + val seed = secret.paddingEd25519Seed() + val plainToken = "Arq0D5A61EgUu4OxUvOp" + val ts = "1725442341" + val body = """{"d":{"plain_token":"Arq0D5A61EgUu4OxUvOp","event_ts":"1725442341"},"op":13}""" + + val pk = Ed25519.keyFromSeed(seed.toByteArray()) + val msg = "$ts$plainToken" + + val signature = pk.sign(msg.toByteArray()) + + assertEquals( + "87befc99c42c651b3aac0278e71ada338433ae26fcb24307bdc5ad38c1adc2d01bcfcadc0842edac85e85205028a1132afe09280305f13aa6909ffc2d652c706", + signature.toHexString() + ) + } + + companion object { + private const val ASSERT_PUBLIC_HEX = "d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" + private const val ASSERT_PRIVATE_HEX = + "6e614f43306f635145337368574c41666666564c42317268595047376e614f43d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" + } +} + diff --git a/simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt b/simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt index f9c6f017..a87f01b7 100644 --- a/simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt +++ b/simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt @@ -1,6 +1,8 @@ +import java.security.KeyFactory import java.security.KeyPairGenerator import java.security.SecureRandom import java.security.Signature +import java.security.spec.EdECPrivateKeySpec import java.security.spec.NamedParameterSpec import kotlin.test.Test import kotlin.test.assertContentEquals @@ -13,7 +15,6 @@ import kotlin.test.assertTrue */ class Ed25519Tests4J { @OptIn(ExperimentalStdlibApi::class) - @Test fun a() { "110 97 79 67 48 111 99 81 69 51 115 104 87 76 65 102 102 102 86 76 66 49 114 104 89 80 71 55 110 97 79 67 215 195 98 254 120 174 248 31 242 50 135 180 147 98 139 93 176 42 60 79 227 11 33 94 77 25 96 155 93 118 103 58" .split(" ") @@ -29,26 +30,28 @@ class Ed25519Tests4J { // https://openjdk.org/jeps/339 // https://howtodoinjava.com/java15/java-eddsa-example/ @OptIn(ExperimentalStdlibApi::class) - // @Test + @Test fun ed25519KeyGenTest() { val secret = "naOC0ocQE3shWLAfffVLB1rhYPG7" val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" println(System.getProperty("java.version")) + val kf = KeyFactory.getInstance("Ed25519") + val privateKey = kf.generatePrivate(EdECPrivateKeySpec(NamedParameterSpec.ED25519, seed.toByteArray())) + + // sun.security.ec.ed.EdDSAKeyPairGenerator.Ed25519 val kpg = KeyPairGenerator.getInstance("Ed25519") + println(kpg::class) kpg.initialize(NamedParameterSpec.ED25519, SecureRandom(seed.toByteArray())) val kp = kpg.generateKeyPair() - assertContentEquals( - ASSERT_PUBLIC_HEX.hexToByteArray(), - kp.public.encoded, - ) assertContentEquals( ASSERT_PRIVATE_HEX.hexToByteArray(), - kp.private.encoded, +// kp.private.encoded, + privateKey.encoded, ) } @@ -56,10 +59,11 @@ class Ed25519Tests4J { // @Test TODO fun ed25519VerifyTest() { val secret = "naOC0ocQE3shWLAfffVLB1rhYPG7" - val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" + val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" val body = """{"op":0,"d":{},"t":"GATEWAY_EVENT_NAME"}""" val timestamp = "1725442341" - val sig = "865ad13a61752ca65e26bde6676459cd36cf1be609375b37bd62af366e1dc25a8dc789ba7f14e017ada3d554c671a911bfdf075ba54835b23391d509579ed002" + val sig = + "865ad13a61752ca65e26bde6676459cd36cf1be609375b37bd62af366e1dc25a8dc789ba7f14e017ada3d554c671a911bfdf075ba54835b23391d509579ed002" // 获取 HTTP Header 中 X-Signature-Ed25519 的值进行 hec (十六进制解码)操作后的得到 Signature 并进行校验 val kpg = KeyPairGenerator.getInstance("Ed25519") From 569445a564463496aafc921eae3bdd362cc54a68 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Wed, 16 Oct 2024 23:01:59 +0800 Subject: [PATCH 11/28] fix CI --- .../simbot/qguild/stdlib/internal/BotImpl.kt | 2 +- .../src/commonTest/kotlin/Ed25519Tests.kt | 4 +- .../src/jvmTest/kotlin/Ed25519Tests.kt | 95 ------------------- 3 files changed, 3 insertions(+), 98 deletions(-) delete mode 100644 simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt index 4d7ae3b4..98ca0286 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt @@ -617,7 +617,7 @@ internal suspend fun BotImpl.emitEvent(dispatch: Signal.Dispatch, raw: String) { /** * Repeat to `length` == 32 */ -private fun String.paddingEd25519Seed(): String { +internal fun String.paddingEd25519Seed(): String { return when { length == 32 -> this length > 32 -> substring(32) diff --git a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt index 327ac1f6..22508ccf 100644 --- a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt +++ b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt @@ -1,6 +1,6 @@ import io.github.andreypfau.curve25519.ed25519.Ed25519 import io.ktor.utils.io.core.* -import love.forte.simbot.qguild.stdlib.paddingEd25519Seed +import love.forte.simbot.qguild.stdlib.internal.paddingEd25519Seed import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals @@ -83,7 +83,7 @@ class Ed25519TestsCommon { val seed = secret.paddingEd25519Seed() val plainToken = "Arq0D5A61EgUu4OxUvOp" val ts = "1725442341" - val body = """{"d":{"plain_token":"Arq0D5A61EgUu4OxUvOp","event_ts":"1725442341"},"op":13}""" + // val body = """{"d":{"plain_token":"Arq0D5A61EgUu4OxUvOp","event_ts":"1725442341"},"op":13}""" val pk = Ed25519.keyFromSeed(seed.toByteArray()) val msg = "$ts$plainToken" diff --git a/simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt b/simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt deleted file mode 100644 index a87f01b7..00000000 --- a/simbot-component-qq-guild-stdlib/src/jvmTest/kotlin/Ed25519Tests.kt +++ /dev/null @@ -1,95 +0,0 @@ -import java.security.KeyFactory -import java.security.KeyPairGenerator -import java.security.SecureRandom -import java.security.Signature -import java.security.spec.EdECPrivateKeySpec -import java.security.spec.NamedParameterSpec -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertTrue - - -/** - * - * @author ForteScarlet - */ -class Ed25519Tests4J { - @OptIn(ExperimentalStdlibApi::class) - fun a() { - "110 97 79 67 48 111 99 81 69 51 115 104 87 76 65 102 102 102 86 76 66 49 114 104 89 80 71 55 110 97 79 67 215 195 98 254 120 174 248 31 242 50 135 180 147 98 139 93 176 42 60 79 227 11 33 94 77 25 96 155 93 118 103 58" - .split(" ") - .map { it.toUByte() } - .map { it.toByte() } - .toByteArray() - .toHexString().also { - println(it) - } - } - - - // https://openjdk.org/jeps/339 - // https://howtodoinjava.com/java15/java-eddsa-example/ - @OptIn(ExperimentalStdlibApi::class) - @Test - fun ed25519KeyGenTest() { - val secret = "naOC0ocQE3shWLAfffVLB1rhYPG7" - val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" - - println(System.getProperty("java.version")) - - val kf = KeyFactory.getInstance("Ed25519") - val privateKey = kf.generatePrivate(EdECPrivateKeySpec(NamedParameterSpec.ED25519, seed.toByteArray())) - - // sun.security.ec.ed.EdDSAKeyPairGenerator.Ed25519 - val kpg = KeyPairGenerator.getInstance("Ed25519") - println(kpg::class) - kpg.initialize(NamedParameterSpec.ED25519, SecureRandom(seed.toByteArray())) - - val kp = kpg.generateKeyPair() - - - assertContentEquals( - ASSERT_PRIVATE_HEX.hexToByteArray(), -// kp.private.encoded, - privateKey.encoded, - ) - } - - @OptIn(ExperimentalStdlibApi::class) - // @Test TODO - fun ed25519VerifyTest() { - val secret = "naOC0ocQE3shWLAfffVLB1rhYPG7" - val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" - val body = """{"op":0,"d":{},"t":"GATEWAY_EVENT_NAME"}""" - val timestamp = "1725442341" - val sig = - "865ad13a61752ca65e26bde6676459cd36cf1be609375b37bd62af366e1dc25a8dc789ba7f14e017ada3d554c671a911bfdf075ba54835b23391d509579ed002" - - // 获取 HTTP Header 中 X-Signature-Ed25519 的值进行 hec (十六进制解码)操作后的得到 Signature 并进行校验 - val kpg = KeyPairGenerator.getInstance("Ed25519") - kpg.initialize(NamedParameterSpec.ED25519, SecureRandom(seed.toByteArray())) - val kp = kpg.generateKeyPair() - // 获取 HTTP Header 中 X-Signature-Timestamp 的和 HTTP Body 的值按照 timestamp+body 顺序进行组合成签名体msg - - // algorithm is pure Ed25519 - val signature = Signature.getInstance("Ed25519") - signature.initVerify(kp.public) - signature.update("$timestamp$body".toByteArray()) - assertTrue(signature.verify(sig.hexToByteArray())) -// signature.initSign(kp.private) -// signature.initSign(kp.private) -// signature.update("$timestamp$body".toByteArray()) -// val s: ByteArray = signature.sign() -// s.toHexString() - - - // 根据公钥、Signature、签名体调用 Ed25519 算法进行验证 - - - } - -} - -private const val ASSERT_PUBLIC_HEX = "d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" -private const val ASSERT_PRIVATE_HEX = - "6e614f43306f635145337368574c41666666564c42317268595047376e614f43d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" From adacd568ea49c224e74c7c6c656c3f87d8f4a434 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Wed, 16 Oct 2024 23:18:03 +0800 Subject: [PATCH 12/28] Apply apiDump and fix api --- buildSrc/src/main/kotlin/JVMConstants.kt | 5 +- .../api/simbot-component-qq-guild-api.api | 91 +++++++++++++++++++ .../forte/simbot/qguild/event/EventIntents.kt | 4 +- .../love/forte/simbot/qguild/event/Signal.kt | 9 +- .../simbot/qguild/event/c2cManagements.kt | 8 +- .../forte/simbot/qguild/event/c2cMessages.kt | 4 +- .../forte/simbot/qguild/event/channels.kt | 6 +- .../love/forte/simbot/qguild/event/forums.kt | 16 ++-- .../simbot/qguild/event/groupManagements.kt | 8 +- .../love/forte/simbot/qguild/event/guilds.kt | 6 +- .../love/forte/simbot/qguild/event/members.kt | 6 +- .../forte/simbot/qguild/event/messages.kt | 14 +-- .../forte/simbot/qguild/event/openForums.kt | 14 +-- .../api/simbot-component-qq-guild-core.api | 24 +++++ .../api/simbot-component-qq-guild-stdlib.api | 44 +++++++++ 15 files changed, 210 insertions(+), 49 deletions(-) diff --git a/buildSrc/src/main/kotlin/JVMConstants.kt b/buildSrc/src/main/kotlin/JVMConstants.kt index 9eada42a..a1daba86 100644 --- a/buildSrc/src/main/kotlin/JVMConstants.kt +++ b/buildSrc/src/main/kotlin/JVMConstants.kt @@ -16,7 +16,6 @@ */ object JVMConstants { - // TODO - const val KT_JVM_TARGET_VALUE = 17 - const val KT_JVM_TARGET = "17" + const val KT_JVM_TARGET_VALUE = 11 + const val KT_JVM_TARGET = "11" } diff --git a/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api b/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api index f8b6bb27..57664530 100644 --- a/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api +++ b/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api @@ -3712,6 +3712,10 @@ public abstract class love/forte/simbot/qguild/event/Opcode { public final fun isSend ()Z } +public final class love/forte/simbot/qguild/event/Opcode$CallbackVerify : love/forte/simbot/qguild/event/Opcode, love/forte/simbot/qguild/event/ReceiveAble { + public static final field INSTANCE Llove/forte/simbot/qguild/event/Opcode$CallbackVerify; +} + public final class love/forte/simbot/qguild/event/Opcode$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } @@ -3758,6 +3762,7 @@ public final class love/forte/simbot/qguild/event/Opcode$SerializerByCode : kotl } public final class love/forte/simbot/qguild/event/Opcodes { + public static final field CallbackVerify I public static final field Dispatch I public static final field Heartbeat I public static final field HeartbeatACK I @@ -4275,6 +4280,92 @@ public abstract class love/forte/simbot/qguild/event/Signal { public static final synthetic fun write$Self (Llove/forte/simbot/qguild/event/Signal;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;Lkotlinx/serialization/KSerializer;)V } +public final class love/forte/simbot/qguild/event/Signal$CallbackVerify : love/forte/simbot/qguild/event/Signal { + public static final field Companion Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Companion; + public fun (Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data;)V + public final fun component1 ()Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data; + public final fun copy (Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data;)Llove/forte/simbot/qguild/event/Signal$CallbackVerify; + public static synthetic fun copy$default (Llove/forte/simbot/qguild/event/Signal$CallbackVerify;Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data;ILjava/lang/Object;)Llove/forte/simbot/qguild/event/Signal$CallbackVerify; + public fun equals (Ljava/lang/Object;)Z + public synthetic fun getData ()Ljava/lang/Object; + public fun getData ()Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public synthetic class love/forte/simbot/qguild/event/Signal$CallbackVerify$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Llove/forte/simbot/qguild/event/Signal$CallbackVerify$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Llove/forte/simbot/qguild/event/Signal$CallbackVerify; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Llove/forte/simbot/qguild/event/Signal$CallbackVerify;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class love/forte/simbot/qguild/event/Signal$CallbackVerify$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class love/forte/simbot/qguild/event/Signal$CallbackVerify$Data { + public static final field Companion Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data; + public static synthetic fun copy$default (Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data; + public fun equals (Ljava/lang/Object;)Z + public final fun getEventTs ()Ljava/lang/String; + public final fun getPlainToken ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public synthetic class love/forte/simbot/qguild/event/Signal$CallbackVerify$Data$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Data;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class love/forte/simbot/qguild/event/Signal$CallbackVerify$Data$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class love/forte/simbot/qguild/event/Signal$CallbackVerify$Verified { + public static final field Companion Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified; + public static synthetic fun copy$default (Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified; + public fun equals (Ljava/lang/Object;)Z + public final fun getPlainToken ()Ljava/lang/String; + public final fun getSignature ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public synthetic class love/forte/simbot/qguild/event/Signal$CallbackVerify$Verified$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class love/forte/simbot/qguild/event/Signal$CallbackVerify$Verified$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class love/forte/simbot/qguild/event/Signal$Companion { public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; } diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt index 7e0a3724..80cc431f 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt @@ -624,7 +624,7 @@ public val EventIntentsInstances: Array @SerialName(READY_TYPE) public data class Ready( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Data ) : Signal.Dispatch() { /** @@ -649,7 +649,7 @@ public data class Ready( @SerialName(RESUMED_TYPE) public data class Resumed( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: String ) : Signal.Dispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt index 771891fe..3e316ce4 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt @@ -163,14 +163,14 @@ public sealed class Signal(@Serializable(Opcode.SerializerByCode::class) publ /** * 事件序列 */ - protected abstract val s: Long? + protected abstract val s: Long public abstract val id: String? /** * 事件序列 */ - public val seq: Long get() = s ?: -1 + public val seq: Long get() = s /** * 此事件的实际本体 @@ -180,6 +180,9 @@ public sealed class Signal(@Serializable(Opcode.SerializerByCode::class) publ public companion object { + + internal const val DEFAULT_SEQ: Long = -1L + /** * [Dispatch] 使用 [Json] 进行多态解析时的类鉴别器属性名。 * @@ -215,7 +218,7 @@ public sealed class Signal(@Serializable(Opcode.SerializerByCode::class) publ */ public data class Unknown @QGInternalApi constructor( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, override val data: JsonElement, val raw: String ) : Dispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt index b0a42878..e2273a42 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt @@ -49,7 +49,7 @@ public sealed class C2CManagementDispatch : Signal.Dispatch() { @SerialName(EventIntents.GroupAndC2CEvent.FRIEND_ADD_TYPE) public data class FriendAdd( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: C2CManagementData ) : C2CManagementDispatch() @@ -63,7 +63,7 @@ public data class FriendAdd( @SerialName(EventIntents.GroupAndC2CEvent.FRIEND_DEL_TYPE) public data class FriendDel( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: C2CManagementData ) : C2CManagementDispatch() @@ -77,7 +77,7 @@ public data class FriendDel( @SerialName(EventIntents.GroupAndC2CEvent.C2C_MSG_REJECT_TYPE) public data class C2CMsgReject( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: C2CManagementData ) : C2CManagementDispatch() @@ -91,7 +91,7 @@ public data class C2CMsgReject( @SerialName(EventIntents.GroupAndC2CEvent.C2C_MSG_RECEIVE_TYPE) public data class C2CMsgReceive( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: C2CManagementData ) : C2CManagementDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt index e98026c6..b6fd768f 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt @@ -30,7 +30,7 @@ import love.forte.simbot.qguild.model.Message @SerialName(EventIntents.GroupAndC2CEvent.C2C_MESSAGE_CREATE_TYPE) public data class C2CMessageCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Data, ) : Signal.Dispatch() { @@ -74,7 +74,7 @@ public data class C2CMessageCreate( @SerialName(EventIntents.GroupAndC2CEvent.GROUP_AT_MESSAGE_CREATE_TYPE) public data class GroupAtMessageCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Data, ) : Signal.Dispatch() { diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt index 514b1d7c..398371fc 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt @@ -45,7 +45,7 @@ public sealed class ChannelDispatch : Signal.Dispatch() { @SerialName(EventIntents.Guilds.CHANNEL_CREATE_TYPE) public data class ChannelCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: EventChannel ) : ChannelDispatch() @@ -59,7 +59,7 @@ public data class ChannelCreate( @SerialName(EventIntents.Guilds.CHANNEL_UPDATE_TYPE) public data class ChannelUpdate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: EventChannel ) : ChannelDispatch() @@ -73,7 +73,7 @@ public data class ChannelUpdate( @SerialName(EventIntents.Guilds.CHANNEL_DELETE_TYPE) public data class ChannelDelete( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: EventChannel ) : ChannelDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt index 216192e4..9af460fb 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt @@ -94,7 +94,7 @@ public sealed class ForumThreadDispatch : ForumDispatch() { @SerialName(EventIntents.ForumsEvent.FORUM_THREAD_CREATE_TYPE) public data class ForumThreadCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Thread ) : ForumThreadDispatch() @@ -108,7 +108,7 @@ public data class ForumThreadCreate( @SerialName(EventIntents.ForumsEvent.FORUM_THREAD_UPDATE_TYPE) public data class ForumThreadUpdate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Thread ) : ForumThreadDispatch() @@ -122,7 +122,7 @@ public data class ForumThreadUpdate( @SerialName(EventIntents.ForumsEvent.FORUM_THREAD_DELETE_TYPE) public data class ForumThreadDelete( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Thread ) : ForumThreadDispatch() @@ -149,7 +149,7 @@ public sealed class ForumPostDispatch : ForumDispatch() { @SerialName(EventIntents.ForumsEvent.FORUM_POST_CREATE_TYPE) public data class ForumPostCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Post ) : ForumPostDispatch() @@ -163,7 +163,7 @@ public data class ForumPostCreate( @SerialName(EventIntents.ForumsEvent.FORUM_POST_DELETE_TYPE) public data class ForumPostDelete( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Post ) : ForumPostDispatch() @@ -190,7 +190,7 @@ public sealed class ForumReplyDispatch : ForumDispatch() { @SerialName(EventIntents.ForumsEvent.FORUM_REPLY_CREATE_TYPE) public data class ForumReplyCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Reply ) : ForumReplyDispatch() @@ -204,7 +204,7 @@ public data class ForumReplyCreate( @SerialName(EventIntents.ForumsEvent.FORUM_REPLY_DELETE_TYPE) public data class ForumReplyDelete( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Reply ) : ForumReplyDispatch() @@ -217,6 +217,6 @@ public data class ForumReplyDelete( @SerialName(EventIntents.ForumsEvent.FORUM_PUBLISH_AUDIT_RESULT_TYPE) public data class ForumPublishAuditResult( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: AuditResult ) : ForumDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt index 51b85280..174b5760 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt @@ -51,7 +51,7 @@ public sealed class GroupRobotManagementDispatch : Signal.Dispatch() { @SerialName(EventIntents.GroupAndC2CEvent.GROUP_ADD_ROBOT_TYPE) public data class GroupAddRobot( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: GroupRobotManagementData ) : GroupRobotManagementDispatch() @@ -65,7 +65,7 @@ public data class GroupAddRobot( @SerialName(EventIntents.GroupAndC2CEvent.GROUP_DEL_ROBOT_TYPE) public data class GroupDelRobot( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: GroupRobotManagementData ) : GroupRobotManagementDispatch() @@ -79,7 +79,7 @@ public data class GroupDelRobot( @SerialName(EventIntents.GroupAndC2CEvent.GROUP_MSG_REJECT_TYPE) public data class GroupMsgReject( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: GroupRobotManagementData ) : GroupRobotManagementDispatch() @@ -93,7 +93,7 @@ public data class GroupMsgReject( @SerialName(EventIntents.GroupAndC2CEvent.GROUP_MSG_RECEIVE_TYPE) public data class GroupMsgReceive( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: GroupRobotManagementData ) : GroupRobotManagementDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt index faec97ca..42578bab 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt @@ -43,7 +43,7 @@ public sealed class EventGuildDispatch : Signal.Dispatch() { @SerialName(EventIntents.Guilds.GUILD_CREATE_TYPE) public data class GuildCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: EventGuild ) : EventGuildDispatch() @@ -59,7 +59,7 @@ public data class GuildCreate( @SerialName(EventIntents.Guilds.GUILD_UPDATE_TYPE) public data class GuildUpdate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: EventGuild ) : EventGuildDispatch() @@ -76,7 +76,7 @@ public data class GuildUpdate( @SerialName(EventIntents.Guilds.GUILD_DELETE_TYPE) public data class GuildDelete( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: EventGuild ) : EventGuildDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt index 76c244b9..c4a6b28d 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt @@ -33,7 +33,7 @@ import love.forte.simbot.qguild.time.ZERO_ISO_INSTANT @SerialName(EventIntents.GuildMembers.GUILD_MEMBER_ADD_TYPE) public data class GuildMemberAdd( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: EventMember ) : Signal.Dispatch() @@ -46,7 +46,7 @@ public data class GuildMemberAdd( @SerialName(EventIntents.GuildMembers.GUILD_MEMBER_UPDATE_TYPE) public data class GuildMemberUpdate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: EventMember ) : Signal.Dispatch() @@ -59,7 +59,7 @@ public data class GuildMemberUpdate( @SerialName(EventIntents.GuildMembers.GUILD_MEMBER_REMOVE_TYPE) public data class GuildMemberRemove( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: EventMember ) : Signal.Dispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt index 613ffe2c..52633c78 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt @@ -46,7 +46,7 @@ public sealed class MessageDispatch : Signal.Dispatch() { @SerialName(EventIntents.PublicGuildMessages.AT_MESSAGE_CREATE_TYPE) public data class AtMessageCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Message ) : MessageDispatch() @@ -59,7 +59,7 @@ public data class AtMessageCreate( @SerialName(EventIntents.PublicGuildMessages.PUBLIC_MESSAGE_DELETE_TYPE) public data class PublicMessageDeleteCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Unit /* TODO 文档没找到描述。 */ ) : Signal.Dispatch() @@ -76,7 +76,7 @@ public data class PublicMessageDeleteCreate( @SerialName(EventIntents.DirectMessage.DIRECT_MESSAGE_CREATE_TYPE) public data class DirectMessageCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Message ) : MessageDispatch() @@ -97,7 +97,7 @@ public sealed class MessageAuditedDispatch : Signal.Dispatch() { @SerialName(EventIntents.GuildMessages.MESSAGE_CREATE_TYPE) public data class MessageCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Message ) : MessageDispatch() @@ -108,7 +108,7 @@ public data class MessageCreate( @SerialName(EventIntents.GuildMessages.MESSAGE_DELETE_TYPE) public data class MessageDelete( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: Unit /* TODO 文档没找到描述。 */ ) : Signal.Dispatch() @@ -125,7 +125,7 @@ public data class MessageDelete( @SerialName(EventIntents.MessageAudit.MESSAGE_AUDIT_PASS_TYPE) public data class MessageAuditPass( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: MessageAudited ) : MessageAuditedDispatch() @@ -141,6 +141,6 @@ public data class MessageAuditPass( @SerialName(EventIntents.MessageAudit.MESSAGE_AUDIT_REJECT_TYPE) public data class MessageAuditReject( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: MessageAudited ) : MessageAuditedDispatch() diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt index 5e6b49fa..f4dc44ad 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt @@ -120,7 +120,7 @@ public sealed class OpenForumThreadDispatch : OpenForumDispatch() { @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_CREATE_TYPE) public data class OpenForumThreadCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: OpenForumThreadData ) : OpenForumThreadDispatch() @@ -134,7 +134,7 @@ public data class OpenForumThreadCreate( @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_UPDATE_TYPE) public data class OpenForumThreadUpdate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: OpenForumThreadData ) : OpenForumThreadDispatch() @@ -148,7 +148,7 @@ public data class OpenForumThreadUpdate( @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_DELETE_TYPE) public data class OpenForumThreadDelete( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: OpenForumThreadData ) : OpenForumThreadDispatch() @@ -188,7 +188,7 @@ public sealed class OpenForumPostDispatch : OpenForumDispatch() { @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_POST_CREATE_TYPE) public data class OpenForumPostCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: OpenForumPostData ) : OpenForumPostDispatch() @@ -202,7 +202,7 @@ public data class OpenForumPostCreate( @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_POST_DELETE_TYPE) public data class OpenForumPostDelete( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: OpenForumPostData ) : OpenForumPostDispatch() @@ -241,7 +241,7 @@ public sealed class OpenForumReplyDispatch : OpenForumDispatch() { @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_REPLY_CREATE_TYPE) public data class OpenForumReplyCreate( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: OpenForumReplyData ) : OpenForumReplyDispatch() @@ -255,7 +255,7 @@ public data class OpenForumReplyCreate( @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_REPLY_DELETE_TYPE) public data class OpenForumReplyDelete( override val id: String? = null, - override val s: Long? = null, + override val s: Long = DEFAULT_SEQ, @SerialName("d") override val data: OpenForumReplyData ) : OpenForumReplyDispatch() diff --git a/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api b/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api index 623670b9..9561585f 100644 --- a/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api +++ b/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api @@ -73,6 +73,20 @@ public abstract interface class love/forte/simbot/component/qguild/QQGuildUsageB } public abstract interface class love/forte/simbot/component/qguild/bot/QGBot : love/forte/simbot/ability/EventMentionAware, love/forte/simbot/bot/Bot { + public synthetic fun emitEvent (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public synthetic fun emitEvent (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun emitEvent$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun emitEvent$suspendImpl (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun emitEvent$suspendImpl (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun emitEventAsync (Ljava/lang/String;)Ljava/util/concurrent/CompletableFuture; + public fun emitEventAsync (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)Ljava/util/concurrent/CompletableFuture; + public static synthetic fun emitEventAsync$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture; + public fun emitEventBlocking (Ljava/lang/String;)V + public fun emitEventBlocking (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)V + public static synthetic fun emitEventBlocking$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)V + public fun emitEventReserve (Ljava/lang/String;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; + public fun emitEventReserve (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; + public static synthetic fun emitEventReserve$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; public abstract fun getAvatar ()Ljava/lang/String; public abstract fun getComponent ()Llove/forte/simbot/component/qguild/QQGuildComponent; public fun getContactRelation ()Llove/forte/simbot/bot/ContactRelation; @@ -135,12 +149,20 @@ public abstract interface class love/forte/simbot/component/qguild/bot/QGBot : l public fun uploadUserMediaReserve (Llove/forte/simbot/common/id/ID;Ljava/lang/String;I)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; } +public final class love/forte/simbot/component/qguild/bot/QGBotKt { + public static final fun emitEvent (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/common/function/Action;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun emitEvent$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/common/function/Action;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + public abstract interface annotation class love/forte/simbot/component/qguild/bot/QGBotManagerConfigurationDsl : java/lang/annotation/Annotation { } public abstract interface class love/forte/simbot/component/qguild/bot/QQGuildBotManager : love/forte/simbot/bot/BotManager { public static final field Factory Llove/forte/simbot/component/qguild/bot/QQGuildBotManager$Factory; + public abstract fun all ()Lkotlin/sequences/Sequence; + public abstract fun all (Llove/forte/simbot/common/id/ID;)Lkotlin/sequences/Sequence; public fun configurable (Llove/forte/simbot/bot/SerializableBotConfiguration;)Z + public abstract fun get (Llove/forte/simbot/common/id/ID;)Llove/forte/simbot/component/qguild/bot/QGBot; public abstract fun getConfiguration ()Llove/forte/simbot/component/qguild/bot/QQGuildBotManagerConfiguration; public abstract fun getEventDispatcher ()Llove/forte/simbot/event/EventDispatcher; public fun register (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Llove/forte/simbot/component/qguild/bot/QGBot; @@ -412,12 +434,14 @@ public final class love/forte/simbot/component/qguild/bot/config/QGBotFileConfig public fun equals (Ljava/lang/Object;)Z public final fun getCacheConfig ()Llove/forte/simbot/component/qguild/bot/config/CacheConfig; public final fun getClientProperties ()Ljava/util/Map; + public final fun getDisableWs ()Ljava/lang/Boolean; public final fun getDispatcherConfiguration ()Llove/forte/simbot/bot/configuration/DispatcherConfiguration; public final fun getIntentsConfig ()Llove/forte/simbot/component/qguild/bot/config/IntentsConfig; public final fun getServerUrl ()Ljava/lang/String; public final fun getShardConfig ()Llove/forte/simbot/component/qguild/bot/config/ShardConfig; public final fun getTimeoutConfig ()Llove/forte/simbot/component/qguild/bot/config/QGBotFileConfiguration$TimeoutConfig; public fun hashCode ()I + public final fun setDisableWs (Ljava/lang/Boolean;)V public fun toString ()Ljava/lang/String; } diff --git a/simbot-component-qq-guild-stdlib/api/simbot-component-qq-guild-stdlib.api b/simbot-component-qq-guild-stdlib/api/simbot-component-qq-guild-stdlib.api index fb1475f2..3bd18634 100644 --- a/simbot-component-qq-guild-stdlib/api/simbot-component-qq-guild-stdlib.api +++ b/simbot-component-qq-guild-stdlib/api/simbot-component-qq-guild-stdlib.api @@ -2,6 +2,19 @@ public abstract interface class love/forte/simbot/qguild/stdlib/Bot : kotlinx/co public fun asFuture ()Ljava/util/concurrent/CompletableFuture; public fun cancel ()V public abstract fun cancel (Ljava/lang/Throwable;)V + public synthetic fun emitEvent (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract synthetic fun emitEvent (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun emitEvent$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun emitEvent$suspendImpl (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun emitEventAsync (Ljava/lang/String;)Ljava/util/concurrent/CompletableFuture; + public fun emitEventAsync (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)Ljava/util/concurrent/CompletableFuture; + public static synthetic fun emitEventAsync$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture; + public fun emitEventBlocking (Ljava/lang/String;)V + public fun emitEventBlocking (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)V + public static synthetic fun emitEventBlocking$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)V + public fun emitEventReserve (Ljava/lang/String;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; + public fun emitEventReserve (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; + public static synthetic fun emitEventReserve$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; public abstract fun getAccessToken ()Ljava/lang/String; public abstract fun getApiClient ()Lio/ktor/client/HttpClient; public abstract fun getApiDecoder ()Lkotlinx/serialization/json/Json; @@ -68,6 +81,7 @@ public abstract interface class love/forte/simbot/qguild/stdlib/BotConfiguration public abstract fun getApiHttpSocketTimeoutMillis ()Ljava/lang/Long; public abstract fun getClientProperties ()Ljava/util/Map; public abstract fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; + public abstract fun getDisableWs ()Z public abstract fun getExceptionHandler ()Llove/forte/simbot/qguild/stdlib/ExceptionProcessor; public abstract synthetic fun getIntents-DNrqdk0 ()I public fun getIntentsValue ()I @@ -89,6 +103,11 @@ public final class love/forte/simbot/qguild/stdlib/BotFactory { public static synthetic fun create$default (Llove/forte/simbot/qguild/stdlib/Bot$Ticket;Llove/forte/simbot/qguild/stdlib/ConfigurableBotConfiguration;ILjava/lang/Object;)Llove/forte/simbot/qguild/stdlib/Bot; } +public final class love/forte/simbot/qguild/stdlib/BotKt { + public static final fun emitEvent (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/common/function/Action;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun emitEvent$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/common/function/Action;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + public final class love/forte/simbot/qguild/stdlib/BotRequests { public static final fun botToken (Llove/forte/simbot/qguild/stdlib/Bot;)Ljava/lang/String; public static final fun qqBotToken (Llove/forte/simbot/qguild/stdlib/Bot;)Ljava/lang/String; @@ -134,6 +153,7 @@ public final class love/forte/simbot/qguild/stdlib/ConfigurableBotConfiguration public fun getApiHttpSocketTimeoutMillis ()Ljava/lang/Long; public fun getClientProperties ()Ljava/util/Map; public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; + public fun getDisableWs ()Z public fun getExceptionHandler ()Llove/forte/simbot/qguild/stdlib/ExceptionProcessor; public synthetic fun getIntents-DNrqdk0 ()I public fun getIntentsValue ()I @@ -149,6 +169,7 @@ public final class love/forte/simbot/qguild/stdlib/ConfigurableBotConfiguration public fun setApiHttpSocketTimeoutMillis (Ljava/lang/Long;)V public fun setClientProperties (Ljava/util/Map;)V public fun setCoroutineContext (Lkotlin/coroutines/CoroutineContext;)V + public fun setDisableWs (Z)V public fun setExceptionHandler (Llove/forte/simbot/qguild/stdlib/ExceptionProcessor;)V public synthetic fun setIntents-NLurJl8 (I)V public fun setIntentsValue (I)V @@ -166,6 +187,29 @@ public abstract interface class love/forte/simbot/qguild/stdlib/DisposableHandle public abstract fun dispose ()V } +public final class love/forte/simbot/qguild/stdlib/Ed25519SignatureVerification { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Llove/forte/simbot/qguild/stdlib/Ed25519SignatureVerification; + public static synthetic fun copy$default (Llove/forte/simbot/qguild/stdlib/Ed25519SignatureVerification;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Llove/forte/simbot/qguild/stdlib/Ed25519SignatureVerification; + public fun equals (Ljava/lang/Object;)Z + public final fun getSignatureEd25519 ()Ljava/lang/String; + public final fun getSignatureTimestamp ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class love/forte/simbot/qguild/stdlib/EmitEventOptions { + public fun ()V + public final fun getEd25519SignatureVerification ()Llove/forte/simbot/qguild/stdlib/Ed25519SignatureVerification; + public final fun getIgnoreMissingOpcode ()Z + public final fun getIgnoreUnknownOpcode ()Z + public final fun setEd25519SignatureVerification (Llove/forte/simbot/qguild/stdlib/Ed25519SignatureVerification;)V + public final fun setIgnoreMissingOpcode (Z)V + public final fun setIgnoreUnknownOpcode (Z)V +} + public abstract interface class love/forte/simbot/qguild/stdlib/EventProcessor { public abstract synthetic fun invoke (Llove/forte/simbot/qguild/event/Signal$Dispatch;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } From 591513f2976d245aff6facb38502bb490c61b4ec Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Wed, 16 Oct 2024 23:21:33 +0800 Subject: [PATCH 13/28] fix dead-loop --- .../love/forte/simbot/qguild/stdlib/internal/BotImpl.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt index 98ca0286..20ef6821 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt @@ -107,10 +107,13 @@ internal class BotImpl( } } - private val ed25519PrivateKey = + private val ed25519PrivateKey by lazy { Ed25519.keyFromSeed(ticket.secret.paddingEd25519Seed().toByteArray()) + } - private val ed25519PublicKey = ed25519PrivateKey.publicKey() + private val ed25519PublicKey by lazy { + ed25519PrivateKey.publicKey() + } internal val eventDecoder = Signal.Dispatch.dispatchJson { isLenient = true @@ -619,7 +622,7 @@ internal suspend fun BotImpl.emitEvent(dispatch: Signal.Dispatch, raw: String) { */ internal fun String.paddingEd25519Seed(): String { return when { - length == 32 -> this + length == 32 || isEmpty() -> this length > 32 -> substring(32) else -> { buildString(32) { From 5de8fbf34f779d524f819dec869f95f3358d1460 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 09:59:55 +0800 Subject: [PATCH 14/28] =?UTF-8?q?=E8=B0=83=E6=95=B4=20Bot.emitEvent?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4=E5=9B=9E=E8=B0=83=E3=80=81=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=BF=94=E5=9B=9E=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/simbot-component-qq-guild-core.api | 23 +++++---- .../simbot/component/qguild/bot/QGBot.kt | 32 ++++++------ .../api/simbot-component-qq-guild-stdlib.api | 49 ++++++++++++++----- .../love/forte/simbot/qguild/stdlib/Bot.kt | 43 +++++++++++----- .../simbot/qguild/stdlib/internal/BotImpl.kt | 26 +++++----- 5 files changed, 106 insertions(+), 67 deletions(-) diff --git a/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api b/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api index 9561585f..bf1c24cd 100644 --- a/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api +++ b/simbot-component-qq-guild-core/api/simbot-component-qq-guild-core.api @@ -74,19 +74,19 @@ public abstract interface class love/forte/simbot/component/qguild/QQGuildUsageB public abstract interface class love/forte/simbot/component/qguild/bot/QGBot : love/forte/simbot/ability/EventMentionAware, love/forte/simbot/bot/Bot { public synthetic fun emitEvent (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public synthetic fun emitEvent (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun emitEvent$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public synthetic fun emitEvent (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun emitEvent$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun emitEvent$suspendImpl (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun emitEvent$suspendImpl (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun emitEvent$suspendImpl (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun emitEventAsync (Ljava/lang/String;)Ljava/util/concurrent/CompletableFuture; - public fun emitEventAsync (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)Ljava/util/concurrent/CompletableFuture; - public static synthetic fun emitEventAsync$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture; - public fun emitEventBlocking (Ljava/lang/String;)V - public fun emitEventBlocking (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)V - public static synthetic fun emitEventBlocking$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)V + public fun emitEventAsync (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;)Ljava/util/concurrent/CompletableFuture; + public static synthetic fun emitEventAsync$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture; + public fun emitEventBlocking (Ljava/lang/String;)Llove/forte/simbot/qguild/stdlib/EmitResult; + public fun emitEventBlocking (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;)Llove/forte/simbot/qguild/stdlib/EmitResult; + public static synthetic fun emitEventBlocking$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;ILjava/lang/Object;)Llove/forte/simbot/qguild/stdlib/EmitResult; public fun emitEventReserve (Ljava/lang/String;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; - public fun emitEventReserve (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; - public static synthetic fun emitEventReserve$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; + public fun emitEventReserve (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; + public static synthetic fun emitEventReserve$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;ILjava/lang/Object;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; public abstract fun getAvatar ()Ljava/lang/String; public abstract fun getComponent ()Llove/forte/simbot/component/qguild/QQGuildComponent; public fun getContactRelation ()Llove/forte/simbot/bot/ContactRelation; @@ -150,8 +150,7 @@ public abstract interface class love/forte/simbot/component/qguild/bot/QGBot : l } public final class love/forte/simbot/component/qguild/bot/QGBotKt { - public static final fun emitEvent (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/common/function/Action;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun emitEvent$default (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Llove/forte/simbot/common/function/Action;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun emitEvent (Llove/forte/simbot/component/qguild/bot/QGBot;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface annotation class love/forte/simbot/component/qguild/bot/QGBotManagerConfigurationDsl : java/lang/annotation/Annotation { diff --git a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt index 08bb683e..31068112 100644 --- a/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt +++ b/simbot-component-qq-guild-core/src/commonMain/kotlin/love/forte/simbot/component/qguild/bot/QGBot.kt @@ -23,7 +23,6 @@ import io.ktor.client.statement.* import love.forte.simbot.ability.EventMentionAware import love.forte.simbot.bot.Bot import love.forte.simbot.bot.ContactRelation -import love.forte.simbot.common.function.Action import love.forte.simbot.common.id.ID import love.forte.simbot.common.id.StringID.Companion.ID import love.forte.simbot.common.id.literal @@ -49,8 +48,6 @@ import love.forte.simbot.qguild.api.QQGuildApi import love.forte.simbot.qguild.api.files.UploadGroupFilesApi import love.forte.simbot.qguild.api.files.UploadUserFilesApi import love.forte.simbot.qguild.api.message.GetMessageApi -import love.forte.simbot.qguild.event.Opcode -import love.forte.simbot.qguild.event.Signal import love.forte.simbot.qguild.stdlib.* import love.forte.simbot.suspendrunner.ST import love.forte.simbot.suspendrunner.STP @@ -386,8 +383,10 @@ public interface QGBot : Bot, EventMentionAware { */ @ST override suspend fun messageFromId(id: ID): QGMessageContent { - throw UnsupportedOperationException("Cannot query message from `messageId` only. " + - "Use the QGBot.messageFromId(channelId, messageId) or QGBot.messageFromReference(QGReference) plz.") + throw UnsupportedOperationException( + "Cannot query message from `messageId` only. " + + "Use the QGBot.messageFromId(channelId, messageId) or QGBot.messageFromReference(QGReference) plz." + ) } /** @@ -402,8 +401,10 @@ public interface QGBot : Bot, EventMentionAware { @ExperimentalQGApi override suspend fun messageFromReference(reference: MessageReference): QGMessageContent { if (reference !is QGReference) { - throw UnsupportedOperationException("Cannot query message use a reference that type is not QGReference. " + - "Use `reference` type of QGReference or use messageFromId(channelId, messageId) plz.") + throw UnsupportedOperationException( + "Cannot query message use a reference that type is not QGReference. " + + "Use `reference` type of QGReference or use messageFromId(channelId, messageId) plz." + ) } val cid = reference.channelId ?: throw IllegalArgumentException("`reference.channelId` must not be null") @@ -431,9 +432,6 @@ public interface QGBot : Bot, EventMentionAware { * * @param payload 接收到的事件推送的JSON格式正文字符串。 * @param options 额外提供的属性或配置。默认为 `null`。 - * @param onVerified 如果事件是验证事件 - * ([Opcode.CallbackVerify]), - * 且验证成功后,则通过 [onVerified] 回调结果。 * * @throws IllegalArgumentException 参考: * - [EmitEventOptions.ignoreUnknownOpcode] @@ -447,9 +445,8 @@ public interface QGBot : Bot, EventMentionAware { public suspend fun emitEvent( payload: String, options: EmitEventOptions? = null, - onVerified: Action? = null - ) { - source.emitEvent(payload, options, onVerified) + ): EmitResult { + return source.emitEvent(payload, options) } /** @@ -462,8 +459,8 @@ public interface QGBot : Bot, EventMentionAware { * @since 4.1.0 */ @ST - public suspend fun emitEvent(payload: String) { - source.emitEvent(payload) + public suspend fun emitEvent(payload: String): EmitResult { + return source.emitEvent(payload) } } @@ -474,8 +471,7 @@ public interface QGBot : Bot, EventMentionAware { */ public suspend inline fun QGBot.emitEvent( payload: String, - onVerified: Action? = null, block: EmitEventOptions.() -> Unit -) { - source.emitEvent(payload, onVerified, block) +): EmitResult { + return source.emitEvent(payload, block) } diff --git a/simbot-component-qq-guild-stdlib/api/simbot-component-qq-guild-stdlib.api b/simbot-component-qq-guild-stdlib/api/simbot-component-qq-guild-stdlib.api index 3bd18634..602176e8 100644 --- a/simbot-component-qq-guild-stdlib/api/simbot-component-qq-guild-stdlib.api +++ b/simbot-component-qq-guild-stdlib/api/simbot-component-qq-guild-stdlib.api @@ -3,18 +3,18 @@ public abstract interface class love/forte/simbot/qguild/stdlib/Bot : kotlinx/co public fun cancel ()V public abstract fun cancel (Ljava/lang/Throwable;)V public synthetic fun emitEvent (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract synthetic fun emitEvent (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun emitEvent$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public abstract synthetic fun emitEvent (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun emitEvent$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun emitEvent$suspendImpl (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun emitEventAsync (Ljava/lang/String;)Ljava/util/concurrent/CompletableFuture; - public fun emitEventAsync (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)Ljava/util/concurrent/CompletableFuture; - public static synthetic fun emitEventAsync$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture; - public fun emitEventBlocking (Ljava/lang/String;)V - public fun emitEventBlocking (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)V - public static synthetic fun emitEventBlocking$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)V + public fun emitEventAsync (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;)Ljava/util/concurrent/CompletableFuture; + public static synthetic fun emitEventAsync$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture; + public fun emitEventBlocking (Ljava/lang/String;)Llove/forte/simbot/qguild/stdlib/EmitResult; + public fun emitEventBlocking (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;)Llove/forte/simbot/qguild/stdlib/EmitResult; + public static synthetic fun emitEventBlocking$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;ILjava/lang/Object;)Llove/forte/simbot/qguild/stdlib/EmitResult; public fun emitEventReserve (Ljava/lang/String;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; - public fun emitEventReserve (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; - public static synthetic fun emitEventReserve$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;Llove/forte/simbot/common/function/Action;ILjava/lang/Object;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; + public fun emitEventReserve (Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; + public static synthetic fun emitEventReserve$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/qguild/stdlib/EmitEventOptions;ILjava/lang/Object;)Llove/forte/simbot/suspendrunner/reserve/SuspendReserve; public abstract fun getAccessToken ()Ljava/lang/String; public abstract fun getApiClient ()Lio/ktor/client/HttpClient; public abstract fun getApiDecoder ()Lkotlinx/serialization/json/Json; @@ -104,8 +104,7 @@ public final class love/forte/simbot/qguild/stdlib/BotFactory { } public final class love/forte/simbot/qguild/stdlib/BotKt { - public static final fun emitEvent (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/common/function/Action;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun emitEvent$default (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Llove/forte/simbot/common/function/Action;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun emitEvent (Llove/forte/simbot/qguild/stdlib/Bot;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class love/forte/simbot/qguild/stdlib/BotRequests { @@ -210,6 +209,34 @@ public final class love/forte/simbot/qguild/stdlib/EmitEventOptions { public final fun setIgnoreUnknownOpcode (Z)V } +public abstract class love/forte/simbot/qguild/stdlib/EmitResult { +} + +public final class love/forte/simbot/qguild/stdlib/EmitResult$Dispatched : love/forte/simbot/qguild/stdlib/EmitResult { + public static final field INSTANCE Llove/forte/simbot/qguild/stdlib/EmitResult$Dispatched; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class love/forte/simbot/qguild/stdlib/EmitResult$Nothing : love/forte/simbot/qguild/stdlib/EmitResult { + public static final field INSTANCE Llove/forte/simbot/qguild/stdlib/EmitResult$Nothing; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class love/forte/simbot/qguild/stdlib/EmitResult$Verified : love/forte/simbot/qguild/stdlib/EmitResult { + public fun (Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified;)V + public final fun component1 ()Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified; + public final fun copy (Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified;)Llove/forte/simbot/qguild/stdlib/EmitResult$Verified; + public static synthetic fun copy$default (Llove/forte/simbot/qguild/stdlib/EmitResult$Verified;Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified;ILjava/lang/Object;)Llove/forte/simbot/qguild/stdlib/EmitResult$Verified; + public fun equals (Ljava/lang/Object;)Z + public final fun getVerified ()Llove/forte/simbot/qguild/event/Signal$CallbackVerify$Verified; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public abstract interface class love/forte/simbot/qguild/stdlib/EventProcessor { public abstract synthetic fun invoke (Llove/forte/simbot/qguild/event/Signal$Dispatch;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt index 345c5b82..a9484199 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/Bot.kt @@ -23,10 +23,8 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.serialization.json.Json -import love.forte.simbot.common.function.Action import love.forte.simbot.qguild.api.GatewayInfo import love.forte.simbot.qguild.api.user.GetBotInfoApi -import love.forte.simbot.qguild.event.Opcode import love.forte.simbot.qguild.event.Shard import love.forte.simbot.qguild.event.Signal import love.forte.simbot.qguild.model.User @@ -174,9 +172,6 @@ public interface Bot : CoroutineScope { * * @param payload 接收到的事件推送的JSON格式正文字符串。 * @param options 额外提供的属性或配置。默认为 `null`。 - * @param onVerified 如果事件是验证事件 - * ([Opcode.CallbackVerify]), - * 且验证成功后,则通过 [onVerified] 回调结果。 * * @throws IllegalArgumentException 参考: * - [EmitEventOptions.ignoreUnknownOpcode] @@ -188,8 +183,7 @@ public interface Bot : CoroutineScope { public suspend fun emitEvent( payload: String, options: EmitEventOptions? = null, - onVerified: Action? = null - ) + ): EmitResult /** * 主动推送一个事件原文。 @@ -200,8 +194,8 @@ public interface Bot : CoroutineScope { * @since 4.1.0 */ @ST - public suspend fun emitEvent(payload: String) { - emitEvent(payload, null, null) + public suspend fun emitEvent(payload: String): EmitResult { + return emitEvent(payload, null) } /** @@ -292,10 +286,35 @@ public enum class SubscribeSequence { */ public suspend inline fun Bot.emitEvent( payload: String, - onVerified: Action? = null, block: EmitEventOptions.() -> Unit -) { - emitEvent(payload, EmitEventOptions().apply(block), onVerified) +): EmitResult { + return emitEvent(payload, EmitEventOptions().apply(block)) +} + +/** + * 使用 [Bot.emitEvent] 推送事件后的结果, + * 会根据配置的不同和推送事件的 `opcode` 的不同得到不同的结果. + * + * @since 4.1.0 + */ +public sealed class EmitResult { + /** + * 不属于任何可处理的 `opcode`, 但在 [EmitEventOptions] + * 中配置了跳过而得到的无效返回值。 + */ + public data object Nothing : EmitResult() + + /** + * `opcode` 为 `0`, + * 普通的执行了事件调度、结束并返回。 + */ + public data object Dispatched : EmitResult() + + /** + * `opcode` 为 `13`, + * 对内容进行校验后得到了 [verified] 签名结果。 + */ + public data class Verified(val verified: Signal.CallbackVerify.Verified) : EmitResult() } /** diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt index 20ef6821..a2381c2e 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt @@ -37,7 +37,6 @@ import love.forte.simbot.common.atomic.atomic import love.forte.simbot.common.collection.ConcurrentQueue import love.forte.simbot.common.collection.ExperimentalSimbotCollectionApi import love.forte.simbot.common.collection.createConcurrentQueue -import love.forte.simbot.common.function.Action import love.forte.simbot.common.stageloop.loop import love.forte.simbot.common.weak.WeakRef import love.forte.simbot.common.weak.weakRef @@ -414,13 +413,11 @@ internal class BotImpl( override suspend fun emitEvent( payload: String, options: EmitEventOptions?, - onVerified: Action? - ) { + ): EmitResult { // CODE 名称 客户端行为 描述 // 0 Dispatch Receive 服务端进行消息推送 // 13 回调地址验证 Receive 开放平台对机器人服务端进行验证 logger.debug("Emit raw event with payload: {}", payload) - val json = eventDecoder.parseToJsonElement(payload) fun verifyIfNecessary() { val (signature, signatureTimestamp) = options?.ed25519SignatureVerification ?: return @@ -449,29 +446,29 @@ internal class BotImpl( } } - fun signatureIfNecessary(verify: Signal.CallbackVerify) { - verifyIfNecessary() // verify itself first. - val callback = onVerified ?: return - + fun signatureIfNecessary(verify: Signal.CallbackVerify): EmitResult { val (plainToken, eventTs) = verify.data val msg = "$eventTs$plainToken" val signature = ed25519PrivateKey.sign(msg.toByteArray()) - callback.invoke(Signal.CallbackVerify.Verified(plainToken, signature.toHexString())) + val verified = Signal.CallbackVerify.Verified(plainToken, signature.toHexString()) + return EmitResult.Verified(verified) } + // Verify payload + verifyIfNecessary() + + val json = eventDecoder.parseToJsonElement(payload) when (val opcode = json.getOpcode()) { null -> { - if (options?.ignoreMissingOpcode == true) return + if (options?.ignoreMissingOpcode == true) return EmitResult.Nothing throw IllegalArgumentException("Required attribute `$.op` is missing") } Opcodes.Dispatch -> { - verifyIfNecessary() - val dispatch = try { eventDecoder.decodeFromJsonElement(Signal.Dispatch.serializer(), json) } catch (serEx: SerializationException) { @@ -491,16 +488,17 @@ internal class BotImpl( } emitEvent(dispatch, payload) + return EmitResult.Dispatched } Opcodes.CallbackVerify -> { val verify = eventDecoder.decodeFromJsonElement(Signal.CallbackVerify.serializer(), json) logger.debug("On Signal.CallbackVerify: {}", verify) - signatureIfNecessary(verify) + return signatureIfNecessary(verify) } else -> { - if (options?.ignoreUnknownOpcode == true) return + if (options?.ignoreUnknownOpcode == true) return EmitResult.Nothing throw IllegalArgumentException("Unknown opcode: $opcode, emitEvent can only support opcode in [0, 13]") } From 1d69a4e2b9c9bf1efcfd970a1dbebcd1e3a85324 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 11:52:23 +0800 Subject: [PATCH 15/28] =?UTF-8?q?optimize:=20=E6=9B=B4=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E7=9A=84=20Signal.Dispatch=20=E5=BA=8F=E5=88=97=E5=8C=96?= =?UTF-8?q?=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../build.gradle.kts | 47 +++++ .../DispatchSerializerProcessor.kt | 171 ++++++++++++++++++ .../DispatchSerializerProcessorProvider.kt | 31 ++++ ...ols.ksp.processing.SymbolProcessorProvider | 1 + settings.gradle.kts | 1 + .../api/simbot-component-qq-guild-api.api | 14 ++ .../build.gradle.kts | 1 + .../forte/simbot/qguild/OptAnnotations.kt | 7 + .../forte/simbot/qguild/event/EventIntents.kt | 2 + .../love/forte/simbot/qguild/event/Signal.kt | 43 ++++- .../simbot/qguild/event/c2cManagements.kt | 4 + .../forte/simbot/qguild/event/c2cMessages.kt | 2 + .../forte/simbot/qguild/event/channels.kt | 3 + .../love/forte/simbot/qguild/event/forums.kt | 8 + .../simbot/qguild/event/groupManagements.kt | 4 + .../love/forte/simbot/qguild/event/guilds.kt | 3 + .../love/forte/simbot/qguild/event/members.kt | 3 + .../forte/simbot/qguild/event/messages.kt | 7 + .../forte/simbot/qguild/event/openForums.kt | 7 + .../simbot/qguild/stdlib/internal/BotImpl.kt | 26 ++- .../qguild/stdlib/internal/BotStates.kt | 50 ++--- 21 files changed, 401 insertions(+), 34 deletions(-) create mode 100644 internal-processors/dispatch-serializer-processor/build.gradle.kts create mode 100644 internal-processors/dispatch-serializer-processor/src/main/kotlin/qg/internal/processors/dispatcherserializer/DispatchSerializerProcessor.kt create mode 100644 internal-processors/dispatch-serializer-processor/src/main/kotlin/qg/internal/processors/dispatcherserializer/DispatchSerializerProcessorProvider.kt create mode 100644 internal-processors/dispatch-serializer-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider diff --git a/internal-processors/dispatch-serializer-processor/build.gradle.kts b/internal-processors/dispatch-serializer-processor/build.gradle.kts new file mode 100644 index 00000000..3e032d1e --- /dev/null +++ b/internal-processors/dispatch-serializer-processor/build.gradle.kts @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + kotlin("jvm") +} + +repositories { + mavenCentral() +} + +kotlin { + jvmToolchain(JVMConstants.KT_JVM_TARGET_VALUE) + compilerOptions { + javaParameters = true + jvmTarget.set(JvmTarget.fromTarget(JVMConstants.KT_JVM_TARGET_VALUE.toString())) + } +} + +configJavaCompileWithModule() + +dependencies { + api(libs.ksp) + api(libs.kotlinPoet.ksp) + testImplementation(kotlin("test-junit5")) +} + +tasks.getByName("test") { + useJUnitPlatform() +} + diff --git a/internal-processors/dispatch-serializer-processor/src/main/kotlin/qg/internal/processors/dispatcherserializer/DispatchSerializerProcessor.kt b/internal-processors/dispatch-serializer-processor/src/main/kotlin/qg/internal/processors/dispatcherserializer/DispatchSerializerProcessor.kt new file mode 100644 index 00000000..1d213973 --- /dev/null +++ b/internal-processors/dispatch-serializer-processor/src/main/kotlin/qg/internal/processors/dispatcherserializer/DispatchSerializerProcessor.kt @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +package qg.internal.processors.dispatcherserializer + +import com.google.devtools.ksp.getClassDeclarationByName +import com.google.devtools.ksp.isAbstract +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFile +import com.google.devtools.ksp.symbol.Modifier +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.writeTo +import java.time.Instant +import java.time.ZoneOffset + +private const val SIGNAL_DISPATCH_PKG = "love.forte.simbot.qguild.event" +private const val SIGNAL_DISPATCH_SIMPLE_NAME = "Signal.Dispatch" +private const val SIGNAL_DISPATCH_NAME = "$SIGNAL_DISPATCH_PKG.$SIGNAL_DISPATCH_SIMPLE_NAME" + +/** + * + * @author ForteScarlet + */ +internal class DispatchSerializerProcessor( + private val environment: SymbolProcessorEnvironment, +) : SymbolProcessor { + private data class SerializableDispatch( + val declaration: KSClassDeclaration, + val typeName: String + ) + + private lateinit var signalDispatchDeclaration: KSClassDeclaration + + private val dispatchSerializableDeclarations = sortedSetOf( + Comparator.comparing { it.typeName } + ) + + override fun process(resolver: Resolver): List { + signalDispatchDeclaration = resolver.getClassDeclarationByName(SIGNAL_DISPATCH_NAME) + ?: error("Cannot find class declaration $SIGNAL_DISPATCH_NAME") + + // find all subtypes, + resolver.getAllFiles() + .flatMap { it.declarations } + .filterIsInstance() + .filter { + signalDispatchDeclaration.asStarProjectedType().isAssignableFrom(it.asStarProjectedType()) + } + // not abstract + .filter { + !it.isAbstract() && !it.modifiers.contains(Modifier.SEALED) + } + // marked @kotlinx.serialization.Serializable + .filter { + it.annotations.any { anno -> + with(anno.annotationType.resolve().declaration) { + packageName.asString() == "kotlinx.serialization" && + simpleName.asString() == "Serializable" + } + } + } + .mapNotNull { + // find @love.forte.simbot.qguild.event.DispatchTypeName + val typeNameAnnotation = it.annotations.find { anno -> + with(anno.annotationType.resolve().declaration) { + packageName.asString() == "love.forte.simbot.qguild.event" && + simpleName.asString() == "DispatchTypeName" + } + } ?: return@mapNotNull null + + val argument = typeNameAnnotation.arguments.find { a -> a.name?.asString() == "value" } + ?: return@mapNotNull null + + val typeName = argument.value as String + + SerializableDispatch(it, typeName) + } + .toCollection(dispatchSerializableDeclarations) + + return emptyList() + } + + override fun finish() { + genResolver() + } + + private fun genResolver() { + val orgFiles = mutableListOf() + signalDispatchDeclaration.containingFile?.also(orgFiles::add) + + val fileSpecBuilder = FileSpec.builder("love.forte.simbot.qguild.event", "SignalDispatchResolvers.generated") + + val funBuilder = FunSpec.builder("resolveDispatchSerializer").apply { + addAnnotation(ClassName("love.forte.simbot.qguild", "Generated")) + addParameter("eventName", String::class) + returns( + ClassName("kotlinx.serialization", "KSerializer") + .parameterizedBy( + WildcardTypeName + .producerOf( + ClassName(SIGNAL_DISPATCH_PKG, SIGNAL_DISPATCH_SIMPLE_NAME) + ) + ) + .copy(nullable = true) + ) + + addCode( + CodeBlock.builder().apply { + beginControlFlow("return when(eventName)") + // "" -> Type.serializer() + for (dispatchSerializableDeclaration in dispatchSerializableDeclarations) { + dispatchSerializableDeclaration.declaration.containingFile?.also(orgFiles::add) + + addStatement( + "%S -> %T.serializer()", + dispatchSerializableDeclaration.typeName, + dispatchSerializableDeclaration.declaration.toClassName() + ) + } + addStatement("else -> null") + endControlFlow() + }.build() + ) + + addKdoc( + CodeBlock.builder().apply { + addStatement("| event name | target |") + addStatement("| --- | --- |") + for (dispatchSerializableDeclaration in dispatchSerializableDeclarations) { + addStatement( + "| `%S` | [%T] |", + dispatchSerializableDeclaration.typeName, + dispatchSerializableDeclaration.declaration.toClassName() + ) + } + addStatement("") + addStatement("@since 4.1.0") + }.build() + ) + } + + fileSpecBuilder.addFileComment("Auto generated at ${Instant.now().atOffset(ZoneOffset.ofHours(8))}") + fileSpecBuilder.addFunction(funBuilder.build()) + + fileSpecBuilder.build().writeTo( + environment.codeGenerator, + aggregating = true, + originatingKSFiles = orgFiles + ) + } +} diff --git a/internal-processors/dispatch-serializer-processor/src/main/kotlin/qg/internal/processors/dispatcherserializer/DispatchSerializerProcessorProvider.kt b/internal-processors/dispatch-serializer-processor/src/main/kotlin/qg/internal/processors/dispatcherserializer/DispatchSerializerProcessorProvider.kt new file mode 100644 index 00000000..3ab304a0 --- /dev/null +++ b/internal-processors/dispatch-serializer-processor/src/main/kotlin/qg/internal/processors/dispatcherserializer/DispatchSerializerProcessorProvider.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +package qg.internal.processors.dispatcherserializer + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +/** + * + * @author ForteScarlet + */ +class DispatchSerializerProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = + DispatchSerializerProcessor(environment) +} diff --git a/internal-processors/dispatch-serializer-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/internal-processors/dispatch-serializer-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 00000000..4cae3785 --- /dev/null +++ b/internal-processors/dispatch-serializer-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +qg.internal.processors.dispatcherserializer.DispatchSerializerProcessorProvider diff --git a/settings.gradle.kts b/settings.gradle.kts index 211f45f9..07a90dab 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,6 +19,7 @@ rootProject.name = "qq-guild" // internals include(":internal-processors:api-reader") +include(":internal-processors:dispatch-serializer-processor") include(":internal-processors:intents-processor") //include(":builder-generator") diff --git a/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api b/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api index 57664530..8ab37d67 100644 --- a/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api +++ b/simbot-component-qq-guild-api/api/simbot-component-qq-guild-api.api @@ -33,6 +33,9 @@ public final class love/forte/simbot/qguild/ErrInfo$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public abstract interface annotation class love/forte/simbot/qguild/Generated : java/lang/annotation/Annotation { +} + public abstract interface annotation class love/forte/simbot/qguild/PrivateDomainOnly : java/lang/annotation/Annotation { } @@ -2434,6 +2437,10 @@ public final class love/forte/simbot/qguild/event/DirectMessageCreate$Companion public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public abstract interface annotation class love/forte/simbot/qguild/event/DispatchTypeName : java/lang/annotation/Annotation { + public abstract fun value ()Ljava/lang/String; +} + public final class love/forte/simbot/qguild/event/EventChannel { public static final field Companion Llove/forte/simbot/qguild/event/EventChannel$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Llove/forte/simbot/qguild/model/ChannelType;Llove/forte/simbot/qguild/model/ChannelSubType;Ljava/lang/String;Ljava/lang/String;)V @@ -4373,6 +4380,7 @@ public final class love/forte/simbot/qguild/event/Signal$Companion { public abstract class love/forte/simbot/qguild/event/Signal$Dispatch : love/forte/simbot/qguild/event/Signal { public static final field Companion Llove/forte/simbot/qguild/event/Signal$Dispatch$Companion; public static final field DISPATCH_CLASS_DISCRIMINATOR Ljava/lang/String; + public fun ()V public synthetic fun (ILlove/forte/simbot/qguild/event/Opcode;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public abstract fun getData ()Ljava/lang/Object; public abstract fun getId ()Ljava/lang/String; @@ -4615,8 +4623,14 @@ public final class love/forte/simbot/qguild/event/Signal$Resume$Data$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class love/forte/simbot/qguild/event/SignalDispatchResolvers_generatedKt { + public static final fun resolveDispatchSerializer (Ljava/lang/String;)Lkotlinx/serialization/KSerializer; +} + public final class love/forte/simbot/qguild/event/SignalKt { public static final fun getOpcode (Lkotlinx/serialization/json/JsonElement;)Ljava/lang/Integer; + public static final fun resolveDispatchSerializer (Lkotlinx/serialization/json/JsonObject;Z)Lkotlinx/serialization/KSerializer; + public static synthetic fun resolveDispatchSerializer$default (Lkotlinx/serialization/json/JsonObject;ZILjava/lang/Object;)Lkotlinx/serialization/KSerializer; } public final class love/forte/simbot/qguild/message/ArkBuilder { diff --git a/simbot-component-qq-guild-api/build.gradle.kts b/simbot-component-qq-guild-api/build.gradle.kts index 24423f72..eb06b6f0 100644 --- a/simbot-component-qq-guild-api/build.gradle.kts +++ b/simbot-component-qq-guild-api/build.gradle.kts @@ -111,6 +111,7 @@ kotlin { dependencies { add("kspJvm", project(":internal-processors:api-reader")) add("kspCommonMainMetadata", project(":internal-processors:intents-processor")) + add("kspCommonMainMetadata", project(":internal-processors:dispatch-serializer-processor")) } ksp { diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/OptAnnotations.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/OptAnnotations.kt index 05492542..eb284b8c 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/OptAnnotations.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/OptAnnotations.kt @@ -43,3 +43,10 @@ public annotation class QGApi4JS @MustBeDocumented @RequiresOptIn("Internal API", level = RequiresOptIn.Level.WARNING) public annotation class QGInternalApi + +/** + * A auto-generated API. + */ +@Retention(AnnotationRetention.SOURCE) +@MustBeDocumented +public annotation class Generated diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt index 80cc431f..0787d2b1 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/EventIntents.kt @@ -622,6 +622,7 @@ public val EventIntentsInstances: Array */ @Serializable @SerialName(READY_TYPE) +@DispatchTypeName(READY_TYPE) public data class Ready( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -647,6 +648,7 @@ public data class Ready( */ @Serializable @SerialName(RESUMED_TYPE) +@DispatchTypeName(RESUMED_TYPE) public data class Resumed( override val id: String? = null, override val s: Long = DEFAULT_SEQ, diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt index 3e316ce4..c7d7ebef 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/Signal.kt @@ -152,13 +152,20 @@ public sealed class Signal(@Serializable(Opcode.SerializerByCode::class) publ * * 当一个事件的解析出现异常或存在未知的 `type` 时,可被解析为 [Unknown] 并仅携带原始信息。 * + * ## 序列化器 + * 建议使用 [resolveDispatchSerializer] 通过 JSON 中 + * [Dispatch.DISPATCH_CLASS_DISCRIMINATOR] 的值来获取对应的序列化器, + * 而不是使用多态。 + * + * [Dispatch] 从 `v4.1.0` 开始不再是 `sealed` ,不能直接使用多态序列化。 + * * @see love.forte.simbot.qguild.event * */ @OptIn(ExperimentalSerializationApi::class) @Serializable @JsonClassDiscriminator(Dispatch.DISPATCH_CLASS_DISCRIMINATOR) - public sealed class Dispatch : Signal<@Contextual Any>(Opcode.Dispatch) { + public abstract class Dispatch : Signal<@Contextual Any>(Opcode.Dispatch) { /** * 事件序列 @@ -180,7 +187,7 @@ public sealed class Signal(@Serializable(Opcode.SerializerByCode::class) publ public companion object { - + internal const val DEFAULT_SEQ: Long = -1L /** @@ -282,6 +289,11 @@ public fun JsonElement.tryGetId(): String? = kotlin.runCatching { jsonObject["id"]?.jsonPrimitive?.contentOrNull }.getOrNull() +@QGInternalApi +public fun JsonElement.tryGetSeq(): Long? = kotlin.runCatching { + jsonObject["s"]?.jsonPrimitive?.longOrNull +}.getOrNull() + /** * [Shared](https://bot.q.qq.com/wiki/develop/api/gateway/shard.html) * @@ -331,3 +343,30 @@ internal object SharedSerializer : KSerializer { arraySerializer.serialize(encoder, intArrayOf(value.value, value.total)) } } + +/** + * @suppress Used by symbol processor. + */ +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.CLASS) +public annotation class DispatchTypeName(val value: String) + +/** + * 解析 [json] 并寻找匹配的 [KSerializer], + * 如果找不到则得到 `null`。 + * + * @see resolveDispatchSerializer + */ +public fun resolveDispatchSerializer( + json: JsonObject, + allowNameMissing: Boolean = false, +): KSerializer? { + val eventName = json[Signal.Dispatch.DISPATCH_CLASS_DISCRIMINATOR]?.jsonPrimitive?.contentOrNull + require(allowNameMissing || eventName != null) { + "Required json attribute $.${Signal.Dispatch.DISPATCH_CLASS_DISCRIMINATOR} is missing" + } + + eventName ?: return null + + return resolveDispatchSerializer(eventName) +} diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt index e2273a42..dfaafe16 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cManagements.kt @@ -47,6 +47,7 @@ public sealed class C2CManagementDispatch : Signal.Dispatch() { */ @Serializable @SerialName(EventIntents.GroupAndC2CEvent.FRIEND_ADD_TYPE) +@DispatchTypeName(EventIntents.GroupAndC2CEvent.FRIEND_ADD_TYPE) public data class FriendAdd( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -61,6 +62,7 @@ public data class FriendAdd( */ @Serializable @SerialName(EventIntents.GroupAndC2CEvent.FRIEND_DEL_TYPE) +@DispatchTypeName(EventIntents.GroupAndC2CEvent.FRIEND_DEL_TYPE) public data class FriendDel( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -75,6 +77,7 @@ public data class FriendDel( */ @Serializable @SerialName(EventIntents.GroupAndC2CEvent.C2C_MSG_REJECT_TYPE) +@DispatchTypeName(EventIntents.GroupAndC2CEvent.C2C_MSG_REJECT_TYPE) public data class C2CMsgReject( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -89,6 +92,7 @@ public data class C2CMsgReject( */ @Serializable @SerialName(EventIntents.GroupAndC2CEvent.C2C_MSG_RECEIVE_TYPE) +@DispatchTypeName(EventIntents.GroupAndC2CEvent.C2C_MSG_RECEIVE_TYPE) public data class C2CMsgReceive( override val id: String? = null, override val s: Long = DEFAULT_SEQ, diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt index b6fd768f..e131a143 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/c2cMessages.kt @@ -28,6 +28,7 @@ import love.forte.simbot.qguild.model.Message */ @Serializable @SerialName(EventIntents.GroupAndC2CEvent.C2C_MESSAGE_CREATE_TYPE) +@DispatchTypeName(EventIntents.GroupAndC2CEvent.C2C_MESSAGE_CREATE_TYPE) public data class C2CMessageCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -72,6 +73,7 @@ public data class C2CMessageCreate( */ @Serializable @SerialName(EventIntents.GroupAndC2CEvent.GROUP_AT_MESSAGE_CREATE_TYPE) +@DispatchTypeName(EventIntents.GroupAndC2CEvent.GROUP_AT_MESSAGE_CREATE_TYPE) public data class GroupAtMessageCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt index 398371fc..35b9d307 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/channels.kt @@ -43,6 +43,7 @@ public sealed class ChannelDispatch : Signal.Dispatch() { */ @Serializable @SerialName(EventIntents.Guilds.CHANNEL_CREATE_TYPE) +@DispatchTypeName(EventIntents.Guilds.CHANNEL_CREATE_TYPE) public data class ChannelCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -57,6 +58,7 @@ public data class ChannelCreate( */ @Serializable @SerialName(EventIntents.Guilds.CHANNEL_UPDATE_TYPE) +@DispatchTypeName(EventIntents.Guilds.CHANNEL_UPDATE_TYPE) public data class ChannelUpdate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -71,6 +73,7 @@ public data class ChannelUpdate( */ @Serializable @SerialName(EventIntents.Guilds.CHANNEL_DELETE_TYPE) +@DispatchTypeName(EventIntents.Guilds.CHANNEL_DELETE_TYPE) public data class ChannelDelete( override val id: String? = null, override val s: Long = DEFAULT_SEQ, diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt index 9af460fb..b5c22628 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/forums.kt @@ -92,6 +92,7 @@ public sealed class ForumThreadDispatch : ForumDispatch() { */ @Serializable @SerialName(EventIntents.ForumsEvent.FORUM_THREAD_CREATE_TYPE) +@DispatchTypeName(EventIntents.ForumsEvent.FORUM_THREAD_CREATE_TYPE) public data class ForumThreadCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -106,6 +107,7 @@ public data class ForumThreadCreate( */ @Serializable @SerialName(EventIntents.ForumsEvent.FORUM_THREAD_UPDATE_TYPE) +@DispatchTypeName(EventIntents.ForumsEvent.FORUM_THREAD_UPDATE_TYPE) public data class ForumThreadUpdate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -120,6 +122,7 @@ public data class ForumThreadUpdate( */ @Serializable @SerialName(EventIntents.ForumsEvent.FORUM_THREAD_DELETE_TYPE) +@DispatchTypeName(EventIntents.ForumsEvent.FORUM_THREAD_DELETE_TYPE) public data class ForumThreadDelete( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -147,6 +150,7 @@ public sealed class ForumPostDispatch : ForumDispatch() { */ @Serializable @SerialName(EventIntents.ForumsEvent.FORUM_POST_CREATE_TYPE) +@DispatchTypeName(EventIntents.ForumsEvent.FORUM_POST_CREATE_TYPE) public data class ForumPostCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -161,6 +165,7 @@ public data class ForumPostCreate( */ @Serializable @SerialName(EventIntents.ForumsEvent.FORUM_POST_DELETE_TYPE) +@DispatchTypeName(EventIntents.ForumsEvent.FORUM_POST_DELETE_TYPE) public data class ForumPostDelete( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -188,6 +193,7 @@ public sealed class ForumReplyDispatch : ForumDispatch() { */ @Serializable @SerialName(EventIntents.ForumsEvent.FORUM_REPLY_CREATE_TYPE) +@DispatchTypeName(EventIntents.ForumsEvent.FORUM_REPLY_CREATE_TYPE) public data class ForumReplyCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -202,6 +208,7 @@ public data class ForumReplyCreate( */ @Serializable @SerialName(EventIntents.ForumsEvent.FORUM_REPLY_DELETE_TYPE) +@DispatchTypeName(EventIntents.ForumsEvent.FORUM_REPLY_DELETE_TYPE) public data class ForumReplyDelete( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -215,6 +222,7 @@ public data class ForumReplyDelete( */ @Serializable @SerialName(EventIntents.ForumsEvent.FORUM_PUBLISH_AUDIT_RESULT_TYPE) +@DispatchTypeName(EventIntents.ForumsEvent.FORUM_PUBLISH_AUDIT_RESULT_TYPE) public data class ForumPublishAuditResult( override val id: String? = null, override val s: Long = DEFAULT_SEQ, diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt index 174b5760..3eb5cc41 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/groupManagements.kt @@ -49,6 +49,7 @@ public sealed class GroupRobotManagementDispatch : Signal.Dispatch() { */ @Serializable @SerialName(EventIntents.GroupAndC2CEvent.GROUP_ADD_ROBOT_TYPE) +@DispatchTypeName(EventIntents.GroupAndC2CEvent.GROUP_ADD_ROBOT_TYPE) public data class GroupAddRobot( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -63,6 +64,7 @@ public data class GroupAddRobot( */ @Serializable @SerialName(EventIntents.GroupAndC2CEvent.GROUP_DEL_ROBOT_TYPE) +@DispatchTypeName(EventIntents.GroupAndC2CEvent.GROUP_DEL_ROBOT_TYPE) public data class GroupDelRobot( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -77,6 +79,7 @@ public data class GroupDelRobot( */ @Serializable @SerialName(EventIntents.GroupAndC2CEvent.GROUP_MSG_REJECT_TYPE) +@DispatchTypeName(EventIntents.GroupAndC2CEvent.GROUP_MSG_REJECT_TYPE) public data class GroupMsgReject( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -91,6 +94,7 @@ public data class GroupMsgReject( */ @Serializable @SerialName(EventIntents.GroupAndC2CEvent.GROUP_MSG_RECEIVE_TYPE) +@DispatchTypeName(EventIntents.GroupAndC2CEvent.GROUP_MSG_RECEIVE_TYPE) public data class GroupMsgReceive( override val id: String? = null, override val s: Long = DEFAULT_SEQ, diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt index 42578bab..20f8854e 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/guilds.kt @@ -41,6 +41,7 @@ public sealed class EventGuildDispatch : Signal.Dispatch() { */ @Serializable @SerialName(EventIntents.Guilds.GUILD_CREATE_TYPE) +@DispatchTypeName(EventIntents.Guilds.GUILD_CREATE_TYPE) public data class GuildCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -57,6 +58,7 @@ public data class GuildCreate( */ @Serializable @SerialName(EventIntents.Guilds.GUILD_UPDATE_TYPE) +@DispatchTypeName(EventIntents.Guilds.GUILD_UPDATE_TYPE) public data class GuildUpdate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -74,6 +76,7 @@ public data class GuildUpdate( */ @Serializable @SerialName(EventIntents.Guilds.GUILD_DELETE_TYPE) +@DispatchTypeName(EventIntents.Guilds.GUILD_DELETE_TYPE) public data class GuildDelete( override val id: String? = null, override val s: Long = DEFAULT_SEQ, diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt index c4a6b28d..5616a1d3 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/members.kt @@ -31,6 +31,7 @@ import love.forte.simbot.qguild.time.ZERO_ISO_INSTANT */ @Serializable @SerialName(EventIntents.GuildMembers.GUILD_MEMBER_ADD_TYPE) +@DispatchTypeName(EventIntents.GuildMembers.GUILD_MEMBER_ADD_TYPE) public data class GuildMemberAdd( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -44,6 +45,7 @@ public data class GuildMemberAdd( */ @Serializable @SerialName(EventIntents.GuildMembers.GUILD_MEMBER_UPDATE_TYPE) +@DispatchTypeName(EventIntents.GuildMembers.GUILD_MEMBER_UPDATE_TYPE) public data class GuildMemberUpdate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -57,6 +59,7 @@ public data class GuildMemberUpdate( */ @Serializable @SerialName(EventIntents.GuildMembers.GUILD_MEMBER_REMOVE_TYPE) +@DispatchTypeName(EventIntents.GuildMembers.GUILD_MEMBER_REMOVE_TYPE) public data class GuildMemberRemove( override val id: String? = null, override val s: Long = DEFAULT_SEQ, diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt index 52633c78..4d50d083 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/messages.kt @@ -44,6 +44,7 @@ public sealed class MessageDispatch : Signal.Dispatch() { */ @Serializable @SerialName(EventIntents.PublicGuildMessages.AT_MESSAGE_CREATE_TYPE) +@DispatchTypeName(EventIntents.PublicGuildMessages.AT_MESSAGE_CREATE_TYPE) public data class AtMessageCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -57,6 +58,7 @@ public data class AtMessageCreate( */ @Serializable @SerialName(EventIntents.PublicGuildMessages.PUBLIC_MESSAGE_DELETE_TYPE) +@DispatchTypeName(EventIntents.PublicGuildMessages.PUBLIC_MESSAGE_DELETE_TYPE) public data class PublicMessageDeleteCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -74,6 +76,7 @@ public data class PublicMessageDeleteCreate( */ @Serializable @SerialName(EventIntents.DirectMessage.DIRECT_MESSAGE_CREATE_TYPE) +@DispatchTypeName(EventIntents.DirectMessage.DIRECT_MESSAGE_CREATE_TYPE) public data class DirectMessageCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -95,6 +98,7 @@ public sealed class MessageAuditedDispatch : Signal.Dispatch() { */ @Serializable @SerialName(EventIntents.GuildMessages.MESSAGE_CREATE_TYPE) +@DispatchTypeName(EventIntents.GuildMessages.MESSAGE_CREATE_TYPE) public data class MessageCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -106,6 +110,7 @@ public data class MessageCreate( */ @Serializable @SerialName(EventIntents.GuildMessages.MESSAGE_DELETE_TYPE) +@DispatchTypeName(EventIntents.GuildMessages.MESSAGE_DELETE_TYPE) public data class MessageDelete( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -123,6 +128,7 @@ public data class MessageDelete( */ @Serializable @SerialName(EventIntents.MessageAudit.MESSAGE_AUDIT_PASS_TYPE) +@DispatchTypeName(EventIntents.MessageAudit.MESSAGE_AUDIT_PASS_TYPE) public data class MessageAuditPass( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -139,6 +145,7 @@ public data class MessageAuditPass( */ @Serializable @SerialName(EventIntents.MessageAudit.MESSAGE_AUDIT_REJECT_TYPE) +@DispatchTypeName(EventIntents.MessageAudit.MESSAGE_AUDIT_REJECT_TYPE) public data class MessageAuditReject( override val id: String? = null, override val s: Long = DEFAULT_SEQ, diff --git a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt index f4dc44ad..63738ffe 100644 --- a/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt +++ b/simbot-component-qq-guild-api/src/commonMain/kotlin/love/forte/simbot/qguild/event/openForums.kt @@ -118,6 +118,7 @@ public sealed class OpenForumThreadDispatch : OpenForumDispatch() { */ @Serializable @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_CREATE_TYPE) +@DispatchTypeName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_CREATE_TYPE) public data class OpenForumThreadCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -132,6 +133,7 @@ public data class OpenForumThreadCreate( */ @Serializable @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_UPDATE_TYPE) +@DispatchTypeName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_UPDATE_TYPE) public data class OpenForumThreadUpdate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -146,6 +148,7 @@ public data class OpenForumThreadUpdate( */ @Serializable @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_DELETE_TYPE) +@DispatchTypeName(EventIntents.OpenForumsEvent.OPEN_FORUM_THREAD_DELETE_TYPE) public data class OpenForumThreadDelete( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -186,6 +189,7 @@ public sealed class OpenForumPostDispatch : OpenForumDispatch() { */ @Serializable @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_POST_CREATE_TYPE) +@DispatchTypeName(EventIntents.OpenForumsEvent.OPEN_FORUM_POST_CREATE_TYPE) public data class OpenForumPostCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -200,6 +204,7 @@ public data class OpenForumPostCreate( */ @Serializable @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_POST_DELETE_TYPE) +@DispatchTypeName(EventIntents.OpenForumsEvent.OPEN_FORUM_POST_DELETE_TYPE) public data class OpenForumPostDelete( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -239,6 +244,7 @@ public sealed class OpenForumReplyDispatch : OpenForumDispatch() { */ @Serializable @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_REPLY_CREATE_TYPE) +@DispatchTypeName(EventIntents.OpenForumsEvent.OPEN_FORUM_REPLY_CREATE_TYPE) public data class OpenForumReplyCreate( override val id: String? = null, override val s: Long = DEFAULT_SEQ, @@ -253,6 +259,7 @@ public data class OpenForumReplyCreate( */ @Serializable @SerialName(EventIntents.OpenForumsEvent.OPEN_FORUM_REPLY_DELETE_TYPE) +@DispatchTypeName(EventIntents.OpenForumsEvent.OPEN_FORUM_REPLY_DELETE_TYPE) public data class OpenForumReplyDelete( override val id: String? = null, override val s: Long = DEFAULT_SEQ, diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt index a2381c2e..a235e7d8 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt @@ -469,21 +469,27 @@ internal class BotImpl( } Opcodes.Dispatch -> { - val dispatch = try { - eventDecoder.decodeFromJsonElement(Signal.Dispatch.serializer(), json) - } catch (serEx: SerializationException) { + val serializer = resolveDispatchSerializer( + json = json.jsonObject, + allowNameMissing = true + ) + + val dispatch = if (serializer != null) { + eventDecoder.decodeFromJsonElement(serializer, json) + } else { + // Unknown val disSeq = -1L val id = json.tryGetId() Signal.Dispatch.Unknown(id, disSeq, json, payload).also { val t = - kotlin.runCatching { json.jsonObject[Signal.Dispatch.DISPATCH_CLASS_DISCRIMINATOR]?.jsonPrimitive?.content } - .getOrNull() - if (tryCheckIsPolymorphicException(serEx)) { - logger.warn("Unknown event type {} in polymorphic, decode it as Unknown event: {}", t, it) - } else { - logger.warn("Unknown event type {}, decode it as Unknown event: {}", t, it, serEx) - } + kotlin.runCatching { + json.jsonObject[Signal.Dispatch.DISPATCH_CLASS_DISCRIMINATOR] + ?.jsonPrimitive + ?.content + }.getOrNull() + + logger.warn("Unknown event type {}, decode it as Unknown event: {}", t, it) } } diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotStates.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotStates.kt index f7a22766..1ebc30ff 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotStates.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotStates.kt @@ -24,7 +24,9 @@ import io.ktor.websocket.* import kotlinx.coroutines.* import kotlinx.coroutines.sync.withLock import kotlinx.serialization.SerializationException -import kotlinx.serialization.json.* +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import love.forte.simbot.logger.Logger import love.forte.simbot.logger.isDebugEnabled import love.forte.simbot.logger.isTraceEnabled @@ -181,8 +183,12 @@ internal class WaitingReadyEvent( val text = frame.readText() logger.debug("Waiting ready event : received frame {}", text) val json = bot.eventDecoder.parseToJsonElement(text) - if (json.jsonObject["op"]?.jsonPrimitive?.int == Opcodes.Dispatch) { - val dispatch = bot.eventDecoder.decodeFromJsonElement(Signal.Dispatch.serializer(), json) + if (json.getOpcode() == Opcodes.Dispatch) { + val dispatchSerializer = resolveDispatchSerializer(json.jsonObject) + ?: continue + + val dispatch = bot.eventDecoder.decodeFromJsonElement(dispatchSerializer, json) + if (dispatch is Ready) { ready = dispatch break @@ -372,30 +378,30 @@ internal class ReceiveEvent( when (val opcode = json.getOpcode()) { Opcodes.Dispatch -> { // event - val dispatch = try { - bot.eventDecoder.decodeFromJsonElement(Signal.Dispatch.serializer(), json) - } catch (serEx: SerializationException) { -// if (tryCheckIsPolymorphicException(serEx)) { + val serializer = resolveDispatchSerializer( + json = json.jsonObject, + allowNameMissing = true + ) + + val dispatch = if (serializer != null) { + bot.eventDecoder.decodeFromJsonElement(serializer, json) + } else { // 未知的事件类型 - val disSeq = runCatching { - json.jsonObject["s"]?.jsonPrimitive?.longOrNull ?: seq.value - }.getOrElse { seq.value } - // dispatch.id - val id = kotlin.runCatching { - json.jsonObject["id"]?.jsonPrimitive?.contentOrNull - }.getOrNull() + val disSeq = json.tryGetSeq() ?: seq.value + val id = json.tryGetId() Signal.Dispatch.Unknown(id, disSeq, json, raw).also { val t = - kotlin.runCatching { json.jsonObject[Signal.Dispatch.DISPATCH_CLASS_DISCRIMINATOR]?.jsonPrimitive?.content } - .getOrNull() - if (tryCheckIsPolymorphicException(serEx)) { - logger.warn("Unknown event type {}, decode it as Unknown event: {}", t, it) - } else { - logger.warn("Unknown event type {}, decode it as Unknown event: {}", t, it, serEx) - } + kotlin.runCatching { + json.jsonObject[Signal.Dispatch.DISPATCH_CLASS_DISCRIMINATOR] + ?.jsonPrimitive + ?.content + }.getOrNull() + + logger.warn("Unknown event type {}, decode it as Unknown event: {}", t, it) } } + logger.debug("Received dispatch: {}", dispatch) val dispatchSeq = dispatch.seq @@ -428,6 +434,7 @@ internal class ReceiveEvent( } } +@Deprecated("Deprecated") internal fun tryCheckIsPolymorphicException(exception: SerializationException): Boolean { // 似乎是某个版本的错误提示,但是至少在 JSON v1.6.3 已经不适用了 if (exception.message?.startsWith("Polymorphic serializer was not found for") == true) { @@ -443,7 +450,6 @@ internal fun tryCheckIsPolymorphicException(exception: SerializationException): } - /** * [4. 恢复连接](https://bot.q.qq.com/wiki/develop/api/gateway/reference.html#_4-%E6%81%A2%E5%A4%8D%E8%BF%9E%E6%8E%A5) * From 4b14314169b98a8064a1cf29ea0bf72c37c47d2b Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 13:00:03 +0800 Subject: [PATCH 16/28] fix api check --- build.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 90f200ad..1cf37049 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -70,7 +70,8 @@ apiValidation { this.ignoredProjects.addAll( listOf( "api-reader", - "intents-processor" + "intents-processor", + "dispatch-serializer-processor", ), ) From fdb95962ed4811213c2180cc5981370eb387270f Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 15:10:18 +0800 Subject: [PATCH 17/28] Samples --- build.gradle.kts | 4 +- buildSrc/build.gradle.kts | 2 + samples/webhook-server-ktor/build.gradle.kts | 64 ++++++++ .../main/kotlin/com/example/Application.kt | 138 ++++++++++++++++++ .../src/main/resources/logback.xml | 12 ++ .../build.gradle.kts | 59 ++++++++ .../main/kotlin/com/example/Application.kt | 30 ++++ .../kotlin/com/example/CallbackHandler.kt | 93 ++++++++++++ .../src/main/resources/application.yml | 7 + .../webhook-server-spring/build.gradle.kts | 56 +++++++ .../main/kotlin/com/example/Application.kt | 30 ++++ .../kotlin/com/example/CallbackHandler.kt | 86 +++++++++++ .../src/main/resources/application.yml | 7 + settings.gradle.kts | 14 +- 14 files changed, 592 insertions(+), 10 deletions(-) create mode 100644 samples/webhook-server-ktor/build.gradle.kts create mode 100644 samples/webhook-server-ktor/src/main/kotlin/com/example/Application.kt create mode 100644 samples/webhook-server-ktor/src/main/resources/logback.xml create mode 100644 samples/webhook-server-spring-webflux/build.gradle.kts create mode 100644 samples/webhook-server-spring-webflux/src/main/kotlin/com/example/Application.kt create mode 100644 samples/webhook-server-spring-webflux/src/main/kotlin/com/example/CallbackHandler.kt create mode 100644 samples/webhook-server-spring-webflux/src/main/resources/application.yml create mode 100644 samples/webhook-server-spring/build.gradle.kts create mode 100644 samples/webhook-server-spring/src/main/kotlin/com/example/Application.kt create mode 100644 samples/webhook-server-spring/src/main/kotlin/com/example/CallbackHandler.kt create mode 100644 samples/webhook-server-spring/src/main/resources/application.yml diff --git a/build.gradle.kts b/build.gradle.kts index 1cf37049..162ce571 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -63,7 +63,6 @@ idea { } } - apiValidation { ignoredPackages.add("*.internal.*") @@ -72,6 +71,9 @@ apiValidation { "api-reader", "intents-processor", "dispatch-serializer-processor", + "webhook-server-ktor", + "webhook-server-spring", + "webhook-server-spring-webflux", ), ) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index cc875403..bcdf4944 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -30,6 +30,8 @@ val kotlinVersion: String = libs.versions.kotlin.get() dependencies { implementation(kotlin("gradle-plugin", kotlinVersion)) implementation(kotlin("serialization", kotlinVersion)) + // for plugin.spring + implementation(kotlin("allopen", kotlinVersion)) implementation(libs.bundles.dokka) // see https://github.com/gradle-nexus/publish-plugin diff --git a/samples/webhook-server-ktor/build.gradle.kts b/samples/webhook-server-ktor/build.gradle.kts new file mode 100644 index 00000000..39d54336 --- /dev/null +++ b/samples/webhook-server-ktor/build.gradle.kts @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + kotlin("jvm") + id("io.ktor.plugin") version "2.3.12" +} + +repositories { + mavenCentral() +} + +kotlin { + jvmToolchain(JVMConstants.KT_JVM_TARGET_VALUE) + compilerOptions { + javaParameters = true + jvmTarget.set(JvmTarget.fromTarget(JVMConstants.KT_JVM_TARGET_VALUE.toString())) + } +} + +configJavaCompileWithModule() + +//application { +// mainClass.set("com.example.ApplicationKt") +// +// val isDevelopment: Boolean = project.ext.has("development") +// applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") +//} + +dependencies { + implementation(project(":simbot-component-qq-guild-core")) + implementation(libs.simbot.core) + + val ktorVersion = "2.3.12" + implementation("io.ktor:ktor-server-core-jvm:$ktorVersion") + implementation("io.ktor:ktor-server-netty-jvm:$ktorVersion") + + val logbackVersion = "1.4.14" + implementation("ch.qos.logback:logback-classic:$logbackVersion") + + testImplementation("io.ktor:ktor-server-test-host") + testImplementation(kotlin("test-junit5")) +} + +tasks.getByName("test") { + useJUnitPlatform() +} + diff --git a/samples/webhook-server-ktor/src/main/kotlin/com/example/Application.kt b/samples/webhook-server-ktor/src/main/kotlin/com/example/Application.kt new file mode 100644 index 00000000..e00192d6 --- /dev/null +++ b/samples/webhook-server-ktor/src/main/kotlin/com/example/Application.kt @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +package com.example + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import love.forte.simbot.component.qguild.bot.emitEvent +import love.forte.simbot.component.qguild.filterIsQQGuildBotManagers +import love.forte.simbot.component.qguild.useQQGuild +import love.forte.simbot.core.application.launchSimpleApplication +import love.forte.simbot.qguild.stdlib.Ed25519SignatureVerification +import love.forte.simbot.qguild.stdlib.EmitResult + +private const val SIGNATURE_HEAD = "X-Signature-Ed25519" +private const val TIMESTAMP_HEAD = "X-Signature-Timestamp" + +// 你也可以考虑直接把注册好的 bot 保存起来,而不只是保存 application +// 或者使用一些DI方案,都可以。 +lateinit var simbotApplication: love.forte.simbot.application.Application + +suspend fun main() { + // 启动simbot application, + // 然后启动内嵌的 HTTP 服务 + // 当然,具体的启动顺序或逻辑根据你的项目需求而定。 + simbotApplication = launchSimbot() + + embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module) + .start(wait = true) + +} + +/** + * 启动 simbot application + */ +suspend fun launchSimbot(): love.forte.simbot.application.Application { + val application = launchSimpleApplication { + useQQGuild() + } + + // 这里配置你的 bot、事件监听等... + // 你也可以考虑直接把注册好的 bot 保存起来,而不只是保存 application + + return application +} + +fun Application.module() { + configureRouting() +} + +fun Application.configureRouting() { + routing { + post("/callback/qq/{appId}") { + val appId = call.parameters["appId"] + + // 寻找指定 `appId` 的 QGBot + val targetBot = simbotApplication.botManagers + .filterIsQQGuildBotManagers() + .firstNotNullOfOrNull { + it.all().firstOrNull { bot -> + bot.source.ticket.appId == appId + } + } + + // 如果找不到,响应 404 异常 + if (targetBot == null) { + call.respond(HttpStatusCode.NotFound) + return@post + } + + // 准备参数 + val signature = call.request.header(SIGNATURE_HEAD) + ?: run { + call.respond( + HttpStatusCode.BadRequest, + "Required header $SIGNATURE_HEAD is missing" + ) + return@post + } + + val timestamp = call.request.header(TIMESTAMP_HEAD) + ?: run { + call.respond( + HttpStatusCode.BadRequest, + "Required header $TIMESTAMP_HEAD is missing" + ) + return@post + } + val payload = call.receiveText() + + val result = targetBot.emitEvent( + payload, + ) { + // 配置 ed25519SignatureVerification, 即代表进行签名校验 + ed25519SignatureVerification = Ed25519SignatureVerification( + signature, + timestamp + ) + } + + val respond: String? = when (result) { + is EmitResult.Verified -> + // 如果你安装了插件 ContentNegotiation, + // 那么也可以直接响应对象。 + // 这里懒得装了,所以提前序列化成JSON字符串 + Json.encodeToString(result.verified) + + else -> null + } + + call.respondText( + respond ?: "{}", + ContentType.Application.Json + ) + } + } +} diff --git a/samples/webhook-server-ktor/src/main/resources/logback.xml b/samples/webhook-server-ktor/src/main/resources/logback.xml new file mode 100644 index 00000000..bdbb64ec --- /dev/null +++ b/samples/webhook-server-ktor/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/samples/webhook-server-spring-webflux/build.gradle.kts b/samples/webhook-server-spring-webflux/build.gradle.kts new file mode 100644 index 00000000..fffcd935 --- /dev/null +++ b/samples/webhook-server-spring-webflux/build.gradle.kts @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + kotlin("jvm") + kotlin("plugin.spring") + id("org.springframework.boot") version "3.3.4" + id("io.spring.dependency-management") version "1.1.6" +} + +repositories { + mavenCentral() +} + +kotlin { + jvmToolchain(17) + compilerOptions { + javaParameters = true + jvmTarget.set(JvmTarget.JVM_17) + } +} + +configJavaCompileWithModule(jvmVersion = "17") + +dependencies { + implementation(project(":simbot-component-qq-guild-core")) + implementation(libs.simbot.spring) + + implementation("org.springframework.boot:spring-boot-starter-webflux") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("io.projectreactor.kotlin:reactor-kotlin-extensions") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") + + testImplementation("io.projectreactor:reactor-test") + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation(kotlin("test-junit5")) +} + +tasks.getByName("test") { + useJUnitPlatform() +} + diff --git a/samples/webhook-server-spring-webflux/src/main/kotlin/com/example/Application.kt b/samples/webhook-server-spring-webflux/src/main/kotlin/com/example/Application.kt new file mode 100644 index 00000000..bb023fd0 --- /dev/null +++ b/samples/webhook-server-spring-webflux/src/main/kotlin/com/example/Application.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +package com.example + +import love.forte.simbot.spring.EnableSimbot +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@EnableSimbot +@SpringBootApplication +class Application + +fun main(args: Array) { + runApplication(*args) +} diff --git a/samples/webhook-server-spring-webflux/src/main/kotlin/com/example/CallbackHandler.kt b/samples/webhook-server-spring-webflux/src/main/kotlin/com/example/CallbackHandler.kt new file mode 100644 index 00000000..a6f2094e --- /dev/null +++ b/samples/webhook-server-spring-webflux/src/main/kotlin/com/example/CallbackHandler.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +package com.example + +import kotlinx.coroutines.async +import kotlinx.coroutines.future.asCompletableFuture +import love.forte.simbot.application.Application +import love.forte.simbot.component.qguild.bot.emitEvent +import love.forte.simbot.component.qguild.filterIsQQGuildBotManagers +import love.forte.simbot.qguild.stdlib.Ed25519SignatureVerification +import love.forte.simbot.qguild.stdlib.EmitResult +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import org.springframework.web.server.ResponseStatusException +import java.util.concurrent.CompletableFuture + +private const val SIGNATURE_HEAD = "X-Signature-Ed25519" +private const val TIMESTAMP_HEAD = "X-Signature-Timestamp" + +/** + * 处理所有qq机器人的回调请求的处理器。 + */ +@RestController("/callback") +class CallbackHandler( + private val application: Application +) { + + /** + * 处理 `/callback/qq/{appId}` 的事件回调请求, + * 找到对应的 bot 并向其推送事件。 + */ + @PostMapping("/qq/{appId}") + fun handleEvent( + @PathVariable("appId") appId: String, + @RequestHeader(SIGNATURE_HEAD) signature: String, + @RequestHeader(TIMESTAMP_HEAD) timestamp: String, + @RequestBody payload: String, + ): CompletableFuture> { + // 寻找指定 `appId` 的 QGBot + val targetBot = application.botManagers.filterIsQQGuildBotManagers().firstNotNullOfOrNull { + it.all().firstOrNull { bot -> bot.source.ticket.appId == appId } + } + + // 如果找不到,响应 404 异常 + if (targetBot == null) { + throw ResponseStatusException( + HttpStatus.NOT_FOUND, + "app $appId not found" + ) + } + + // 在 servlet web 中,在异步中处理. + // 作用域、是否要用异步等根据你的项目情况调整。 + val entityAsync = application.async { + val result = targetBot.emitEvent( + payload, + ) { + // 配置 ed25519SignatureVerification, 即代表进行签名校验 + ed25519SignatureVerification = Ed25519SignatureVerification( + signature, + timestamp + ) + } + + val body: Any? = when (result) { + is EmitResult.Verified -> result.verified + else -> null + } + + // 响应结果。 + ResponseEntity.ok(body) + } + + return entityAsync.asCompletableFuture() + } + +} diff --git a/samples/webhook-server-spring-webflux/src/main/resources/application.yml b/samples/webhook-server-spring-webflux/src/main/resources/application.yml new file mode 100644 index 00000000..042d4d0e --- /dev/null +++ b/samples/webhook-server-spring-webflux/src/main/resources/application.yml @@ -0,0 +1,7 @@ +server: + port: 8080 + +spring: + application: + name: sample + diff --git a/samples/webhook-server-spring/build.gradle.kts b/samples/webhook-server-spring/build.gradle.kts new file mode 100644 index 00000000..ba9228e5 --- /dev/null +++ b/samples/webhook-server-spring/build.gradle.kts @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + kotlin("jvm") + kotlin("plugin.spring") + id("org.springframework.boot") version "3.3.4" + id("io.spring.dependency-management") version "1.1.6" +} + +repositories { + mavenCentral() +} + +kotlin { + jvmToolchain(17) + compilerOptions { + javaParameters = true + jvmTarget.set(JvmTarget.JVM_17) + } +} + +configJavaCompileWithModule(jvmVersion = "17") + +dependencies { + implementation(project(":simbot-component-qq-guild-core")) + implementation(libs.simbot.spring) + + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.jetbrains.kotlin:kotlin-reflect") + + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation(kotlin("test-junit5")) +} + +tasks.getByName("test") { + useJUnitPlatform() +} + diff --git a/samples/webhook-server-spring/src/main/kotlin/com/example/Application.kt b/samples/webhook-server-spring/src/main/kotlin/com/example/Application.kt new file mode 100644 index 00000000..bb023fd0 --- /dev/null +++ b/samples/webhook-server-spring/src/main/kotlin/com/example/Application.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +package com.example + +import love.forte.simbot.spring.EnableSimbot +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@EnableSimbot +@SpringBootApplication +class Application + +fun main(args: Array) { + runApplication(*args) +} diff --git a/samples/webhook-server-spring/src/main/kotlin/com/example/CallbackHandler.kt b/samples/webhook-server-spring/src/main/kotlin/com/example/CallbackHandler.kt new file mode 100644 index 00000000..7734cae8 --- /dev/null +++ b/samples/webhook-server-spring/src/main/kotlin/com/example/CallbackHandler.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +package com.example + +import love.forte.simbot.application.Application +import love.forte.simbot.component.qguild.bot.emitEvent +import love.forte.simbot.component.qguild.filterIsQQGuildBotManagers +import love.forte.simbot.qguild.stdlib.Ed25519SignatureVerification +import love.forte.simbot.qguild.stdlib.EmitResult +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import org.springframework.web.server.ResponseStatusException + +private const val SIGNATURE_HEAD = "X-Signature-Ed25519" +private const val TIMESTAMP_HEAD = "X-Signature-Timestamp" + +/** + * 处理所有qq机器人的回调请求的处理器。 + */ +@RestController("/callback") +class CallbackHandler( + private val application: Application +) { + /** + * 处理 `/callback/qq/{appId}` 的事件回调请求, + * 找到对应的 bot 并向其推送事件。 + */ + @PostMapping("/qq/{appId}") + suspend fun handleEvent( + @PathVariable("appId") appId: String, + @RequestHeader(SIGNATURE_HEAD) signature: String, + @RequestHeader(TIMESTAMP_HEAD) timestamp: String, + @RequestBody payload: String, + ): ResponseEntity { + // 寻找指定 `appId` 的 QGBot + val targetBot = application.botManagers + .filterIsQQGuildBotManagers() + .firstNotNullOfOrNull { + it.all().firstOrNull { bot -> + bot.source.ticket.appId == appId + } + } + + // 如果找不到,响应 404 异常 + if (targetBot == null) { + throw ResponseStatusException( + HttpStatus.NOT_FOUND, + "app $appId not found" + ) + } + + val result = targetBot.emitEvent( + payload, + ) { + // 配置 ed25519SignatureVerification, 即代表进行签名校验 + ed25519SignatureVerification = Ed25519SignatureVerification( + signature, + timestamp + ) + } + + val body: Any? = when (result) { + is EmitResult.Verified -> result.verified + else -> null + } + + // 响应结果 + return ResponseEntity.ok(body) + } +} diff --git a/samples/webhook-server-spring/src/main/resources/application.yml b/samples/webhook-server-spring/src/main/resources/application.yml new file mode 100644 index 00000000..042d4d0e --- /dev/null +++ b/samples/webhook-server-spring/src/main/resources/application.yml @@ -0,0 +1,7 @@ +server: + port: 8080 + +spring: + application: + name: sample + diff --git a/settings.gradle.kts b/settings.gradle.kts index 07a90dab..23baae11 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,17 +22,13 @@ include(":internal-processors:api-reader") include(":internal-processors:dispatch-serializer-processor") include(":internal-processors:intents-processor") -//include(":builder-generator") include(":simbot-component-qq-guild-api") include(":simbot-component-qq-guild-stdlib") include(":simbot-component-qq-guild-core") -//include(":simbot-component-qq-guild-core") -//include(":simbot-component-qq-guild-benchmark") -// tests -//if (!System.getenv("IS_CI").toBoolean()) { -// include(":tests:application-test") -// include(":tests:spring-boot-test") -// include(":tests:plugin-test") -//} +// samples +include(":samples:webhook-server-ktor") +include(":samples:webhook-server-spring") +include(":samples:webhook-server-spring-webflux") + From 22770dacc5ac28add9e1c4c6aab12240d062241e Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 15:40:13 +0800 Subject: [PATCH 18/28] fix build --- samples/webhook-server-ktor/build.gradle.kts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/webhook-server-ktor/build.gradle.kts b/samples/webhook-server-ktor/build.gradle.kts index 39d54336..cc8f989b 100644 --- a/samples/webhook-server-ktor/build.gradle.kts +++ b/samples/webhook-server-ktor/build.gradle.kts @@ -36,12 +36,12 @@ kotlin { configJavaCompileWithModule() -//application { -// mainClass.set("com.example.ApplicationKt") -// -// val isDevelopment: Boolean = project.ext.has("development") -// applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") -//} +application { + mainClass.set("com.example.ApplicationKt") + + val isDevelopment: Boolean = project.ext.has("development") + applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") +} dependencies { implementation(project(":simbot-component-qq-guild-core")) From 83d0bcfb0f915ee28bb603bd27aa31abbef4fd70 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 16:31:03 +0800 Subject: [PATCH 19/28] Update CI config --- .github/workflows/publish-release.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index c4974d9a..60f11d8f 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -82,6 +82,17 @@ jobs: steps: # 检出仓库代码 - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: ${{ env.JAVA_DISTRIBUTION }} + java-version: ${{ env.JAVA_VERSION }} + cache: 'gradle' + + - name: Publish releases + uses: gradle/actions/setup-gradle@v3 + with: + gradle-version: ${{ env.GRADLE_VERSION }} + arguments: createChangelog # Create gitHub release - name: Create Github Release From ba66b63e742534a0f70d7a1eb1a582f9aed0d531 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 17:10:20 +0800 Subject: [PATCH 20/28] Update simbot from 4.6.0 to 4.6.1 --- gradle/libs.versions.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 252f89ab..85d54509 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,10 +5,9 @@ kotlinx-serialization = "1.7.3" kotlinx-datetime = "0.6.1" dokka = "1.9.20" ktor = "2.3.12" -openjdk-jmh = "1.37" log4j = "2.24.1" # simbot -simbot = "4.6.0" +simbot = "4.6.1" suspendTransform = "2.0.20-0.9.3" gradleCommon = "0.6.0" # ksp From 7464169d8e97889024c3b6bbe1ace05f5fb00886 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 19:56:42 +0800 Subject: [PATCH 21/28] more tests --- .../src/commonTest/kotlin/Ed25519Tests.kt | 49 +++++++++---------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt index 22508ccf..0c91b283 100644 --- a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt +++ b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt @@ -1,10 +1,13 @@ import io.github.andreypfau.curve25519.ed25519.Ed25519 import io.ktor.utils.io.core.* +import kotlinx.coroutines.test.runTest +import love.forte.simbot.qguild.stdlib.BotFactory +import love.forte.simbot.qguild.stdlib.EmitResult import love.forte.simbot.qguild.stdlib.internal.paddingEd25519Seed import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals -import kotlin.test.assertTrue +import kotlin.test.assertIs /** * @@ -50,31 +53,6 @@ class Ed25519TestsCommon { ) } - // is OK - @OptIn(ExperimentalStdlibApi::class) - private fun a() { - val secret = "DG5g3B4j9X2KOErG" - val seed = secret.paddingEd25519Seed() - val body = """{"d":{"plain_token":"Arq0D5A61EgUu4OxUvOp","event_ts":"1725442341"},"op":13}""" - val timestamp = "1725442341" - val sig = - "865ad13a61752ca65e26bde6676459cd36cf1be609375b37bd62af366e1dc25a8dc789ba7f14e017ada3d554c671a911bfdf075ba54835b23391d509579ed002" - - - val sigBytes = sig.hexToByteArray() - - assertEquals(Ed25519.SIGNATURE_SIZE_BYTES, sigBytes.size) - assertEquals(0u, sigBytes[63].toUByte().and(224u)) - - val pk = Ed25519.keyFromSeed(seed.toByteArray()) - val msg = "$timestamp$body" - - println(pk.sign(msg.toByteArray()).toHexString()) - println(pk.sign(msg.toByteArray()).toHexString()) - - assertTrue { pk.publicKey().verify(msg.toByteArray(), sigBytes) } - } - @OptIn(ExperimentalStdlibApi::class) @Test fun ed25519VerifyTest() { @@ -96,10 +74,27 @@ class Ed25519TestsCommon { ) } + @Test + fun opcode13ProcessTest() = runTest { + val bot = BotFactory.create("11111111", "DG5g3B4j9X2KOErG", "") { + disableWs = true + } + + val result = bot.emitEvent( + """{"d":{"plain_token":"Arq0D5A61EgUu4OxUvOp","event_ts":"1725442341"},"op":13}""", + ) + + assertIs(result) + assertEquals("87befc99c42c651b3aac0278e71ada338433ae26fcb24307bdc5ad38c1adc2d01bcfc" + + "adc0842edac85e85205028a1132afe09280305f13aa6909ffc2d652c706", result.verified.signature) + assertEquals("Arq0D5A61EgUu4OxUvOp", result.verified.plainToken) + } + companion object { private const val ASSERT_PUBLIC_HEX = "d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" private const val ASSERT_PRIVATE_HEX = - "6e614f43306f635145337368574c41666666564c42317268595047376e614f43d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" + "6e614f43306f635145337368574c41666666564c42317268595047376e614f43" + + "d7c362fe78aef81ff23287b493628b5db02a3c4fe30b215e4d19609b5d76673a" } } From e0b25bf956d9d177f0dff111dbf6204f7faa41c0 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 19:57:44 +0800 Subject: [PATCH 22/28] release: v4.1.0 --- buildSrc/src/main/kotlin/P.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/P.kt b/buildSrc/src/main/kotlin/P.kt index d024e69d..b80c5220 100644 --- a/buildSrc/src/main/kotlin/P.kt +++ b/buildSrc/src/main/kotlin/P.kt @@ -57,7 +57,7 @@ object P { const val VERSION = "4.1.0" - const val NEXT_VERSION = "4.1.0" + const val NEXT_VERSION = "4.1.1" override val snapshotVersion = "$NEXT_VERSION-SNAPSHOT" override val version = if (isSnapshot()) snapshotVersion else VERSION From 9222d02d960d4e0be68678f70dae7b1d171bacc1 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 20:21:57 +0800 Subject: [PATCH 23/28] release: v4.1.0 --- .../src/commonTest/kotlin/Ed25519Tests.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt index 0c91b283..7eeef385 100644 --- a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt +++ b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt @@ -58,7 +58,7 @@ class Ed25519TestsCommon { fun ed25519VerifyTest() { // val appId = "11111111" val secret = "DG5g3B4j9X2KOErG" - val seed = secret.paddingEd25519Seed() + val seed = secret.paddingEd25519Seed() val plainToken = "Arq0D5A61EgUu4OxUvOp" val ts = "1725442341" // val body = """{"d":{"plain_token":"Arq0D5A61EgUu4OxUvOp","event_ts":"1725442341"},"op":13}""" @@ -85,9 +85,17 @@ class Ed25519TestsCommon { ) assertIs(result) - assertEquals("87befc99c42c651b3aac0278e71ada338433ae26fcb24307bdc5ad38c1adc2d01bcfc" + - "adc0842edac85e85205028a1132afe09280305f13aa6909ffc2d652c706", result.verified.signature) - assertEquals("Arq0D5A61EgUu4OxUvOp", result.verified.plainToken) + assertEquals( + "87befc99c42c651b3aac0278e71ada338433ae26fcb24307bdc5ad38c1adc2d01bcfc" + + "adc0842edac85e85205028a1132afe09280305f13aa6909ffc2d652c706", + result.verified.signature, + "Verified signature not equal" + ) + assertEquals( + "Arq0D5A61EgUu4OxUvOp", + result.verified.plainToken, + "Verified plainToken not equal" + ) } companion object { From 7614dbcfef7b58d557ad7ea3cf94bb3895e97636 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 21:31:12 +0800 Subject: [PATCH 24/28] Migration of ed25519 library and release: v4.1.0 --- kotlin-js-store/yarn.lock | 12 ++ .../build.gradle.kts | 5 +- .../simbot/qguild/stdlib/internal/BotImpl.kt | 24 ++-- .../simbot/qguild/stdlib/internal/Ed25519s.kt | 108 ++++++++++++++++++ .../src/commonTest/kotlin/Ed25519Tests.kt | 94 +++++++++++++-- 5 files changed, 221 insertions(+), 22 deletions(-) create mode 100644 simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/Ed25519s.kt diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 57605d0a..ec6b9598 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -291,6 +291,18 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +libsodium-sumo@^0.7.13: + version "0.7.15" + resolved "https://registry.npmmirror.com/libsodium-sumo/-/libsodium-sumo-0.7.15.tgz#91c1d863fe3fbce6d6b9db1aadaa622733a1d007" + integrity sha512-5tPmqPmq8T8Nikpm1Nqj0hBHvsLFCXvdhBFV7SGOitQPZAA6jso8XoL0r4L7vmfKXr486fiQInvErHtEvizFMw== + +libsodium-wrappers-sumo@0.7.13: + version "0.7.13" + resolved "https://registry.npmmirror.com/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.13.tgz#a33aea845a0bb56db067548f04feba28c730ab8e" + integrity sha512-lz4YdplzDRh6AhnLGF2Dj2IUj94xRN6Bh8T0HLNwzYGwPehQJX6c7iYVrFUPZ3QqxE0bqC+K0IIqqZJYWumwSQ== + dependencies: + libsodium-sumo "^0.7.13" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" diff --git a/simbot-component-qq-guild-stdlib/build.gradle.kts b/simbot-component-qq-guild-stdlib/build.gradle.kts index 6be59c1a..e48f218f 100644 --- a/simbot-component-qq-guild-stdlib/build.gradle.kts +++ b/simbot-component-qq-guild-stdlib/build.gradle.kts @@ -68,7 +68,10 @@ kotlin { api(libs.ktor.client.ws) // https://github.com/andreypfau/curve25519-kotlin - implementation("io.github.andreypfau:curve25519-kotlin:0.0.8") +// implementation("io.github.andreypfau:curve25519-kotlin:0.0.8") + + // https://github.com/ionspin/kotlin-multiplatform-libsodium + implementation("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings:0.9.2") } commonTest.dependencies { diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt index a235e7d8..c278dcb3 100644 --- a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/BotImpl.kt @@ -17,7 +17,7 @@ package love.forte.simbot.qguild.stdlib.internal -import io.github.andreypfau.curve25519.ed25519.Ed25519 +import com.ionspin.kotlin.crypto.signature.crypto_sign_BYTES import io.ktor.client.* import io.ktor.client.plugins.* import io.ktor.client.plugins.contentnegotiation.* @@ -106,13 +106,15 @@ internal class BotImpl( } } - private val ed25519PrivateKey by lazy { - Ed25519.keyFromSeed(ticket.secret.paddingEd25519Seed().toByteArray()) + private val ed25519KeyPair: Ed25519Keypair by lazy { + genEd25519Keypair(ticket.secret.paddingEd25519Seed().toByteArray()) } - private val ed25519PublicKey by lazy { - ed25519PrivateKey.publicKey() - } + private val ed25519PrivateKey + get() = ed25519KeyPair.privateKey + + private val ed25519PublicKey + get() = ed25519KeyPair.publicKey internal val eventDecoder = Signal.Dispatch.dispatchJson { isLenient = true @@ -425,9 +427,9 @@ internal class BotImpl( val signatureBytes = signature.hexToByteArray() - check(Ed25519.SIGNATURE_SIZE_BYTES == signatureBytes.size) { + check(crypto_sign_BYTES == signatureBytes.size) { "Invalid signature hex size, " + - "expect ${Ed25519.SIGNATURE_SIZE_BYTES}, " + + "expect ${crypto_sign_BYTES}, " + "actual ${signatureBytes.size}" } @@ -452,7 +454,11 @@ internal class BotImpl( val signature = ed25519PrivateKey.sign(msg.toByteArray()) - val verified = Signal.CallbackVerify.Verified(plainToken, signature.toHexString()) + val verified = Signal.CallbackVerify.Verified( + plainToken, + signature.signatureBytes().toHexString() + ) + return EmitResult.Verified(verified) } diff --git a/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/Ed25519s.kt b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/Ed25519s.kt new file mode 100644 index 00000000..45f31cf2 --- /dev/null +++ b/simbot-component-qq-guild-stdlib/src/commonMain/kotlin/love/forte/simbot/qguild/stdlib/internal/Ed25519s.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-qq-guild. + * + * simbot-component-qq-guild is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-qq-guild is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-qq-guild. + * If not, see . + */ + +package love.forte.simbot.qguild.stdlib.internal + +import com.ionspin.kotlin.crypto.LibsodiumInitializer +import com.ionspin.kotlin.crypto.signature.InvalidSignatureException +import com.ionspin.kotlin.crypto.signature.Signature +import com.ionspin.kotlin.crypto.signature.SignatureKeyPair +import com.ionspin.kotlin.crypto.signature.crypto_sign_BYTES +import love.forte.simbot.common.atomic.atomic +import love.forte.simbot.logger.LoggerFactory +import kotlin.jvm.JvmInline + +@JvmInline +@OptIn(ExperimentalUnsignedTypes::class) +internal value class Ed25519Keypair(private val keyPair: SignatureKeyPair) { + val publicKey: Ed25519PublicKey + get() = Ed25519PublicKey(keyPair.publicKey) + + val privateKey: Ed25519PrivateKey + get() = Ed25519PrivateKey(keyPair.secretKey) +} + +@JvmInline +@OptIn(ExperimentalUnsignedTypes::class) +internal value class Ed25519PrivateKey(private val key: UByteArray) { + fun sign(msg: ByteArray): SignResult = + SignResult(Signature.sign(msg.asUByteArray(), key)) + + @JvmInline + internal value class SignResult(private val signature: UByteArray) { + fun signatureUBytes(): UByteArray = + signature.copyOf(crypto_sign_BYTES) + + fun signatureBytes(): ByteArray = + signatureUBytes().asByteArray() + + fun plainUBytes(): UByteArray = + signature.copyOfRange(crypto_sign_BYTES, signature.size) + + fun plainBytes(): ByteArray = + plainUBytes().asByteArray() + } +} + +@JvmInline +@OptIn(ExperimentalUnsignedTypes::class) +internal value class Ed25519PublicKey(private val key: UByteArray) { + fun verify(signature: ByteArray, message: ByteArray): Boolean { + return try { + Signature.verifyDetached( + signature.asUByteArray(), + message.asUByteArray(), + key + ) + true + } catch (ise: InvalidSignatureException) { + false + } + + } +} + +@OptIn(ExperimentalUnsignedTypes::class) +internal fun genEd25519Keypair(seed: ByteArray): Ed25519Keypair { + initialed + + val pk = Signature.seedKeypair(seed.asUByteArray()) + return Ed25519Keypair(pk) +} + +private val Ed25519Logger = LoggerFactory.getLogger("love.forte.simbot.qguild.stdlib.internal.Ed25519") + +private val initialed: Unit by lazy( + mode = LazyThreadSafetyMode.SYNCHRONIZED +) { + if (!LibsodiumInitializer.isInitialized()) { + Ed25519Logger.info("LibsodiumInitializer is not initialed yet, initializing...") + val done = atomic(false) + LibsodiumInitializer.initializeWithCallback { + Ed25519Logger.info("LibsodiumInitializer initialized in callback") + done.value = true + } + + @Suppress("ControlFlowWithEmptyBody") + while (!done.value) { + } + + Ed25519Logger.info("LibsodiumInitializer initialized") + } + + Unit +} diff --git a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt index 7eeef385..ee2ecfa7 100644 --- a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt +++ b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt @@ -1,4 +1,6 @@ -import io.github.andreypfau.curve25519.ed25519.Ed25519 +import com.ionspin.kotlin.crypto.LibsodiumInitializer +import com.ionspin.kotlin.crypto.signature.Signature +import com.ionspin.kotlin.crypto.signature.crypto_sign_BYTES import io.ktor.utils.io.core.* import kotlinx.coroutines.test.runTest import love.forte.simbot.qguild.stdlib.BotFactory @@ -15,6 +17,35 @@ import kotlin.test.assertIs */ class Ed25519TestsCommon { + +// @OptIn(ExperimentalStdlibApi::class) +// @Test +// fun ed25519KeyGenTest() { +// // val secret = "naOC0ocQE3shWLAfffVLB1rhYPG7" +// val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" +// +// +// +// val pk = Ed25519.keyFromSeed(seed.toByteArray()) +// +// assertContentEquals( +// ASSERT_PUBLIC_HEX.hexToByteArray(), +// pk.publicKey().toByteArray() +// ) +// +// assertContentEquals( +// ASSERT_PRIVATE_HEX.hexToByteArray(), +// pk.toByteArray() +// ) +// } + +// @BeforeTest +// fun initLibsodiumInitializer() = runTest { +// if (!LibsodiumInitializer.isInitialized()) { +// LibsodiumInitializer.initialize() +// } +// } + /** * https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/sign.html * @@ -22,22 +53,23 @@ class Ed25519TestsCommon { * 根据开发者平台的 Bot Secret 值进行repeat操作得到签名32字节的 seed , * 根据 seed 调用 Ed25519 算法生成32字节公钥 */ - @OptIn(ExperimentalStdlibApi::class) + @OptIn(ExperimentalUnsignedTypes::class, ExperimentalStdlibApi::class) @Test - fun ed25519KeyGenTest() { + fun libsodiumEd25519KeyGenTest() = runTest { + LibsodiumInitializer.initialize() // val secret = "naOC0ocQE3shWLAfffVLB1rhYPG7" val seed = "naOC0ocQE3shWLAfffVLB1rhYPG7naOC" - val pk = Ed25519.keyFromSeed(seed.toByteArray()) + val pk = Signature.seedKeypair(seed.toByteArray().asUByteArray()) assertContentEquals( ASSERT_PUBLIC_HEX.hexToByteArray(), - pk.publicKey().toByteArray() + pk.publicKey.asByteArray() ) assertContentEquals( ASSERT_PRIVATE_HEX.hexToByteArray(), - pk.toByteArray() + pk.secretKey.asByteArray() ) } @@ -53,9 +85,30 @@ class Ed25519TestsCommon { ) } - @OptIn(ExperimentalStdlibApi::class) +// @OptIn(ExperimentalStdlibApi::class) +// @Test +// fun ed25519VerifyTest() { +// // val appId = "11111111" +// val secret = "DG5g3B4j9X2KOErG" +// val seed = secret.paddingEd25519Seed() +// val plainToken = "Arq0D5A61EgUu4OxUvOp" +// val ts = "1725442341" +// // val body = """{"d":{"plain_token":"Arq0D5A61EgUu4OxUvOp","event_ts":"1725442341"},"op":13}""" +// +// val pk = Ed25519.keyFromSeed(seed.toByteArray()) +// val msg = "$ts$plainToken" +// +// val signature = pk.sign(msg.toByteArray()) +// +// assertEquals( +// "87befc99c42c651b3aac0278e71ada338433ae26fcb24307bdc5ad38c1adc2d01bcfcadc0842edac85e85205028a1132afe09280305f13aa6909ffc2d652c706", +// signature.toHexString() +// ) +// } + + @OptIn(ExperimentalUnsignedTypes::class, ExperimentalStdlibApi::class) @Test - fun ed25519VerifyTest() { + fun libsodiumEd25519VerifyTest() { // val appId = "11111111" val secret = "DG5g3B4j9X2KOErG" val seed = secret.paddingEd25519Seed() @@ -63,14 +116,30 @@ class Ed25519TestsCommon { val ts = "1725442341" // val body = """{"d":{"plain_token":"Arq0D5A61EgUu4OxUvOp","event_ts":"1725442341"},"op":13}""" - val pk = Ed25519.keyFromSeed(seed.toByteArray()) + val pk = Signature.seedKeypair(seed.toByteArray().asUByteArray()) +// val pk = Ed25519.keyFromSeed(seed.toByteArray()) + val msg = "$ts$plainToken" - val signature = pk.sign(msg.toByteArray()) + // 得到的是加密的密文 (size=crypto_sign_BYTES) + // 和原文 (size=msg.size) + val signature = Signature.sign( + msg.toByteArray().asUByteArray(), + pk.secretKey + ) + + val plain = signature.copyOfRange(crypto_sign_BYTES, signature.size) + + assertEquals( + msg.toByteArray().toHexString(), + plain.toHexString() + ) + +// val signature = pk.sign(msg.toByteArray()) assertEquals( "87befc99c42c651b3aac0278e71ada338433ae26fcb24307bdc5ad38c1adc2d01bcfcadc0842edac85e85205028a1132afe09280305f13aa6909ffc2d652c706", - signature.toHexString() + signature.copyOf(crypto_sign_BYTES).toHexString() ) } @@ -80,6 +149,7 @@ class Ed25519TestsCommon { disableWs = true } + val result = bot.emitEvent( """{"d":{"plain_token":"Arq0D5A61EgUu4OxUvOp","event_ts":"1725442341"},"op":13}""", ) @@ -90,7 +160,7 @@ class Ed25519TestsCommon { "adc0842edac85e85205028a1132afe09280305f13aa6909ffc2d652c706", result.verified.signature, "Verified signature not equal" - ) + ) assertEquals( "Arq0D5A61EgUu4OxUvOp", result.verified.plainToken, From ef3c24cf10453f84058b9db36f72620d4912c192 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 21:41:08 +0800 Subject: [PATCH 25/28] release: v4.1.0 --- .github/workflows/publish-release.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 60f11d8f..e3e7dbd2 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -43,6 +43,14 @@ jobs: - name: Run all tests run: gradle assemble allTests --stacktrace --warning-mode all + - name: Upload test reports + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-reports-${{ runner.os }} + path: '**/build/reports/tests' + retention-days: 7 + publish-releases: name: Publish releases needs: run-test From 92ffb862f6da0366f7507495f1ceee5129f9bf7a Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 21:48:29 +0800 Subject: [PATCH 26/28] fix test and release: v4.1.0 --- .../src/commonTest/kotlin/Ed25519Tests.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt index ee2ecfa7..19020258 100644 --- a/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt +++ b/simbot-component-qq-guild-stdlib/src/commonTest/kotlin/Ed25519Tests.kt @@ -1,6 +1,7 @@ import com.ionspin.kotlin.crypto.LibsodiumInitializer import com.ionspin.kotlin.crypto.signature.Signature import com.ionspin.kotlin.crypto.signature.crypto_sign_BYTES +import io.ktor.client.engine.mock.* import io.ktor.utils.io.core.* import kotlinx.coroutines.test.runTest import love.forte.simbot.qguild.stdlib.BotFactory @@ -147,6 +148,9 @@ class Ed25519TestsCommon { fun opcode13ProcessTest() = runTest { val bot = BotFactory.create("11111111", "DG5g3B4j9X2KOErG", "") { disableWs = true + apiClientEngine = MockEngine { + respondOk() + } } From 09c2986c41c8e72bd5df5582d57e1abf8dd19d4e Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 22:36:05 +0800 Subject: [PATCH 27/28] fix test and release: v4.1.0 --- simbot-component-qq-guild-stdlib/build.gradle.kts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/simbot-component-qq-guild-stdlib/build.gradle.kts b/simbot-component-qq-guild-stdlib/build.gradle.kts index e48f218f..f014426a 100644 --- a/simbot-component-qq-guild-stdlib/build.gradle.kts +++ b/simbot-component-qq-guild-stdlib/build.gradle.kts @@ -52,7 +52,10 @@ kotlin { } applyTier1() - applyTier2() + applyTier2( + // multiplatform-crypto-libsodium 不支持 watchosX64 target. + watchosX64 = false + ) applyTier3(supportKtorClient = true) sourceSets { From 81bfbf5f22104cfd9d5145aa97f4a610ccafbd61 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Thu, 17 Oct 2024 22:46:11 +0800 Subject: [PATCH 28/28] fix build, remove target watchosX64 and release: v4.1.0 --- simbot-component-qq-guild-core/build.gradle.kts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/simbot-component-qq-guild-core/build.gradle.kts b/simbot-component-qq-guild-core/build.gradle.kts index 5c9e7688..e08a8a12 100644 --- a/simbot-component-qq-guild-core/build.gradle.kts +++ b/simbot-component-qq-guild-core/build.gradle.kts @@ -52,7 +52,10 @@ kotlin { } applyTier1() - applyTier2() + applyTier2( + // stdlib 不再支持 + watchosX64 = false + ) applyTier3(supportKtorClient = true) sourceSets {