diff --git a/.github/workflows/bintray.yml b/.github/workflows/bintray.yml index 54cb2eb01..19a978607 100644 --- a/.github/workflows/bintray.yml +++ b/.github/workflows/bintray.yml @@ -6,8 +6,7 @@ name: Bintray Publish # events but only for the master branch on: release: - types: - - created + types: [created, prereleased] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -28,31 +27,36 @@ jobs: run: ./gradlew build # if test's failed, don't publish - name: Check keys - run: ./gradlew :mirai-core-utils:ensureBintrayAvailable - :mirai-core-api:ensureBintrayAvailable - :mirai-core:ensureBintrayAvailable - -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} - -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + run: > + ./gradlew :mirai-core-utils:ensureBintrayAvailable + :mirai-core-api:ensureBintrayAvailable + :mirai-core:ensureBintrayAvailable + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - name: Gradle :mirai-core-utils:publish - run: ./gradlew :mirai-core-utils:publish --info - -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} - -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + run: > + ./gradlew :mirai-core-utils:publish --info + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - name: Gradle :mirai-core-api:publish - run: ./gradlew :mirai-core-api:publish --info - -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} - -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + run: > + ./gradlew :mirai-core-api:publish --info + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - name: Gradle :mirai-core:publish - run: ./gradlew :mirai-core:publish --info - -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} - -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + run: > + ./gradlew :mirai-core:publish --info + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - name: Gradle :mirai-core-all:bintrayUpload - run: ./gradlew :mirai-core-all:bintrayUpload --info - -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} - -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + run: > + ./gradlew :mirai-core-all:bintrayUpload --info + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} # - name: Upload artifact # uses: actions/upload-artifact@v1.0.0 diff --git a/.github/workflows/devTagPublish.yml b/.github/workflows/devTagPublish.yml index ff91b6ea4..36cb0d0d2 100644 --- a/.github/workflows/devTagPublish.yml +++ b/.github/workflows/devTagPublish.yml @@ -29,31 +29,36 @@ jobs: run: ./gradlew build # if test's failed, don't publish - name: Check keys - run: ./gradlew :mirai-core-utils:ensureBintrayAvailable - :mirai-core-api:ensureBintrayAvailable - :mirai-core:ensureBintrayAvailable - -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} - -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + run: >- + ./gradlew :mirai-core-utils:ensureBintrayAvailable + :mirai-core-api:ensureBintrayAvailable + :mirai-core:ensureBintrayAvailable + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - name: Gradle :mirai-core-utils:publish - run: ./gradlew :mirai-core-utils:publish --info - -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} - -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + run: >- + ./gradlew :mirai-core-utils:publish --info + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - name: Gradle :mirai-core-api:publish - run: ./gradlew :mirai-core-api:publish --info - -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} - -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + run: >- + ./gradlew :mirai-core-api:publish --info + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - name: Gradle :mirai-core:publish - run: ./gradlew :mirai-core:publish --info - -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} - -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + run: >- + ./gradlew :mirai-core:publish --info + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} - name: Gradle :mirai-core-all:bintrayUpload - run: ./gradlew :mirai-core-all:bintrayUpload - -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} - -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + run: >- + ./gradlew :mirai-core-all:bintrayUpload + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} # - name: Upload artifact # uses: actions/upload-artifact@v1.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 9fe03dc7c..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,1180 +0,0 @@ -# Version 1.x - -## `1.3.2` 2020/10/04 - -- 修复缓存过滤的错误导致的戳一戳等信息没法及时处理的问题 - -## `1.3.1` 2020/9/21 - -### 新特性 -- 无 - -### 优化和修复 -- 修复 `BotJoinGroupEvent.Retrieve` 的显示错误 (#606 by @Karlatemp) -- 添加缺少的戳一戳模板 (#615 by @sandtechnology) -- 修复罕见情况下的群成员加群 `member -XXXX not found in XXXX` - -## `1.3.0` 2020/9/16 - -### 新特性 - -- 支持群恢复相关事件: `MemberJoinEvent.Retrieve`, `BotJoinGroupEvent.Retrieve` (#531 by @Karlatemp) -- 群荣耀获取 (`Bot._lowLevelGetGroupHonorListData`) (#501 by @yyuueexxiinngg) -- 戳一戳事件: `MemberNudgedEvent`, `BotNudgedEvent`.(#600 by @sandtechnology) -- 发送戳一戳: `Bot.nudge()`, `User.nudge()` -- 为 `BotFactory` 添加伴生对象. 在顶层方法不方便使用时可使用伴生对象的 `Bot` 构建方法 - -### 优化和修复 -- **修复好友消息和事件同步相关问题:** - - 部分情况下无法同步好友消息 (#249) - - BotInvitedJoinGroupRequestEvent 重复执行两次 (#449) - - 群消息可能发送失败 (#527) - - 机器人启动后第二次被拉入群聊不会刷新群列表 (#580) - - 新群员入群事件只触发一次 (#590) - - 入群申请 MemberJoinRequestEvent 没有广播 (#542) - - 新成员入群未处理 (#567) -- 修复 `At` 之后的多余空格的问题 (#557) -- 修复 `QuoteReply` 前多余 `At` 和空格的问题 (#524) -- 修复群信息初始值 (`isMuteAll`, `isAllowMemberInvite`, ...) (#286) -- 修复 `Voice.url` 的域名缺失问题 (#584 by @Hieuzest) -- 修复日志颜色污染的问题 (#596 by @Karlatemp) -- 修复 `Bot.nick` 无法获取的问题 (#566) -- 修复登录时 `IndexOutOfBoundsException` 的问题 (#598) -- 修复 Bot 被踢出群收到的事件类型错误的问题 (#358) (#509 by @sandtechnology) -- 修复 `Bot.close` 后必须收到一个数据包才会关闭的问题 (#557) -- 优化 `PermissionDeniedException` 的消息内容 - -## `1.2.3` 2020/9/11 -- 在同步事件失败时添加重试, 改善 #249, #482, #542, #567, #590 -- 修复不断重连同一个服务器的问题 (#589) -- 修复 `BotJoinGroupEvent.Active` 和 `BotJoinGroupEvent.Invite` 没有继承 `BotJoinGroupEvent` 的问题 (#532 by [@yyuueexxiinngg](https://github.com/yyuueexxiinngg)) -- 修复 `EmptyMessageChain` 与其他 `MessageChain` 拼接时发生 `NoSuchElementException` 的问题 (#550, #561 by [@sandtechnology](https://github.com/sandtechnology)) -- 为 `Image.queryUrl` 添加 JvmBlockingBridge -- 优化 `PermissionDeniedException` 的错误信息 -- 更新到 Kotlin `1.4.10` - -## `1.2.2` 2020/8/22 -- 修复依赖冲突问题 (#523) - -## `1.2.1` 2020/8/19 -- 修复在 Java 调用 `group.uploadImage` 时编译出错的问题 (#511) -- 为 `group.uploadVoice` 添加 Java 方法 (需要 [kotlin-jvm-blocking-bridge](https://github.com/mamoe/kotlin-jvm-blocking-bridge)) (#512) -- 更新 ktor 到 1.4.0 - -## `1.2.0` 2020/8/19 - -### 新特性 -- 初步语音支持: `Group.uploadVoice`, 支持 silk 或 amr 格式. - **注意**: 现阶段语音实现仅为临时方案, 在将来 (`2.0.0`) 一定会变动. 使用时请评估可能带来的不兼容性. - -- 新增将日志转换为 log4j, JDK Logger, SLF4J 等框架的方法: `LoggerAdapters` (#498 by [@Karlatemp](https://github.com/Karlatemp)) -- 支持解析好友输入状态: `FriendInputStatusChangedEvent` (by [@sandtechnology](https://github.com/sandtechnology)) -- 支持解析好友昵称改变事件: `FriendNickChangedEvent` (#507 by [@Karlatemp](https://github.com/Karlatemp)) -- `nextEvent` 和 `nextEventOrNull` 添加 `filter` -- 将 mirai 码相关内容从 mirai-serialization 集成到 mirai-core -- `GroupMessageEvent` 现在实现接口 `GroupEvent` -- `FriendMessageEvent` 现在实现接口 `FriendEvent` (#444) - -### 依赖更新 -- 更新 Kotlin 版本到 [`1.4.0`](https://blog.jetbrains.com/zh-hans/kotlin/2020/08/kotlin-1-4-released-with-a-focus-on-quality-and-performance-zh/) -- 更新 kotlinx-coroutines-core 到 `1.3.9` -- 使用者也需要更新到 `1.4.0`, 至少更新编译器 (Maven 和 Gradle 插件) -- **更新 kotlinx-serialization 到 `1.0.0-RC`**: kotlinx-serialization 在此版本做了较大的不兼容改动. - -### API 弃用 -- `String.toMessage`: 为避免和 mirai code 产生混乱. -- `URL.toExternalImage` -- `Input.toExternalImage` - -### 优化和修复 -- 显式 API - - mirai 的所有公开 API 均已经显式加上 `public` 修饰符, 遵循 [Koltin Explicit API mode](https://kotlinlang.org/docs/reference/whatsnew14.html#explicit-api-mode-for-library-authors) 规范. 如: - ```kotlin - data class BotOnlineEvent internal constructor( - override val bot: Bot - ) : BotActiveEvent, AbstractEvent() - ``` - - *调整了一些不应该公开的 API 为 `internal`, 这些调整在绝大多数情况下不影响现有代码* -- 修复群权限判断失败的问题 (#389) -- 修复 `syncFromEvent` 文档错误 (#427) -- 新增 `BotConfiguration.loadDeviceInfoJson(String)` (#450) -- 修复成员进群后第一次发言触发改名事件的问题 (#475 by [@cxy654849388](https://github.com/cxy654849388) -- 修复 `group.quit` 未正确执行的问题 (#472, #477 by [@Mr4s](https://github.com/Mrs4s) -- 修复初始化时 syncCookie 同步问题 -- 修复 Network Protocol: java.lang.IllegalStateException: returnCode = -10008 (#470) -- 修复 `Member.isMuted` -- 修复 `Method.registerEvent` 相关问题 (#495 by @sandtechnology, #499 by @Karlatemp) -- 修复 Android 手表协议无法监听撤回事件的问题 (#448) -- 改进好友消息同步过程 - -## `1.1.3` 2020/7/17 -- 修复 ListenerHost Java 兼容性问题 (#443, #446 by [@Karlatemp](https://github.com/Karlatemp)) - -## `1.1.2` 2020/7/16 -- 修复 JvmMethodEvents `T.registerEvents` 注册时错误判断 `@NotNull` 注解的问题 (#436) - -## `1.1.1` 2020/7/11 -- 修复最后一个 mirai 码之后的消息无法解析的问题 (#431 [@cxy654849388](https://github.com/cxy654849388)) - -## `1.1.0` 2020/7/9 -- 支持 Android 手表协议 (`BotConfiguration.MiraiProtocol.ANDROID_WATCH`) -- `EventHandler` 现在支持 `Nothing` 类型. -- 修复无需同意直接进群时,在加载新群信息完成前收到消息过早处理的问题 (#370) -- 修复在某些情况下,管理员邀请群Bot加群会被误判为群成员申请加群的问题 (#402 by [@kenvix](https://github.com/kenvix)) -- 修复从其他客户端加群时未同步的问题 (#404, #410) -- 修复 `ConfigPushSvc.PushReq` 解析失败的问题 (#417) -- 修复 `_lowLevelGetGroupActiveData` -- 修复 `SimpleListenerHost.coroutineScope` 潜在的 Job 被覆盖的问题 - -## `1.0.4` 2020/7/2 -- 修复上传图片失败时内存泄露的问题 (#385) -- 修复大量图片同时上传时出错的问题 (#387) -- 修复在一些情况下 BotOfflineEvent 没有正常处理而无法继续接收消息的问题 (#376) -- 修复 Bot 在某个群 T 出某个人导致 Bot 终止的问题 (#372) -- 修复 `@PlannedRemoval` 的文档 - -## `1.1-EA2` 2020/7/2 - -- 添加 `BotConfiguration.json`, 作为序列化时使用的 Json format, 修复潜在的因 kotlinx.serialization 进行不兼容更新而导致的不兼容. - -**不兼容变更**: -- Image.imageId 后缀由 `.mirai` 变为图片文件实际类型, 如 `.png`, `.jpg`. 兼容原 `.mirai` 后缀. - -**修复**: -- ([1.0.4](https://github.com/mamoe/mirai/releases/tag/1.0.4) 中修复的问题) -- ([1.0.3](https://github.com/mamoe/mirai/releases/tag/1.0.3) 中修复的问题) - -## `1.0.3` 2020/6/29 -- 修复 friendlist.GetTroopListReqV2:java.lang.IllegalStateException: type mismatch 10 (#405) - -## `1.1-EA` 2020/6/16 - -**主要**: -- 添加实验性 `CodableMessage` 作为支持 mirai 码的 `Message` 的接口. -- 支持 [mirai 码](docs\mirai-code-specification.md) 解析; 新模块 [`mirai-serialization`](mirai-serialization) -- 实现 `MessagePreSendEvent` 和 `MessagePostSendEvent` (#339). - -**不兼容变更**: -- 重命名实验性 API `CustomMessage.Factory.serialize` 到 `CustomMessage.Factory.dump` -- 重命名实验性 API `CustomMessage.Factory.deserialize` 到 `CustomMessage.Factory.load` -- 弃用 `MessageSendEvent` (#339). 迁移计划: WARNING in 1.1.0, ERROR in 1.2.0, REMOVE in 1.3.0 -- 调整 `VipFace` 的 mirai 码表示, 详见 mirai 码规范 -- `Face.toString()` 现在返回表情名称, 如 "\[偷笑\]", 而不是 "\[表情\]" (#345 @goldimax) - -**优化和修复**: - -- 修复群头像的获取不正确的问题 (#340) -- 将 `PttMessage` 与 `Voice` 标注 `@MiraiExperimentalAPI` (missing) -- 删除 `Message.plus(another: Flow)` 的 `@ExperimentalCoroutinesApi` -- 提升发送群消息的稳定性 -- 一些文档优化 -- 其他内部优化 -- 提升在上个版本中弃用的 API 的弃用等级 - - -## `1.0.2` 2020/6/1 -- 新增 `Bot.botInstancesSequence` -- 修复日志中的时间未更新的问题 -- 修复在某些情况下,Bot登录的时候无限重连 (#361) -- 优化一些文档注释 - -## `1.0.1` 2020/5/25 -- 新增临时会话消息发送事件: `TempMessageSendEvent` (#338) -- 新增 `Bot.isOnline` (#342) -
- -- 修复日志重定向到文件后无换行的问题 -- 修复 Bot 被邀请入群事件的解析, 添加 `BotJoinGroupEvent.Invite` (#344) -- 修复 IPv6 地址支持 (#334) -- 修复一些 KDoc (#337) -- 优化一些内部的日志的显示 - -## `1.0.0` 2020/5/22 - -- `ContactOrBot` 现在继承 `CoroutineScope` -- 在没有手动指定 `deviceInfo` 时构建 Bot 将会发出警告, 须手动选择使用 `randomDeviceInfo` 或 `fileBasedDeviceInfo` 或自定义, 详见 [BotConfiguration.kt: Lines 69-72](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.common.kt#L69-L72) -
- -- 引入 `SimpleListenerHost` 以帮助 Java 处理事件监听 -- 添加 Java 广播事件的方式: `EventKt.broadcast(Event)` -- 添加 `Bot.getInstanceOrNull` -- 改进 JVM 平台的 `PlatformLogger`, 添加 `DirectoryLogger`, `SingleFileLogger` 以提供重定向日志的快捷方式 -- 统一日志格式, 使用 (正则) `^([\w-]*\s[\w:]*)\s(\w)\/(.*?):\s(.+)$`. 详见 [PlatformLogger.jvm.kt: Line 46](mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.jvm.kt#L46) - -
- -- 弃用 `Bot.queryUrl(Image)`, 改用 `image.queryUrl()` 扩展. (保留兼容到 1.2.0) -- 弃用 `Bot.accept*`, `Bot.reject*` 等相应入群请求等事件的方法, 改用事件的成员函数. (保留兼容到 1.2.0) - -
- -- 修复 `Bot` 实例化时 `NPE` 问题 -- 修复网络状态差时 `Bot` 网络模块无法处理分包的问题 -- 修复当无 Bot 在线时调用 `image.queryUrl()` 抛出的异常与 KDoc 描述不符的问题 -- 修复 `BotJoinGroupEvent` 重复广播问题 -- 修复邀请 Bot 进群时事件处理异常的问题 (#319) -- 修复当 `Event` 被实现为一个 Kotlin `object` 时无法正常拦截事件的问题 -- 修复图片链接获取为空的问题 (#318) -- 修复成员被移除群后可能发生内存泄露的问题 -- 修复异常没有正确输出到日志的问题 -- 修复一些 `DefaultLogger` 的不恰当使用的问题 -- 修复 `UnknownHostException` 未被正常捕获的问题 - -
- -- 在 Bot 被禁言时忽略 `reply` 方式创建的监听器 (`subscribeMessages` DSL) -- 使用更宽松的方式读取 `device.json` -- 将 `Bot.selfQQ` 标注 `@MiraiExperimentalAPI` -- 提高默认心跳超时时间 -- 改进多处 KDoc -- 更新 kotlinx-coroutines-core 到 1.3.7 -- ... 忽略了内部变动 - -## `1.0-RC2-1` 2020/5/11 -修复一个 `VerifyError` - -## `1.0-RC2` 2020/5/11 -主要内容: -- 增强网络稳定性 (#298, #317), 修复 `Bot.close` 或 Bot 离线后没有从 `Bot.botInstances` 中删除的问题 (#317) -- `subscribeMessages` 现在默认使用 `MONITOR` 优先级 -- `MessageChain` 现在继承 `List` -- 新增 `messageChainOf(vararg Message)` -- 支持 Bot 头像更改事件: `BotAvatarChangedEvent` (#271) -- 支持好友头像更改事件: `FriendAvatarChangedEvent` -- 新增 `nextEventOrNull`: 挂起当前协程, 直到监听到事件的广播, 返回这个事件实例. 超时时返回 `null` -- **弃用 `Bot.subscribe.*`, `Bot.nextMessage`, `Bot.subscribe.*Messages`: - 为了更好的协程生命周期管理, 这些函数已经被隐藏, 保留二进制兼容到 1.3.0**. - 现有源代码不会被破坏, 但将不再筛选事件的 `Bot` 实例. 在 mirai 决定好替代的 API 前需要手动筛选. (即不影响目前单 Bot 运行的服务) -- 支持在事件监听时使用 Kotlin 函数引用: - ```kotlin - suspend fun onMessage(event: GroupMessageEvent): ListeningStatus { - return ListeningStatus.LISTENING - } - scope.subscribe(::onMessage /*, priority=..., concurrency=... */) - ``` -- 支持反射式事件监听, 改善 Java 的事件监听体验. 示例查看 [JvmMethodEventsTest.kt: Line 22](mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/event/JvmMethodEventsTest.kt#L22) -- 添加 `typealias EventPriority = Listener.EventPriority` -- 优化 `Face` 的构造器: 现在 `Face` 拥有一个参数为 `id` 的公开构造器 -- 让 `ContactList` 实现接口 `Collection` -- 弃用 `QuoteReply.time` 等语意不明的扩展 (无法区分 `time` 是 `source` 的时间还是 `QuoteReply` 自身时间) - -优化 & 修复: -- 删除 `FileCacheStrategy.newImageCache(URL, format: String)` 中的 `format` 参数 -- 隐藏 `MessageChain` 原有 `Iterable` 相关 API (兼容现有代码) -- 修复 `Message.repeat` -- 修复 `MemberJoinEvent` 比 `MemberJoinRequestEvent` 早广播的问题 (#288) -- 修复 Bot 接受好友申请时 groupId 处理错误 (#309) -- 修复 `MessageSubscribersBuilder` 一处 KDoc 错误 (#308 @wuxianucw) -- 修复 Android 平台 `BufferedImage ClassNotDefFound` 的问题 -- 优化 `MessageSource.internalId` KDoc -- 优化 重连时的计时显示 (#311 @Karlatemp) -- 优化 `Bot.getInstance` 找不到相关 `Bot` 实例时的异常信息 -- 将 `MessageMetadata.contentToString` 定义为 `final` -- 忽略了 732 类型同步消息 (原启动后会大量显示) -- 忽略 'VIP 进群提示' 的群同步消息 -- 让随机设备信息更随机 -- 其他一些内部优化 (无公开 API 变更) - -## `1.0-RC` 2020/5/6 - -### 事件优先级与拦截 -> 特别感谢 @Karlatemp (#279) - -- 支持事件拦截: `Event.intercept()`, `Event.isIntercepted` - -- 支持事件优先级: `HIGHEST, HIGH, NORMAL, LOW, LOWEST` 和 `MONITOR` -事件广播时按监听器的优先级从高到低依次调用, 在任意一个监听器 拦截事件(`Event.intercept()`) 后停止广播, 不调用后续监听器. -最后调用 `MONITOR` 级别的监听器. - -- 在 `subscribe`, `subscribeAlways`, `nextMessage`, `syncFromEvent`, `subscribeMessages` 等所有事件监听函数中添加 `priority` 参数, 默认使用 `NORMAL` 优先级. -兼容 `1.0` 以前的 API 到 `1.2.0`, 旧版本 API 使用 `MONITOR` 级别. - -### 图片缓存策略 `FileCacheStrategy` -- 新增 `FileCacheStrategy`, 可管理上传图片等操作时的缓存行为. -- 内置内存缓存 (`FileCacheStrategy.MemoryCache`) 与默认使用的临时文件 (`FileCacheStrategy.TempCache`) 缓存, 可选临时文件存放目录 -- 新增 `BotConfiguration.fileCacheStrategy`, 为单个 `Bot` 指定缓存策略 -- 在图片上传 (无论是否成功) 删除临时文件 -- 图片上传失败时支持自动重试 -- 修复部分情况下文件没有关闭的问题 (#302) -- 因新架构为懒惰处理, 弃用所有 `*.suspendToExternalImage` - -### 修正 `ContactMessage` 命名歧义 -(#299) - -- 原有 `ContactMessage` 实际上是一个事件, 而其命名与消息 `Message` 易产生迷惑. - 弃用 (兼容到 `1.2.0`): - - `MessagePacket` - - `MessagePacketBase` - - 进行如下更名: - - `ContactMessage` -> `MessageEvent` - - `FriendMessage` -> `FriendMessageEvent` - - `GroupMessage` -> `GroupMessageEvent` - - `TempMessage` -> `TempMessageEvent` - - 暂未决定是否提供 `UserMessageEvent` 作为 `TempMessageEvent` 和 `FriendMessageEvent` 的公共父类. - -- 优化扩展函数结构, 统一放置在 `MessageEventExtensions`, 以使 `MessageEvent` 结构清晰. - -### 支持平板登录方式 -- 可选, 且默认作为平板身份登录, 与手机电脑不冲突. -- 可通过 `BotConfiguration.protocol` 切换协议. - -### 其他 - -- **`MessageChain.get` 现在返回可空的 `Message`**. 可迁移到 `MessageChain.getOrFail`. -- 添加 `nextEvent`: 挂起当前协程, 直到监听到事件 `[E]` 的广播, 返回这个事件实例. -- 删除部分冗长的如 `nextMessageContainingOrNullAsync` 等函数. -- 添加 `Message.content` 扩展属性作为 `Message.contentToString()` 的捷径 -- 简化图片结构, 弃用 `OnlineFriendImage`, `OnlineGroupImage`, `OfflineGroupImage`, `OfflineFriendImage` 这四个类. -- 修复关闭验证码窗口后阻塞协程的问题 (#296) -- 删除全部 `0.x.x` 版本更新时做的兼容 -- 删除全部 `@SinceMirai("0.x.0")` -- 支持接收群语音消息 -- 优化图片 ID 正则表达式 -- 优化大量 KDoc -- 优化上传图片和长消息时的日志内容 -- 允许引用回复离线的消息源 (在 `MessageChain.quote` 时消息链中的 `MessageSource` 可以为 `OfflineMessageSource`) -- 拆分 JCE 序列化到独立的库 (#300) -- 在重连时增加计时 -- 简化 `MemberPermission` 比较 -- 在消息事件中使用强引用 (#303) -- 修复邀请机器人进群事件无法解析的问题 (#301) - -# Version 0.x - -开发版本. 频繁更新, 不保证高稳定性 - -## `0.40.0` 2020/4/29 -在 `1.0.0` 正式版发布时, 所有为旧版本做的兼容都将删除, 因此请尽快迁移. - -- `Message` 不再继承 `CharSequence` (兼容到 `1.0.0`) -- 废弃 `XmlMessage` 和 `JsonMessage`. 需使用 `ServiceMessage` 并手动指定 `serviceId` -- 修复登录时概率失败的问题 -- 提高事件处理稳定性 -- Java 事件默认 `LOCKED`, 而不是 `CONCURRENT` -- 弃用 `PlainText.stringValue`, 以 `PlainText.content` 替代 -- 将 `VipFace` 作为 `PlainText` 发送, 而不是抛出异常 -- 修复 `BufferedImage.toExternalImage` 降低图片质量的问题 - -## `0.39.5` 2020/4/28 -- 优化登录初始化, 提高稳定性 (#282) -- 支持 VIP 表情的解析: `VipFace` (不支持发送) -- 支持更多的戳一戳消息 (`PokeMessage`) 类型 -- 修复 Android 平台的正则语法错误问题 -- 修复 `BotInvitedJoinRequestEvent.ignore` -- 提升 `LockFreeLinkedList` 遍历性能, 即 `ContactList` 遍历性能 -- 将 `LockFreeLinkedList` 标注 `@MiraiInternalAPI` 并计划于 1.0.0 修改为 `internal` - -## `0.39.4` 2020/4/27 -- 支持匿名消息解析 (#277) -- 修复部分情况下撤回失败的问题 -- 修复部分情况下解析群名片错误的问题 -- 修复解析匿名群成员错误的问题 -- 修复 `LoginSolver` `Swing` 选择问题 -- 添加 `NoStandardInputForCaptchaException`, 在无可用标准输入时中断登录 - -## `0.39.3` 2020/4/25 -- 添加 `Message.isContentEmpty()` 和 `Message.isContentNotEmpty()` -- 在发送消息前检查是否为空 (#268) -- 修复重复收到一些事件的问题 (#259) -- 支持所有图片的下载链接获取 (#250) -- 修复部分情况下验证码窗口无法显示的问题 (#270) -- 构造 `ForwardMessage` 时不允许 `ForwardMessage.nodeList` 为空. - -## `0.39.2` 2020/4/24 -- 完善 `Message` 相关的 KDoc -- 在支持图像界面的环境下弹出验证码输入 (#257) -- 修复无法通过 id 发送图片的问题 (#262) -- 修复彩色群名解析不全的问题 (#263) - -## `0.39.1` 2020/4/24 -- 修复长消息发送失败的问题 (#256) -- 撤销 `Bot.instances` 更改, 添加新的 `Bot.botInstances` 以兼容以前代码 -- 修复密码错误时未停止重连的问题 -- 修复 `ForwardMessage` 无法从 `firstOrNull` 获取的问题 - -## `0.39.0` 2020/4/23 -**二进制不兼容的修改:** `Bot.instances` 现在返回 `List`, 而不是 `List>` 由于他们在 JVM 签名相同, 无法做兼容. - -### Contact 架构改变 -原有 `Member` 继承 `QQ`, `QQ` 继承 `Contact` 架构改变. - - -新架构为: -- 弃用 `QQ` 命名 (二进制兼容到 1.0.0) -- 新增 `User` 继承 `Contact`, 作为 `Member` 和 `Friend` 的父类 -- `Member` 继承 `User` -- `Friend` 继承 `User` - -#### 迁移 -由于 `Member` 不再是 `QQ` 子类, 而原本表示 '好友' 意义的 `QQ` 删除, -需要根据实际情况替换 `QQ` 的引用为 `Friend` 或 `Group` - - -因修改, 新增以下 API: -- `fun Member.asFriend(): Friend`: 得到此成员作为好友的对象或抛出异常 -- `fun Member.asFriendOrNull(): Friend`: 得到此成员作为好友的对象或返回 `null` -- `inline val Member.isFriend: Boolean`: 判断此成员是否为好友 - - -同时有以下修改: -- `val User.nameCardOrNick`: 获取非空群名片 (如果是群员) 或昵称 -- 弃用 `fun Member.isMuted()` 而改为属性 `val Member.isMuted` - -### 图片 -- 构造所有类型图片时只接受唯一一个参数 `imageId: String`. -- 所有类型图片只能获取唯一一个属性 `imageId: String` (以前可以获取长宽等数据) -- 提高发送图片的性能 -- 优化 `BufferedImage.toExternalImage` 的性能 -- 统一图片后缀: `{ ... }.mirai` - -### 消息 -- 新增合并转发及其 DSL -- 新增 `OfflineMessageSource` 构造 -- 新增 `MessageSource` 修改: `MessageSource.copyAmend(block)` -- 修复 'sequence not yet available' 问题 (#) -- 修复好友消息的消息源 id 错误的问题 (#247) -- 如果群成员是好友, 则发送好友消息, 而不是临时会话消息. -- 添加 `MessageSource.internalId` 以便将来使用 -- 添加 `OnlineMessageSource.toOffline` - -- 添加 `ContactMessage.time` -- 添加 `ContactMessage.senderName` - -#### `OfflineMessageSource` 构造 -可使用 DSL 构造离线消息, 修改其发送人, 发送时间, 发送内容等. 这对于跨群转发等情况十分有用. -[MessageSourceBuilder.kt: Line 90](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSourceBuilder.kt#L90) -DSL 总览: -``` -val source: OfflineMessageSource = bot.buildMessageSource { - bot sendTo target // 指定发送人和发送目标 - metadata(source) // 从另一个消息源复制 id, internalId, time - - messages { // 指定消息内容 - +"hi" - } -} -``` - -#### 合并转发及其 DSL -合并转发: [ForwardMessage](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/ForwardMessage.kt#L80) -DSL: [ForwardMessageBuilder](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/ForwardMessage.kt#L315) - -DSL 总览: -```kotlin -buildForwardMessage { - 123456789 named "鸽子 A" says "咕" - 100200300 named "鸽子 C" at 1582315452 says "咕咕咕" // at 设置时间 - 987654321 named "鸽子 B" says "咕" - myFriend says "咕" - bot says { // 构造消息链, 同 `buildMessageChain` - +"发个图片试试" - +Image("{90CCED1C-2D64-313B-5D66-46625CAB31D7}.jpg") - } -} -``` -不支持解析别人的转发. - -### 其他 -- 支持 bot 名片被其他人修改时的同步 -- 修复登录时遇到服务器不可用时无法继续重连的问题 -- 更名 `Identified` 到 `ContactOrBot`, 去掉其 '实验性' 注解 -- `Bot.instances` 现在返回 `List`, 而不是 `List>` (二进制兼容) -- 更名 `subscribingGet` 到 `syncFromEvent`, 并将其定义为稳定 API. -- 更名 `subscribingGetAsync` 到 `asyncFromEvent`, 并将其定义为稳定 API. -- 添加接受 `eventClass: KClass` 参数的事件监听 `subscribe` -- 在 `MessageSubscribersBuilder` 添加 `sentBy(User)`, `sentFrom(Group)`, `atAll`, `at` DSL -- 修复某些时候未处理 `BotOfflineEvent.Force` 的问题 - -## `0.38.0` 2020/4/20 -- 新增自定义消息 (实验性): `CustomMessage` -- 新增 `MessageChain.contentEquals` -- 新增 `Message.isPlain`, `Message.isNotPlain` -- 新增 `MessageChain.allContent`, `MessageChain.noneContent` -- 修复 `CombinedMessage.toString` 顺序错误, 添加缓存 -- 新增 `BotConfiguration.inheritCoroutineContext` -- 将 Java API `MessageChain.getOrNull` 更名为 `MessageChain.firstOrNull` -- 将 Java API `MessageChain.get` 更名为 `MessageChain.first` -- 将 Java API `MessageReceipt.recall(long)` 更名为 `MessageReceipt.recallIn(long)` 以与其他 API 保持一致 -- 优化 `MessageChainBuilder` 构建逻辑 - -## `0.37.5` 2020/4/20 -- 上传长消息和图片时允许重试, 提高稳定性 -- 优化无网络时的重连逻辑 -- 在 `Message` 中添加 `equals` 和 `hashCode`, 将部分类型消息定义为 `data class` -- `MessageSource.id` 现在返回非 0 序列号 -- 实现已撤回判断, 同一个 `MessageSource` 只能撤回一次 - -## `0.37.4` 2020/4/17 -- 修复 #220: 无法正常解析邀请机器人进群的富文本消息 -- 修复 #236: 删除无用的 getter 方法生成 -- 修复上传长消息时报错错误的问题 - -## `0.37.3` 2020/4/15 -新增: - -- 在群名修改事件(`GroupNameChangeEvent`)中支持获取操作人 -- 修复 #229, 引入 `ServiceMessage` 作为 `JsonMessage`, `XmlMessage` 的父类并处理所有类型富文本消息解析 -- 将所有 `RichMessage` 标注 `MiraiExperimentalAPI` 以警告将来改动 - -问题修复: - -- 修复潜在的长消息上传失败问题 -- 简化 `MessageSubscriberBuilder` DSL, 整理 `linear.kt`, `subscribers.kt` -- 修复启动时概率解析失败 ConfigPushSvc.PushReq -- 修复 #228: 登录时没有因 `LoginFailedException` 中断 -- 重构登录重连控制, 确保单一进程 -- 处理无网络连接问题, 在无网络时将不尝试登录而等待网络连接 -- 修复 #227: Android 最新版无法编译 -- 修复 #226: BotUnmuteEvent -- 修复 #225: 重复接收到群消息撤回问题 -- 修复 #220: 无法正常解析邀请机器人进群的富文本消息 -- 修复 #217: 解析 OnlinePush confess 状态时没有覆盖全面 -- 优化遇到未知消息时的日志 - -## `0.37.2` 2020/4/13 -- 修复 `OnlineMessageSource.Incoming.target` 类型错误 -- 引入实验性 `Identified` 接口作为 `Contact` 和 `Bot` 的公共接口 -- 加快图片 MD5 计算过程 -- 加快图片上传过程 -- 其他小优化 - -## `0.37.1` 2020/4/12 -**从 `0.37.1` 起 JVM 平台依赖无需带 "-jvm" 模块名** -**即原 "mirai-core-jvm" 和 "mirai-core-qqandroid-jvm" 均需去掉 "-jvm", 变为 "mirai-core" 和 "mirai-core-qqandroid"** - -- 登录时尝试多个服务器, 随服务器需求切换服务器 (解决潜在的无法登录的问题) (#52) -- 优化带有 `QuoteReply` 时的消息长度估算 -- 添加 `MessageChainBuilder.build`, 效果同 `asMessageChain` -- 在 `ContactMessage` 中添加 `At.isBot` -- 在 `MessageSubscribersBuilder` 中添加 `String.invoke`, `atBot` DSL - -## `0.37.0` 2020/4/11 -- 支持主动退群: `Group.quit`, `BotLeaveEvent.Active` -- 支持临时消息撤回 -- 支持好友消息撤回 -- 修复一个内存泄露问题 -- 修复彩色群名片读取失败的问题 -- 修复退群事件重复广播的问题 (#221) - -## `0.36.1` 2020/4/10 -- 修复 `botPermission` -- 删除一些无用的调试输出 - -## `0.36.0` 2020/4/10 -- 支持临时会话: `TempMessage` (#16) -- 支持群员主动加入事件 `MemberJoinEvent.Active` -- 添加 `subscribeTempMessages` 等相关 DSL -- 添加 `FriendAddEvent`, `FriendDeleteEvent` (#216) -- 修复各种事件重复广播的问题 (#173, #212) -- 修复 `OfflineMessageSource.id` -- 修复 `Member.kick` -- 修复彩色群名片读取, 支持群名片更改事件 (#210) -- 增加超时 (#175) -- 支持合并转发消息的解析, 修复部分情况下长消息解析失败的问题 -- 修复新成员加入时没有添加进成员列表的问题 (#172) - -## `0.35.0` 2020/4/8 -- 新增处理加好友请求: `NewFriendRequestEvent` -- 新增处理加群请求: `MemberJoinRequestEvent` -- 现在 `MessageSource.originalMessage` 也可以获取到 `MessageSource` -- 支持机器人加入了大量群时的群列表获取 -- 优化 init 过程 -- 添加更清晰的错误日志 -- 修复撤回自己发送的消息时的权限判定 -- 修复 `botAsMember.nameCard` 修改时需要管理员权限的问题 -- 修复 `MessageSource.key` -- 修复其他一些小问题 - -## `0.34.0` 2020/4/6 - -- 修复长消息判定. -- 为 `selectMessages`, `selectMessagesUnit` 添加可选筛选 context 的参数: `filterContext: Boolean` -- 统一消息日志 -- 加快重连速度 - -`Message` 改动 (二进制兼容): - -- 添加 `Message.contentToString` 以转换为最接近官方消息的字符串 -- 添加 `ConstrainSingle` 的 `Message` 类型以保证一个消息链中只存在一个 `QuoteReply` 和一个 `MessageSource` -- `CombinedMessage` 现在实现接口 `MessageChian` 并变为 `internal` 以降低复杂度 (使用 `MessageChain` 替换 `CombinedMessage` 的引用). -- `Message.plus` 现在返回 `MessageChain` 而不是 `CombinedMessage` -- 弃用 `NullMessageChain` (使用 `null` 替代) -- `Message` 中 `eq`, `contains` 等函数移动至 `SingleMessage` 以避免歧义. -- 更名 `MessageChain.any` 到 `MessageChain.anyInInstance` 以与标准库的 `Iterable.any` 区分 -- 更名 `MessageChain.first` 到 `MessageChain.firstIsInstance` 以与标准库的 `Iterable.first` 区分 -- 更名 `MessageChain.firstOrNull` 到 `MessageChain.firstIsInstanceOrNull` 以与标准库的 `Iterable.firstOrNull` 区分 - -## `0.33.0` 2020/4/4 -- 重构 [`MessageSource`](https://github.com/mamoe/mirai/blob/master/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt), 支持直接获取相关对象, 支持所有类型的引用. -- 简化引用回复, 现在只需要 `source.quote()` 即可创建引用 (而不需要 `sender` 参数) -- 现在可通过 `QuoteReply.source` 获取源消息, 且可以撤回该消息或再次引用. -- 支持闪照: 可通过 `Image.flash()` 将普通图片转为闪照. -- 支持 `Bot.nick` (#93) -- 修复消息长度判断 (#195) (实验性) -- 修复 Android 目标上 `SystemDeviceInfo.imei` 可能会抛出 NPE 的问题 -- 修复 `GroupNameChangeEvent` 重复广播的问题 -- 修复 `ContactMessage.nextMessageContaining` -- 修复 `selectMessage` 时无法正常完结, 和 timeout 没有被取消的问题 -- 修复 #133, #197, #187, #180, #77, #192 - -## `0.32.0` 2020/4/2 -- 使用 Kotlin 1.3.71, 兼容原使用 Kotlin 1.4-M1 编译的代码. -- 优化 `BotConfiguration`, 去掉 DSL 操作, 使用 `fileBasedDeviceInfo(filename)` 等函数替代. (兼容原操作方式, 计划于 `0.34.0` 删除) -- 调整长消息判定权重, 具体为: Chinese char=4, English char=1, Quote=700, Image=800, 其他消息类型转换为字符串后判断长度. -- 添加 `ContactMessage` 以替代 `MessagePacket<*, *>` 的情况 -- 添加 `MessageTooLargeException` -- 使用 `Bot.id` 替代 `Bot.uin` -- 在 `Dispatchers.IO` 协程调度器中执行 Java API 创建的事件处理. -- 修复 Java API `Member.kick` 参数 `message` 没有正常传递的问题 -- 将部分意外定义为 public 的 API 改为 internal. -- 将部分 internal API 从 `mirai-core` 移至 `mirai-core-qqandroid` - -## `0.31.4` 2020/3/31 -- 修复 At 在手机上显示错误的问题 - -## `0.31.3` 2020/3/31 -- 修复 #178 - -## `0.31.2` 2020/3/30 -- 修复长文本长度检测, 提高判断性能 -- 修复特殊的好友图片 ID 无法构造为消息 -- 新增 `AtAll.display` -- 所有消息元素统一 `toString`: `[mirai:image:ID]`, `[mirai:face:ID]`, `[mirai:at:TARGET]`, `[mirai:poke:TYPE,ID]`, `[mirai:quote:ID]` 等 (仍为实验性) - -## `0.31.1` 2020/3/29 -- 修复重复解析禁言事件的问题 (#83) - -## `0.31.0` 2020/3/29 -- 支持长消息发送, 单条消息最多含 4500 字符和 50 张图片 -- 支持戳一戳消息: `PokeMessage` -- 修复 重复收到好友消息 (#129), 私聊图片出错 (#165) -- 为 `MessageChain.toString` 增加缓存 (非原子), 以提升长消息处理的性能 -- 发消息失败时将抛出带提示的异常 -- 添加 `MessageSource` 将被重写的警告 - -## `0.30.1` 2020/3/26 -- 修复一些事件解析失败的问题 - -## `0.30.0` 2020/3/24 -此版本为二进制不兼容更新, 全部使用者都需要重新编译. - -源码兼容的改变: -- 删除全部 `@Depreacted` 兼容 -- 删除全部多余的 `@JvmName` 以兼容将来的改变 (新 MPP 模块等级制架构) -- 调整部分函数的 JVM 可见性 -- 内联部分 `MessageChain` 工具函数 -- **更新到 Kotlin 1.4-M1** ([如何更新到 Kotlin 1.4-M1](https://gist.github.com/Him188/9f395d058b89226d412a200bdd0595be)) - -源码不兼容的改变: -- 群设置由 `Group` 移动到独立的 `GroupSettings` -- 调整 API 可见性: 将除 `BotFactory` 外 `mirai-core-qqandroid` 中全部 API 改为 `internal` - -消息部分: -- `SingleMessage` 实现接口 `CharSequence` 和 `Comparable` -- 为 `FriendImage`, `GroupImage`, `OnlineImage`, `OfflineImage` 增加 `companion object Key` -- 调整 `RichMessage`, 将所有子类聚合到一个文件 -- 移动 `XmlMessageHelper` 为 `RichMessage.Compation` -- 命名调整: `buildXMLMessage` 改为 `buildXmlMessage` -- 修复 `CombinedMessage` 中错误的 `left` 和 `element` - -事件部分: -- 加强 `selectMessages`, 增加回复, 引用回复, 默认值, 超时支持: -原处理方式: -```kotlin -val message = nextMessageOrNull(10.secondsToMillis) ?: kotlin.run { - quoteReply("请在 10 秒内发送一张图片") - return@case -} -val image = message.getOrNull(OnlineImage) ?: kotlin.run { - reply(message.quote() + "请发送一张图片") - return@case -} -reply(message.quote() + image.originUrl) -``` -使用 `selectMessages` DSL: -```kotlin -selectMessagesUnit { - has() quoteReply { - message[OnlineImage].originUrl - } - timeout(10.secondsToMillis) quoteReply { - "请在 10 秒内发送图片以获取链接" - } - defaultQuoteReply { - "请发送一张图片" - } -} -``` - -## `0.29.1` 2020/3/22 -- 确保二进制兼容, #155 -- 修复 Android 上 ECDH init 失败问题, #154 - -## `0.29.0` 2020/3/22 -- 引入新消息监听 DSL: `whileSelectMessages`, 简化连续监听过程 -```kotlin -bot.subscribeMessages { - "开启复读模式" `->` { - reply("成功开启") - whileSelectMessages { - "stop" `->` { - reply("已关闭复读") - false // 停止循环 - } - default { - reply(message) - true // 继续循环 - } - } - reply("复读模式结束") - } -} -``` -- 引入新消息监听 DSL: `selectMessages`, 简化筛选监听过程 -```kotlin -bot.subscribeMessages { - "test" `->` { - reply("choose option: 'hello', 'hi'") - val value: String = selectMessages { - "hello" `->` { "123" } - "hi" `->` { "222" } - default { "default value" } - } - reply(value) - } -} -``` - -- 监听消息的 DSL 新增 `infix fun String.->(block)` -- 处理 `StatSvc.ReqMSFOffline` (#150) -- `Contact.sendMessage` 现在接受 `Message` 参数, 而不是 `MessageChain` 以兼容 `CombinedMessage` -- `Member.sendMessage` 现在返回 `MessageReceipt` 而不是 QQ 泛型 -- 调整 JVM `MessageUtils` 中一些方法的可见性 (`@JvmSynthetic`) -- 调整命名: `OfflineImage.queryOriginUrl` 改为 `OfflineImage.queryUrl` -- 允许手动重新初始化 `Bot` (`BotNetworkHandler.init`), 确保重初始化资源释放 - -## `0.28.0` 2020/3/19 -- 修复 Jce 反序列化在部分情况下出错的问题, 修复 #145 -- 新增群公告低级 API -- 新增群活跃数据低级 API -- 修复 #141, #143, #131 -- 更多原生表情 (`Face`) - -## `0.27.0` 2020/3/8 -- 支持 `XML`, `Json`, `LightApp` 等 `RichMessage` - -## `0.26.2` 2020/3/8 -- 新增 `MessageChain.repeat` 与 `MessageChain.times` -- JVM 平台下 `PlatformLogger` 可重定向输出 -- 修复 `NullMessageChain.equals` 判断不正确的问题 -- 新增 `PlainText.of` 以应对一些特殊情况 - -## `0.26.1` 2020/3/8 -- 重写 Jce 序列化, 提升反序列性能 -- 更新 `Kotlin` 版本到 1.3.70 -- 更新 `kotlinx.coroutines`, `atomicfu`, `kotlinx.coroutines` 依赖版本 - -## `0.26.0` 2020/3/7 -- 使用 `kotlinx.io` 而不是 `ktor.io` -- 修复 #111, #108, #116, #112 - -## `0.25.0` 2020/3/6 -- 适配 8.2.7 版本(2020 年 3 月)协议 -- 全面的 `Image` 类型: Online/Offline Image, Friend/Group Image -- 修复查询图片链接时好友图片链接错误的问题 -- 修复 bugs: #105, #106, #107 - -## `0.24.1` 2020/3/3 -- 修复 `Member` 的委托 `QQ` 弱引用被释放的问题 -- 用 `Bot.friends` 替代 `Bot.qqs` -- 用 `Bot.containsFriend`, `Bot.containsGroup` 替代 `Bot.contains` -- 新增 `BotFactory.Bot(String, ByteArray)` 用 md5 密码登录 -- 为 `BotFactory` 等类型的一些扩展指定 `JvmName` -- 移动 `Bot.QQ` 到低级 API - -## `0.24.0` 2020/3/1 -- Java 完全友好: Java 使用者可以同 Kotlin 方式直接阻塞式或异步(Future)调用 API -- 新增 `MessageSource.originalMessage: MessageChain` 以获取源消息内容 -- 群消息的撤回现在已稳定 (`Bot.recall`) -- 现在可以引用回复机器人自己发送的消息: `MessageReceipt.quoteReply` -- 新增 `MessageRecallEvent` - -- 整理 `MessageChain` 的构造, 优化性能 -- 整理所有网络层代码, 弃用 `kotlinx.io` 而使用 `io.ktor.utils.io` -- 其他杂项优化 - -## `0.23.0` 2020/2/28 -- 修复上传图片 -- 一些问题修复 -- 大量杂项优化 - -- `MessageReceipt.source` 现在为 public. 可获取源消息 id -- 修复上传好友图片失败的问题 -- 上传群图片现在分包缓存, 优化性能 - -## `0.22.0` 2020/2/24 -- 重构 `MessageChain`, 引入 `CombinedMessage`. (兼容大部分原 API) -- 新增 `MessageChainBuilder`, `buildMessageChain` -- `ExternalImage` 现在接收多种输入参数 - -- 修复访问好友消息回执 `.sequenceId` 时抛出异常的问题 - -## `0.21.0` 2020/2/23 -- 支持好友消息的引用回复 -- 更加结构化的 `QuoteReply` 架构, 支持引用任意群/好友消息回复给任意群/好友. - -## `0.20.0` 2020/2/23 - -- 支持图片下载: `image.channel(): ByteReadChannel`, `image.url()` - -- 添加 `LockFreeLinkedList.iterator` -- 添加 `LockFreeLinkedList.forEachNode` - -- 并行处理事件监听 -- 添加 `nextMessageContaining` 和相关可空版本 - -- '撤回' 从 `Contact` 移动到 `Bot` -- 删除 `MessageSource.sourceMessage` -- 让 MessageSource 拥有唯一的 long 类型 id, 删除原 `uid` 和 `sequence` 结构. -- 修复 `Message.eq` 歧义 - -## `0.19.1` 2020/2/21 - -- 支持机器人撤回群消息 (含自己发送的消息): `Group.recall`, `MessageReceipt.recall` -- 支持一定时间后自动撤回: `Group.recallIn`, `MessageReceipt.recallIn` -- `sendMessage` 返回 `MessageReceipt` 以实现撤回功能 -- 添加 `MessageChain.addOrRemove` -- 添加 `ContactList.firstOrNull`, `ContactList.first` -- 新的异步事件监听方式: `subscribingGetAsync` 启动一个协程并从一个事件从获取返回值到 `Deferred`. -- 新的线性事件监听方式: `subscribingGet` 挂起当前协程并从一个事件从获取返回值. - -##### 新的线性消息连续处理: `nextMessage` 挂起当前协程并等待下一条消息: -使用该示例, 发送两条消息, 一条为 "禁言", 另一条包含一个 At -```kotlin -case("禁言") { - val value: At = nextMessage { message.any(At) }[At] - value.member().mute(10) -} -``` -示例 2: -```kotlin -case("复读下一条") { - reply(nextMessage().message) -} -``` - - -- 修复一些情况下 `At` 无法发送的问题 -- 统一 ImageId: 群消息收到的 ImageId 均为 `{xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx}.jpg` 形式(固定长度 37) -- 支持成员主动离开事件的解析 (#51) - -## `0.18.0` 2020/2/20 - - -- 添加 `MessageSource.time` -- 添加事件监听时额外的 `coroutineContext` -- 为一些带有 `operator` 的事件添加 `.isByBot` 的属性扩展 -- 优化事件广播逻辑, 修复可能无法触发监听的问题 -- 为所有 `Contact` 添加 `toString()` (#80) - - -- 支持成员禁言状态和时间查询 `Member.muteTimeRemaining` -- 修复 `At` 的 `display` (#73), 同时修复 `QuoteReply` 无法显示问题 (#54). -- 广播 `BotReloginEvent` (#78) -- 支持机器人自身禁言时间的更新和查询 (#82) - -## `0.17.0` 2020/2/20 - - -- 支持原生表情 `Face` -- 修正 `groupCardOrNick` 为 `nameCardOrNick` -- 增加 `MessageChain.foreachContent(lambda)` 和 `Message.hasContent(): Boolean` - - -- 提高重连速度 -- 修复重连后某些情况不会心跳 -- 修复收包时可能产生异常 - -## `0.16.0` 2020/2/19 - - -- 添加 `Bot.subscribe` 等筛选 Bot 实例的监听方法 -- 其他一些小问题修复 - - -- 优化重连处理逻辑 -- 确保好友消息和历史事件在初始化结束前同步完成 -- 同步好友消息记录时不广播 - -## `0.15.5` 2020/2/19 - - -- 为 `MiraiLogger` 添加 common property `val isEnabled: Boolean` -- 修复 #62: 掉线重连后无 heartbeat -- 修复 #65: `Bot` close 后仍会重连 -- 修复 #70: ECDH is not available on Android platform - - -- 从服务器收到的事件将会额外使用 `bot.logger` 记录 (verbose). -- 降低包记录的等级: `info` -> `verbose` -- 改善 `Bot` 的 log 记录 -- 加载好友列表失败时会重试 -- 改善 `Bot` 或 `NetworkHandler` 关闭时取消 job 的逻辑 -- 修复初始化(init)时同步历史好友消息时出错的问题 - -## `0.15.4` 2020/2/18 - -- 放弃使用 `atomicfu` 以解决其编译错误的问题. (#60) - -## `0.15.3` 2020/2/18 - -- 修复无法引入依赖的问题. - -## `0.15.2` 2020/2/18 - - -- 尝试修复 `atomicfu` 编译错误的问题 - - -- 查询群信息失败后重试 - -## `0.15.1` 2020/2/15 - - -- 统一异常处理: 所有群成员相关操作无权限时均抛出异常而不返回 `false`. - - -- 初始化未完成时缓存接收的所有事件包 (#46) -- 解析群踢人事件时忽略找不到的群成员 -- 登录完成后广播事件 `BotOnlineEvent` - -## `0.15.0` 2020/2/14 - - - -- 新增事件: `BotReloginEvent` 和 `BotOfflineEvent.Dropped` -- `AtAll` 现在实现 `Message.Key` -- 新增 `BotConfiguration` DSL, 支持自动将设备信息存储在文件系统等 -- 新增 `MessageSource.quote(Member)` - -- 更好的网络层连接逻辑 -- 密码错误后不再重试登录 -- 掉线后尝试快速重连, 失败则普通重连 (#47) -- 有原因的登录失败时将抛出特定异常: `LoginFailedException` -- 默认心跳时间调整为 60s - - - -- 解决一些验证码无法识别的问题 -- 忽略一些不需要处理的事件(机器人主动操作触发的事件) - -## `0.14.0` 2020/2/13 - - - -- **支持 at 全体成员: `AtAll`** - - - -- **支持 `AtAll` 的发送和解析** -- **修复某些情况下禁言处理异常** - -小优化: -- 在 `GroupMessage` 添加 `quoteReply(Message)`, 可快速引用消息并回复 -- 为 `CoroutineScope.subscribeMessages` 添加返回值. 返回 lambda 的返回值 -- 在验证码无法处理时记录更多信息 -- 优化 `At` 的空格处理 (自动为 `At` 之后的消息添加空格) -- 删除 `BotConfiguration` 中一些过时的设置 - -## `0.13.0` 2020/2/12 - - -- 修改 BotFactory, 添加 `context` 参数. -- currentTimeMillis 减少不必要对象创建 -- 优化无锁链表性能 (大幅提升 `addAll` 性能) - --qqanroid -安卓协议发布, 基于最新 QQ, 版本 `8.2.0` -支持的功能: -- 登录: 密码登录. 设备锁支持, 不安全状态支持, 图片验证码支持, 滑动验证码支持. -- 消息: 文字消息, 图片消息(含表情消息), 群员 At, 引用回复. -- 列表: 群列表, 群员列表, 好友列表均已稳定. -- 群操作: 查看和修改群名, 查看和修改群属性(含全体禁言, 坦白说, 自动批准加入, 匿名聊天, 允许成员拉人), 设置和解除成员禁言, 查看和修改成员名片, 踢出成员. -- 消息事件: 接受群消息和好友消息并解析 -- 群事件: 群员加入, 群员离开, 禁言和解除禁言, 群属性(含全体禁言, 坦白说, 匿名聊天, 允许成员拉人)改动. - -### mirai-api-http -HTTP API 已完成, by [@ryoii](https://github.com/ryoii). -详见 [README](https://github.com/mamoe/mirai/tree/master/mirai-api-http) - -Mirai 仍处于快速迭代状态. 将来仍可能会有 API 改动. -## `0.12.0` *2020/1/19* - -1. 监听消息时允许使用条件式的表达式, 如: -```kotlin -(contains("1") and has()){ - reply("Your message has a string '1' and an image contained") -} - -(contains("1") or endsWith("2")){ - -} -``` -原有单一条件语法不变: -```kotlin -contains("1"){ - -} - -"Hello" reply "World" -``` - -2. Message: 修复 `eq` 无法正确判断的问题; 性能优化. -3. 简化 logger 结构(API 不变). -4. 事件 `cancelled` 属性修改为 `val` (以前是 `var` with `private set`) - -## `0.11.0` *2020/1/12* - -- 弃用 `BotAccount.id`. 将来它可能会被改名成为邮箱等账号. QQ 号码需通过 `bot.uin` 获取. -- `Gender` 由 `inline class` 改为 enum -- `String.chain()` 改为 `String.toChain()` -- `List.chain()` 改为 `List.toChain()` --timpc -- 修复在有入群验证时无法解析群资料的问题 (#30) - -## `0.10.6` *2020/1/8* -TIMPC -- Fix #27, 群成员找不到的问题 -- 一些小优化 - -## `0.10.5` *2020/1/3* -- 修复有时表情消息无法解析的问题 -- 为心跳增加重试, 降低掉线概率 -- 消息中的换行输出为 \n -- 其他一些小问题修复 - -## `0.10.4` *2020/1/1* -- 事件处理抛出异常时不停止监听 -- 添加 `Bot(qq, password, config=Default)` -- 一些性能优化 - -## `0.10.3` *2020/1/1* -- 修复一个由 atomicfu 的 bug 导致的 VerifyError -- 添加 `ExternalImageAndroid` -- 事件处理抛出异常时正确地停止监听 - -## `0.10.1` *2019/12/30* -**Bot 构造** -`Bot` 构造时修改 `BotConfiguration` 而不是登录时. -移除 `CoroutineScope.Bot` -移除 `suspend Bot(...)` -添加 `Bot(..., BotConfiguration.() -> Unit)` -添加 `Bot(..., BotConfiguration = BotConfiguration.Default)` - -**其他** -全面的在线状态 (`OnlineStatus`) -移动部分文件, 模块化 - -## `0.10.0` *2019/12/23* -**事件优化** -更快的监听过程 -现在监听不再是 `suspend`, 而必须显式指定 `CoroutineScope`. 详见 `Subscribers.kt` -删除原本的 bot.subscribe 等监听模式. - -**其他** -`Contact` 现在实现接口 `CoroutineScope` - -## `0.9.0` *2019/12/20* -**协议模块独立** -现在 `mirai-core` 只提供基础的抽象类. 具体的各协议实现为 `mirai-core-PROTOCOL`. -这些模块都继承自 `mirai-core`. -现在, 要使用 mirai, 必须依赖于特定的协议模块, 如 `mirai-core-timpc`. -查阅 API 时请查看 `mirai-core`. -每个模块只提供少量的额外方法. 我们会给出详细列表. - -在目前的开发中您无需考虑多协议兼容. - -**Bot 构造** -协议抽象后构造 Bot 需指定协议的 `BotFactory`. -在 JVM 平台, Mirai 通过 classname 自动加载协议模块的 `BotFactory`, 因此若您只使用一套协议, 则无需修改现行源码 - -**事件** -大部分事件包名修改. - -**UInt -> Long** -修改全部 QQ ID, Group ID 的类型由 UInt 为 Long. -**此为 API 不兼容更新**, 请将所有无符号标志 `u` 删除即可. 如 `123456u` 改为 `123456` - -另还有其他 API 的包名或签名修改. 请使用 IDE 自动修补 import 即可. -## `0.8.2` *2019/12/15* -- 修复 GroupId.toGroupInternalId 错误 -- 修复解析群消息时小概率出现的一个错误 - -## `0.8.1` *2019/12/15* -- 修复有时群资料无法获取的情况 -- 现在 `At.qq`, `Long.qq` 等函数不再是 `suspend` - -## `0.8.0` *2019/12/14* -协议 -- 现在查询群资料时可处理群号无效的情况 -- 现在能正常分辨禁言事件包 - -功能 -- 增加无锁链表: LockFreeLinkedList, 并将 ContactList 的实现改为该无锁链表 -- **ContactSystem.getQQ 不再是 `suspend`** -- ContactSystem.getGroup 仍是 `suspend`, 原因为需要查询群资料. 在群 ID 无效时抛出 `GroupNotFoundException` - -优化 -- 日志中, 发送给服务器的包将会被以名字记录, 而不是 id - -## `0.7.5` *2019/12/09* -- 修复验证码包发出后无回复 (错误的验证码包) - -## `0.7.4` *2019/12/08* -- 修复 bug -- 优化 JVM 平台上需要验证码时的提示 - -## `0.7.3` *2019/12/07* -- 删除 klock 依赖, 添加 Time.kt. 待将来 kotlin Duration 稳定后替换为 Duration - -## `0.7.2` *2019/12/07* -- 使所有协议相关类 `internal` -- 去掉一些 `close` 的不应该有的 `suspend` -- `QQ`, `Member`, `Group` 现在继承接口 `CoroutineScope` -- 将 `LoginResult` 由 `inline class` 修改为 `enum class` -- 添加和修改了 `BotAccount` 和 `Bot` 的构造器 - -## `0.7.1` *2019/12/05* -- 修复禁言时间范围错误的问题 -- 禁言的扩展函数现在会传递实际函数的返回值 - -## `0.7.0` *2019/12/04* -协议 -- 重新分析验证码包, 解决一些无法解析的情况. (这可能会产生新的问题, 遇到后请提交 issue) -- 重新分析提交密码包 -- *提交验证码仍可能出现问题 (已在 `0.7.5` 修复)* - -功能 -- XML 消息 DSL 构造支持 (实验性) (暂不支持发送) -- 群成员列表现在包含群主 (原本就应该包含) -- 在消息事件处理中添加获取 `.qq()` 和 `.group()` 的扩展函数. -- 现在处理群消息时 sender 为 Member (以前为 QQ) -- 修改 `Message.concat` 为 `Message.followedBy` -- 修改成员权限 `OPERATOR` 为 `ADMINISTRATOR` -- **bot.subscribeAll<>() 等函数的 handler lambda 的 receiver 由 Bot 改变为 BotSession**; 此变动不会造成现有代码的修改, 但并不兼容旧版本编译的代码 - -性能优化 -- 内联 ContactList -- 2 个 Contact.sendMessage 重载改为内联扩展函数 **(需要添加 import)** -- 其他小优化 - -## `0.6.1` *2019/12/03* -- 新增: 无法解析密码包/验证码包时的调试输出. 以兼容更多的设备情况 -- 新增: `MessagePacket` 下 `At.qq()` 捷径获取 QQ - -## `0.6.0` *2019/12/02* -- 新增: 禁言群成员 (`Member.mute(TimeSpan|Duration|MonthsSpan|Int|UInt)`) -- 新增: 解禁群成员 (`Member.unmute()`) -- 修复: ContactList key 无法匹配 (Kotlin 内联类型泛型投影错误) diff --git a/README-eng.md b/README-eng.md index 33d25fd5d..6a1915d90 100644 --- a/README-eng.md +++ b/README-eng.md @@ -10,8 +10,6 @@ Mirai is designed to handle all sorts of messaging works that can be automatical ## Start **Development document**: [docs/mirai.md](docs/mirai.md) -[CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md) - ### Use as a framework Mirai is able to run as a plugin-supported framework. diff --git a/README.md b/README.md index f27f9b003..4317c1f53 100644 --- a/README.md +++ b/README.md @@ -109,84 +109,27 @@ mirai 是一个在全平台下运行,提供 QQ Android 协议支持的高效 ## 开始 -### 在开始之前,建议你了解一下 Mirai 生态 -[Mirai 生态概览](docs/mirai-ecology.md) - -### 文档 - -**对于一般使用者, 更建议使用 [Mirai Console](https://github.com/mamoe/mirai-console)。拥有更完善的文档。** - -- 快速上手:[quickstart](docs/guide_quick_start.md) -- 开发文档:[docs/mirai.md](docs/mirai.md) -- 常见问题: [docs/FAQ.md](docs/FAQ.md) -- 更新日志: [CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md) 或 [release](https://github.com/mamoe/mirai/releases) +- 开发文档(新,编写中):[docs](docs/README.md) +- 开发文档(旧):[docs/mirai.md](docs/mirai.md) +- 更新日志: [release](https://github.com/mamoe/mirai/releases) - 开发计划: [milestones](https://github.com/mamoe/mirai/milestones) + - 贡献: [CONTRIBUTING](CONTRIBUTING.md) -### 使用者 - -- [mirai-console](https://github.com/mamoe/mirai-console) 支持插件的控制台服务端,支持PC和Android平台 **本模块正在开发中** - [awesome-mirai](https://github.com/project-mirai/awsome-mirai/blob/master/README.md) **mirai相关项目合集** - -#### 从其他平台迁移 - -- 酷Q的插件可以在 `mirai` 中加载,详见 [Mirai Native](https://github.com/iTXTech/mirai-native) -- 使用 `酷Q HTTP API` 的插件将可以在 `mirai` 中通过`CQHTTP Mirai`加载,详见 [cqhttp-mirai](https://github.com/yyuueexxiinngg/cqhttp-mirai) - -### 开发者 - -开发交流:[![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) - -#### 使用 mirai-console 服务端,为 mirai-console 开发插件 - -官方支持 SDK 列表: - -- `Java`,`Kotlin` 等 JVM 语言: 为 [mirai-console](https://github.com/mamoe/mirai-console) 直接编写插件并与其他插件开发者合作共享 -- `Kotlin Script`: [mirai-kts](https://github.com/iTXTech/mirai-kts) 支持使用 `kts` 编写插件,享受 `Kotlin` 带来的一切便利(**仅 OpenJDK 8 以上环境,不支持 Android**) -- `C`,`C++` 等原生语言: [mirai-native](https://github.com/iTXTech/mirai-native) 支持酷 Q 插件在 mirai 上运行 **(仅限 `Windows 32 位 JRE`/支持 `Wine`)** -- `JavaScript`: [mirai-js](https://github.com/iTXTech/mirai-js) 支持使用 `JavaScript` 编写插件并**直接**与 mirai 交互 -- *Http*:使用由 [mirai-api-http](https://github.com/mamoe/mirai-api-http) 提供的 http 接口进行接入 - -
- 社区支持的 SDK 列表 - -基于 `mirai-core` (独立使用): -- `Lua`: [lua-mirai](https://github.com/only52607/lua-mirai) 基于 mirai-core 的 Lua SDK,并提供了 Java 扩展支持,可在 Lua 中调用 Java 代码开发机器人 - - -基于 `mirai-http-api` (配合 [mirai-console](https://github.com/mamoe/mirai-console)): - -- `Python`: [Graia Framework](https://github.com/GraiaProject/Application) 基于 `mirai-api-http` 的机器人开发框架 -- `JavaScript`(`Node.js`): [node-mirai](https://github.com/RedBeanN/node-mirai) mirai 的 Node.js SDK -- `Go`: [gomirai](https://github.com/Logiase/gomirai) 基于 mirai-api-http 的 GoLang SDK -- `Mozilla Rhino`: [mirai-rhinojs-sdk](https://github.com/StageGuard/mirai-rhinojs-sdk) 为基于 Rhino(如 Auto.js 等安卓 app 或运行环境)的 JavaScript 提供简单易用的 SDK -- `C++`: [mirai-cpp](https://github.com/cyanray/mirai-cpp) mirai-http-api 的 C++ 封装,方便使用 C++ 开发 mirai-http-api 插件 -- `C++`: [miraipp](https://github.com/Chlorie/miraipp-template) mirai-http-api 的另一个 C++ 封装,使用现代 C++ 特性,并提供了较完善的说明文档 -- `C#`: [mirai-CSharp](https://github.com/Executor-Cheng/mirai-CSharp) 基于 mirai-api-http 的 C# SDK -- `Rust`: [mirai-rs](https://github.com/HoshinoTented/mirai-rs) mirai-http-api 的 Rust 封装 -- `TypeScript`: [mirai-ts](https://github.com/YunYouJun/mirai-ts) mirai-api-http 的 TypeScript SDK,附带声明文件,拥有良好的注释和类型提示,也可作为 JavaScript SDK 使用。 -- `易语言`: [e-mirai](https://github.com/only52607/e-mirai) mirai-api-http 的 易语言 SDK,使用全中文环境开发插件,适合编程新手使用。 -- `.Net/C#`: [Hyperai](https://github.com/theGravityLab/ProjHyperai) 从 mirai-api-http 对接到机器人开发框架再到开箱即用的插件式机器人程序一应俱全。 - -
- -#### 使用 mirai-core 为第三方依赖库引入项目 - -Demos: [mirai-demos](https://github.com/mamoe/mirai-demos) - -- `Kotlin` 简略版: [mirai Guide - Quick Start](/docs/guide_quick_start.md) -- `Kotlin` 新手版: [mirai Guide - Getting Started](/docs/guide_getting_started.md) -- `Java`: 查看上述 Demos +- 常见问题: [docs/FAQ.md](docs/FAQ.md) ## [贡献](CONTRIBUTING.md) 我们欢迎一切形式的贡献。 我们也期待有更多人能加入 mirai 的开发。 -若在使用过程中有任何疑问,可提交 `issue` 或是[邮件联系](mailto:support@mamoe.net). 我们希望 mirai 变得更易用. +若在使用过程中有任何疑问,可提交 [`issue`](https://github.com/mamoe/mirai/issues) 或在 [`Discussions`](https://github.com/mamoe/mirai/discussions) 讨论。 我们希望 mirai 变得更易用. 您的 `star` 是对我们最大的鼓励(点击项目右上角) +开发交流:[![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + ### 加入 mirai 开发组 若您有意加入 mirai, mirai-console 和相关社区开发, 请 [邮件联系](mailto:support@mamoe.net) (`support@mamoe.net`), 并附加相关开发经验证明. diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 6d3c24703..0996e519f 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -21,7 +21,7 @@ import org.gradle.api.attributes.Attribute */ object Versions { - const val project = "2.0-M1-dev-3" + const val project = "2.0-M1-1" const val kotlinCompiler = "1.4.21" const val kotlinStdlib = "1.4.21" diff --git a/docs/Bots.md b/docs/Bots.md new file mode 100644 index 000000000..4e641f261 --- /dev/null +++ b/docs/Bots.md @@ -0,0 +1,41 @@ +# Mirai - Creating Bots + +## 获取 `Bot` 实例 + +一个机器人被以 `Bot` 对象描述。mirai 的交互入口点是 `Bot`。`Bot` 只可通过 [`BotFactory`](../mirai-core-api/src/commonMain/kotlin/BotFactory.kt#L22-L87) 内的 `newBot` 方法获得: + +```kotlin +interface BotFactory { + fun newBot(qq: Long, password: String, configuration: BotConfiguration): Bot + fun newBot(qq: Long, password: String): Bot + fun newBot(qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration): Bot + fun newBot(qq: Long, passwordMd5: ByteArray): Bot + + companion object : BotFactory by BotFactoryImpl +} +``` + +通常的调用方法为: +``` +// Kotlin +val bot = BotFactory.newBot( ) + +// Java +Bot bot = BotFactory.INSTANCE.newBot( ); +``` + +### 获取当前所有 `Bot` 实例 +在登录后,`Bot` 实例会被自动记录。可在 `Bot.instances` 获取到当前在线的所有 `Bot` 列表。当 `Bot` 离线,其实例就会被删除。 + +## 登录 + +创建 `Bot` 后不会自动登录,需要手动调用其 `login()` 方法。在 Kotlin 还可以使用 `Bot.alsoLogin()` 扩展,相当于 `bot.apply { login() }` + +### 重新登录 + +可以再次调用 `Bot.login()` 以重新登录 `Bot`。这一般没有必要——`Bot` 运行时会自动与服务器同步事件(如群成员变化,好友数量变化)。 + + +> 下一步,[Contacts](Contacts.md) +> +> [回到 Mirai 文档索引](README.md) diff --git a/docs/ConfiguringProjects.md b/docs/ConfiguringProjects.md new file mode 100644 index 000000000..d56a10fb5 --- /dev/null +++ b/docs/ConfiguringProjects.md @@ -0,0 +1,132 @@ +# Mirai - Configuring Projects + +本文介绍如何在一个项目中使用 mirai。 + +mirai 使用纯 Kotlin 开发,最低要求 `JDK 1.8`,`Kotlin 1.4`。但注意不要使用 Oracle JDK,推荐使用 OpenJDK。 + +如果你熟悉 Gradle,只需要添加 `jcenter` 仓库和依赖 `net.mamoe:mirai-core:VERSION` 即可而不需要继续阅读。下文将详细解释其他方法。 + +本文提供如下三种配置方法,但更推荐使用 Gradle 构建。 + +- [A. 使用 Gradle](#a-使用-gradle) +- [B. 使用 Maven](#b-使用-maven) +- [C. 使用 IntelliJ](#c-使用-intellij) + +### 选择版本 +有关各类版本的区别,参考 [版本规范](Evolution.md#版本规范) + +[Version]: https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg? +[Bintray Download]: https://bintray.com/him188moe/mirai/mirai-core/ + +| 版本类型 | 版本号 | +|:------:|:------------------------------:| +| 稳定 | 1.3.3 | +| 预览 | 2.0-M1-1 | +| 开发 | [![Version]][Bintray Download] | + +即使 2.0 还没有稳定,也建议使用 2.0 预览版本,因 1.x 版本将不会收到任何更新。 + +## A. 使用 Gradle + +### Gradle Kotlin DSL + +在 `build.gradle.kts` 添加: + +```kotlin +plugins { + kotlin("jvm") version "1.4.21" +} + +repositories { + jcenter() +} + +dependencies { + api("net.mamoe", "mirai-core", "1.3.3") // 替换为你需要的版本号 +} +``` + +注意,必须添加 Kotlin 插件才能正确获取 mirai 软件包。 + +### Gradle Groovy DSL + +在 `build.gradle` 添加 + +```groovy +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.4.21' +} + +repositories { + jcenter() +} + +dependencies { + api('net.mamoe', 'mirai-core', '1.3.3') // 替换为你需要的版本号 +} +``` + + +## B. 使用 Maven + +在 `pom.xml` 中: + +### 1. 添加 jcenter 仓库 +```xml + + + jcenter + https://jcenter.bintray.com/ + + +``` + +### 2. 添加 mirai 依赖 + +```xml + + + net.mamoe + mirai-core-jvm + 1.3.3 + + +``` + +### 3. 添加 Kotlin 依赖 + +通常 mirai 可以直接使用。但 mirai 使用的 Kotlin 1.4 可能与你的项目使用的其他库依赖的 Kotlin 版本冲突,Maven 无法正确处理这种冲突。此时请手动添加 Kotlin 标准库依赖。 + +```xml + + 1.4.20 + +``` +```xml + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + +``` + +> 可以在 [Kotlin 官方文档](https://www.kotlincn=.net/docs/reference/using-maven.html) 获取更多有关配置 Kotlin 的信息。 + +## C. 使用 IntelliJ + +### 1. 创建项目 + +使用现有项目,或创建一个新项目(`File->New->Project`)或新模块(`File->New->Module`)。 + +### 2. 添加依赖 + +1. 进入 `Project Structure`(`File->Project Structure`,`Ctrl+Alt+Shift+S`) +2. 进入 `Libraries` +3. 找到 `+` 按钮,点击 `From Maven` +4. 输入 `net.mamoe:mirai-core:1.3.3`,勾选 `Sources` +5. 确认并等待下载 + + +> [回到 Mirai 文档索引](README.md) diff --git a/docs/Contacts.md b/docs/Contacts.md new file mode 100644 index 000000000..718eb06f3 --- /dev/null +++ b/docs/Contacts.md @@ -0,0 +1,11 @@ +# Mirai - Contacts + +[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIEJvdCB7XG4gICAgK2ZyaWVuZHM6IENvbnRhY3RMaXN0XG4gICAgK2dyb3VwczogQ29udGFjdExpc3RcbiAgICArZ2V0RnJpZW5kKExvbmcpIEZyaWVuZD9cbiAgICArZ2V0RnJpZW5kT3JOdWxsKExvbmcpIEZyaWVuZFxuICAgICtnZXRHcm91cChMb25nKSBHcm91cD9cbiAgICArZ2V0R3JvdXBPckZhaWwoTG9uZykgR3JvdXBcbiAgICArbG9naW4oKVxuICAgICtjbG9zZSgpXG59XG5cbmNsYXNzIENvbnRhY3RPckJvdCB7XG4gICAgK2lkOiBJbnRcbn1cblxuY2xhc3MgVXNlck9yQm90IHtcbiAgICArbnVkZ2UoKSBOdWRnZVxufVxuXG5jbGFzcyBDb250YWN0IHtcbiAgICArYm90OiBCb3RcbiAgICArc2VuZE1lc3NhZ2UoTWVzc2FnZSkgTWVzc2FnZVJlY2VpcHRcbiAgICArc2VuZE1lc3NhZ2UoU3RyaW5nKSBNZXNzYWdlUmVjZWlwdFxuICAgICt1cGxvYWRJbWFnZShFeHRlcm5hbEltYWdlKSBJbWFnZVxufVxuXG5jbGFzcyBVc2VyIHtcbiAgICArbmljazogU3RyaW5nXG4gICAgK3JlbWFyazogU3RyaW5nXG4gICAgK2F2YXRhclVybDogU3RyaW5nXG59XG5cbmNsYXNzIEdyb3VwIHtcbiAgICArbWVtYmVyczogQ29udGFjdExpc3RcbiAgICArbmFtZTogU3RyaW5nXG4gICAgK3NldHRpbmdzOiBHcm91cFNldHRpbmdzXG4gICAgK293bmVyOiBOb3JtYWxNZW1iZXJcbiAgICArYm90TXV0ZVJlbWFpbmluZzogTG9uZ1xuICAgICtib3RQZXJtaXNzaW9uOiBNZW1iZXJQZXJtaXNzaW9uXG4gICAgK3F1aXQoKSBCb29sZWFuXG4gICAgK3VwbG9hZFZvaWNlKCkgVm9pY2Vcbn1cblxuY2xhc3MgTm9ybWFsTWVtYmVyIHtcbiAgICArbXV0ZSgpXG4gICAgK2tpY2soKVxufVxuXG5jbGFzcyBBbm9ueW1vdXNNZW1iZXIge1xuICAgICthbm9ueW1vdXNJZDogU3RyaW5nXG59XG5cbmNsYXNzIE1lbWJlciB7XG4gICAgK2dyb3VwOiBHcm91cFxufVxuXG5Db250YWN0T3JCb3Q8LS1Db250YWN0XG5Db250YWN0T3JCb3Q8LS1Vc2VyT3JCb3RcblxuVXNlck9yQm90PC0tQm90XG5Vc2VyT3JCb3Q8LS1Vc2VyXG5cbkNvbnRhY3Q8LS1Vc2VyXG5Db250YWN0PC0tR3JvdXBcblxuVXNlcjwtLU1lbWJlclxuVXNlcjwtLUZyaWVuZFxuXG5NZW1iZXI8LS1Ob3JtYWxNZW1iZXJcbk1lbWJlcjwtLUFub255bW91c01lbWJlciIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIEJvdCB7XG4gICAgK2ZyaWVuZHM6IENvbnRhY3RMaXN0XG4gICAgK2dyb3VwczogQ29udGFjdExpc3RcbiAgICArZ2V0RnJpZW5kKExvbmcpIEZyaWVuZD9cbiAgICArZ2V0RnJpZW5kT3JOdWxsKExvbmcpIEZyaWVuZFxuICAgICtnZXRHcm91cChMb25nKSBHcm91cD9cbiAgICArZ2V0R3JvdXBPckZhaWwoTG9uZykgR3JvdXBcbiAgICArbG9naW4oKVxuICAgICtjbG9zZSgpXG59XG5cbmNsYXNzIENvbnRhY3RPckJvdCB7XG4gICAgK2lkOiBJbnRcbn1cblxuY2xhc3MgVXNlck9yQm90IHtcbiAgICArbnVkZ2UoKSBOdWRnZVxufVxuXG5jbGFzcyBDb250YWN0IHtcbiAgICArYm90OiBCb3RcbiAgICArc2VuZE1lc3NhZ2UoTWVzc2FnZSkgTWVzc2FnZVJlY2VpcHRcbiAgICArc2VuZE1lc3NhZ2UoU3RyaW5nKSBNZXNzYWdlUmVjZWlwdFxuICAgICt1cGxvYWRJbWFnZShFeHRlcm5hbEltYWdlKSBJbWFnZVxufVxuXG5jbGFzcyBVc2VyIHtcbiAgICArbmljazogU3RyaW5nXG4gICAgK3JlbWFyazogU3RyaW5nXG4gICAgK2F2YXRhclVybDogU3RyaW5nXG59XG5cbmNsYXNzIEdyb3VwIHtcbiAgICArbWVtYmVyczogQ29udGFjdExpc3RcbiAgICArbmFtZTogU3RyaW5nXG4gICAgK3NldHRpbmdzOiBHcm91cFNldHRpbmdzXG4gICAgK293bmVyOiBOb3JtYWxNZW1iZXJcbiAgICArYm90TXV0ZVJlbWFpbmluZzogTG9uZ1xuICAgICtib3RQZXJtaXNzaW9uOiBNZW1iZXJQZXJtaXNzaW9uXG4gICAgK3F1aXQoKSBCb29sZWFuXG4gICAgK3VwbG9hZFZvaWNlKCkgVm9pY2Vcbn1cblxuY2xhc3MgTm9ybWFsTWVtYmVyIHtcbiAgICArbXV0ZSgpXG4gICAgK2tpY2soKVxufVxuXG5jbGFzcyBBbm9ueW1vdXNNZW1iZXIge1xuICAgICthbm9ueW1vdXNJZDogU3RyaW5nXG59XG5cbmNsYXNzIE1lbWJlciB7XG4gICAgK2dyb3VwOiBHcm91cFxufVxuXG5Db250YWN0T3JCb3Q8LS1Db250YWN0XG5Db250YWN0T3JCb3Q8LS1Vc2VyT3JCb3RcblxuVXNlck9yQm90PC0tQm90XG5Vc2VyT3JCb3Q8LS1Vc2VyXG5cbkNvbnRhY3Q8LS1Vc2VyXG5Db250YWN0PC0tR3JvdXBcblxuVXNlcjwtLU1lbWJlclxuVXNlcjwtLUZyaWVuZFxuXG5NZW1iZXI8LS1Ob3JtYWxNZW1iZXJcbk1lbWJlcjwtLUFub255bW91c01lbWJlciIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9) + +要主动发送一条消息,调用 `Contact.sendMessage()` 即可。 + +可通过 `Bot.getFriend` 或 `Bot.getGroup` 获取相关对象。 + +> 下一步,[Events](Events.md) +> +> [回到 Mirai 文档索引](README.md) \ No newline at end of file diff --git a/docs/Events.md b/docs/Events.md new file mode 100644 index 000000000..dbd8303c8 --- /dev/null +++ b/docs/Events.md @@ -0,0 +1,333 @@ +# Mirai - Events + +## 目录 + +- [事件系统](#事件系统) +- [监听事件](#监听事件) + - [使用 `ListenerHost` 监听事件](#使用-listenerhost-监听事件) + - [在 Kotlin 函数式监听](#在-kotlin-函数式监听) +- [实现事件](#实现事件) +- [工具函数(Kotlin)](#工具函数kotlin) + + +## 事件系统 + +Mirai 以事件驱动,使用者需要监听如 `收到消息`,`收到入群申请` 等事件。 + +[`Event`]: ../mirai-core-api/src/commonMain/kotlin/event/Event.kt#L21-L62 + +每个事件都实现接口 [`Event`],且继承 `AbstractEvent`。 +实现 `CancellableEvent` 的事件可以被取消(`CancellableEvent.cancel`)。 + +[事件列表](../mirai-core-api/src/commonMain/kotlin/event/events/README.md#事件) + +## 监听事件 + +有两种方法监听事件: +- [使用 `ListenerHost` 监听事件](#使用-listenerhost-监听事件) +- [在 Kotlin 函数式监听](#在-kotlin-函数式监听) + +### 使用 `ListenerHost` 监听事件 + +标注一个函数(方法)为事件监听器。mirai 通过反射获取他们并为之注册事件。 + +> 详见 [EventHandler](../mirai-core-api/src/commonMain/kotlin/event/JvmMethodListeners.kt#L27-L168) + +#### Kotlin 函数 + +Kotlin 函数要求: +- 接收者 (英 receiver) 和函数参数: 所标注的 Kotlin 函数必须至少拥有一个接收者或一个函数参数, 或二者都具有. 接收者和函数参数的类型必须相同 (如果二者都存在) + 接收者或函数参数的类型都必须为 `Event` 或其子类. +- 返回值: 为 `Unit` 或不指定返回值时将注册为 `CoroutineScope.subscribeAlways`, 为 `ListeningStatus` 时将注册为 `CoroutineScope.subscribe`. + 任何其他类型的返回值将会在注册时抛出异常. + +所有 Kotlin 非 `suspend` 的函数都将会在 `Dispatchers.IO` 中调用 + +支持的函数类型: +```kotlin +// 所有函数参数, 函数返回值都不允许标记为可空 (带有 '?' 符号) +// T 表示任何 Event 类型. +suspend fun T.onEvent(T) +suspend fun T.onEvent(T): ListeningStatus +suspend fun T.onEvent(T): Nothing +suspend fun onEvent(T) +suspend fun onEvent(T): ListeningStatus +suspend fun onEvent(T): Nothing +suspend fun T.onEvent() +suspend fun T.onEvent(): ListeningStatus +suspend fun T.onEvent(): Nothing +fun T.onEvent(T) +fun T.onEvent(T): ListeningStatus +fun T.onEvent(T): Nothing +fun onEvent(T) +fun onEvent(T): ListeningStatus +fun onEvent(T): Nothing +fun T.onEvent() +fun T.onEvent(): ListeningStatus +fun T.onEvent(): Nothing +``` + +Kotlin 使用示例: + +- 独立 `CoroutineScope` 和 `ListenerHost` +```kotlin +object MyEvents : ListenerHost { + override val coroutineContext = SupervisorJob() + // 可以抛出任何异常, 将在 this.coroutineContext 或 registerEvents 时提供的 CoroutineScope.coroutineContext 中的 CoroutineExceptionHandler 处理. + @EventHandler + suspend fun MessageEvent.onMessage() { + reply("received") + } +} +myCoroutineScope.registerEvents(MyEvents) +``` +`onMessage` 抛出的异常将会交给 `myCoroutineScope` 处理 + +- 合并 `CoroutineScope` 和 `ListenerHost`: 使用 `SimpleListenerHost` +```kotlin +object MyEvents : SimpleListenerHost( /* override coroutineContext here */ ) { + override fun handleException(context: CoroutineContext, exception: Throwable) { + // 处理 onMessage 中未捕获的异常 + } + @EventHandler + suspend fun MessageEvent.onMessage() { // 可以抛出任何异常, 将在 handleException 处理 + reply("received") + // 无返回值 (或者返回 Unit), 表示一直监听事件. + } + @EventHandler + suspend fun MessageEvent.onMessage(): ListeningStatus { // 可以抛出任何异常, 将在 handleException 处理 + reply("received") + return ListeningStatus.LISTENING // 表示继续监听事件 + // return ListeningStatus.STOPPED // 表示停止监听事件 + } +} +MyEvents.registerEvents() +``` +#### Java 方法 + +所有 Java 方法都会在 `Dispatchers.IO` 中调用,因此在 Java 可以调用阻塞方法。 + +支持的方法类型: +``` +// T 表示任何 Event 类型. +void onEvent(T) +Void onEvent(T) +ListeningStatus onEvent(T) // 返回 null 时将抛出异常 +``` + +Java 使用示例: + +```java +public class MyEventHandlers extends SimpleListenerHost { + @Override + public void handleException(@NotNull CoroutineContext context, @NotNull Throwable exception){ + // 处理事件处理时抛出的异常 + } + @EventHandler + public void onMessage(@NotNull MessageEvent event) throws Exception { // 可以抛出任何异常, 将在 handleException 处理 + event.subject.sendMessage("received"); + // 无返回值, 表示一直监听事件. + } + @NotNull + @EventHandler + public ListeningStatus onMessage(@NotNull MessageEvent event) throws Exception { // 可以抛出任何异常, 将在 handleException 处理 + event.subject.sendMessage("received"); + return ListeningStatus.LISTENING; // 表示继续监听事件 + // return ListeningStatus.STOPPED; // 表示停止监听事件 + } +} + +// 注册: +// Events.registerEvents(new MyEventHandlers()) +``` + +### 在 Kotlin 函数式监听 +[CoroutineScope.subscribe](../mirai-core-api/src/commonMain/kotlin/event/subscriber.kt#L137-L220) + +用法示例: +```kotlin +object MyApplication : CoroutineScope by CoroutineScope(SupervisorJob()) + +// 启动事件监听器 +MyApplication.subscribeAlways { + // this: GroupMessageEvent + // it: GroupMessageEvent + // lambda 的 this 和参数都是 GroupMessageEvent + + group.sendMessage(sender.at() + "Hello! ${sender.nick}") +} + +// YouApplication[Job]!!.cancel() // +``` + +Mirai 也支持传递函数引用: +```kotlin +suspend fun GroupMessageEvent.onEvent() { + group.sendMessage(sender.at() + "Hello! ${sender.nick}") +} + +MyApplication.subscribeAlways(GroupMessageEvent::onEvent) +``` +既可以使用接收者参数,又可以使用普通参数,还可以同时拥有。如下三个定义都是被接受的: +```kotlin +suspend fun GroupMessageEvent.onEvent() +suspend fun GroupMessageEvent.onEvent(event: GroupMessageEvent) +suspend fun onEvent(event: GroupMessageEvent) +``` + +### 在 Kotlin 使用 DSL 监听事件 +> **警告:此节内容需要坚实的 Kotlin 技能,盲目使用会导致问题** + +[subscribeMessages](../mirai-core-api/src/commonMain/kotlin/event/subscribeMessages.kt#L37-L64) + +示例: +```kotlin +MyApplication.subscribeMessages { + "test" { + // 当消息内容为 "test" 时执行 + // this: MessageEvent + reply("test!") + } + + "Hello" reply "Hi" // 当消息内容为 "Hello" 时回复 "Hi" + "quote me" quoteReply "ok" // 当消息内容为 "quote me" 时引用该消息并回复 "ok" + "quote me2" quoteReply { + // lambda 也是允许的: + // 返回值接受 Any? + // 为 Unit 时不发送任何内容; + // 为 Message 时直接发送; + // 为 String 时发送为 PlainText; + // 否则 toString 并发送为 PlainText + + "ok" + } + + case("iGNorECase", ignoreCase=true) reply "OK" // 忽略大小写 + startsWith("-") reply { cmd -> + // 当消息内容以 "-" 开头时执行 + // cmd 为消息去除开头 "-" 的内容 + } + + + val listener: Listener = "1" reply "2" + // 每个语句都会被注册为事件监听器,可以这样获取监听器 + + listener.complete() // 停止 "1" reply "2" 这个事件监听 +} +``` + +## 实现事件 + +只要实现接口 `Event` 并继承 `AbstractEvent` 的对象就可以被广播。 + +要广播一个事件,使用 `Event.broadcast()`(Kotlin)或 `EventKt.broadcast(Event)`(Java)。 + +## 工具函数(Kotlin) + +*可能需要较好的 Kotlin 技能才能理解以下内容。* + +基于 Kotlin 协程特性,mirai 提供 ` + +### 线性同步(`syncFromEvent`) +[linear.kt](../mirai-core-api/src/commonMain/kotlin/event/linear.kt) + +挂起协程并获取下一个戳 Bot 的对象: +```kotlin +val target: UserOrBot = syncFromEvent { sender } +``` + +带超时版本: +```kotlin +val target: UserOrBot = syncFromEvent(5000) { sender } // 5000ms +``` + +异步 `async` 版本: + +```kotlin +val target: Deferred = coroutineScope.asyncFromEvent { sender } +``` + +### 线性同步(`nextEvent`) +[nextEvent.kt](../mirai-core-api/src/commonMain/kotlin/event/nextEvent.kt) + +挂起协程并获取下一个指定事件: + +```kotlin +val event: BotNudgedEvent = nextEvent() +``` + +带超时和过滤器版本: + +```kotlin +val event: BotNudgedEvent = nextEvent(5000) { it.bot.id == 123456L } +``` + +### 条件选择(`selectMessages`) +> **警告:此节内容需要坚实的 Kotlin 技能,盲目使用会导致问题** + +[select.kt](../mirai-core-api/src/commonMain/kotlin/event/select.kt) + +类似于 Kotlin 协程 `select`,mirai 也提供类似的功能。 + +`selectMessages`:挂起当前协程,等待任意一个事件监听器触发后返回其返回值。 + +```kotlin +MyCoroutineScope.subscribeAlways { + if (message.contentEquals("ocr")) { + reply("请发送你要进行 OCR 的图片或图片链接") + val image: InputStream = selectMessages { + has { URL(it.queryUrl()).openStream() } + has { URL(it.content).openStream() } + defaultReply { "请发送图片或图片链接" } + timeout(30_000) { event.quoteReply("请在 30 秒内发送图片或图片链接"); null } + } ?: return@subscribeAlways + + val result = ocr(image) + quoteReply(result) + } +} +``` + +这种语法就相当于(伪代码): +``` +val image = when (下一条消息) { + 包含图片 { 查询图片链接() } + 包含纯文本 { 下载图片() } + 其他情况 { 引用回复() } + 超时 { 引用回复() } +} +``` + +### 循环条件选择(`whileSelectMessages`) +> **警告:此节内容需要坚实的 Kotlin 技能,盲目使用会导致问题** + +[select.kt](../mirai-core-api/src/commonMain/kotlin/event/select.kt) + +类似于 Kotlin 协程 `whileSelect`,mirai 也提供类似的功能。 + +`whileSelectMessages`:挂起当前协程,等待任意一个事件监听器返回 `false` 后返回。 + +```kotlin +reply("开启复读模式") +whileSelectMessages { + "stop" { + reply("已关闭复读") + false // 停止循环 + } + // 也可以使用 startsWith("") { true } 等 DSL + default { + reply(message) + true // 继续循环 + } + timeout(3000) { + // on + true + } +} // 等待直到 `false` +reply("复读模式结束") +``` + + +> 下一步,[Messages](Messages.md) +> +> [回到 Mirai 文档索引](README.md) \ No newline at end of file diff --git a/docs/Evolution.md b/docs/Evolution.md new file mode 100644 index 000000000..3f5aaaf8a --- /dev/null +++ b/docs/Evolution.md @@ -0,0 +1,50 @@ +# Mirai - Evolution + +### Mirai 演进 + +Mirai 是不断前进的库,将来必定会发生 API 弃用和重构。 +维护者会严谨地推进每一项修改,并提供迁移周期(至少 2 个次版本)。 + +### 版本规范 + +Mirai 的版本号遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/#spec-item-9) 规范。 + +在日常开发中, Mirai 会以 `-dev-1`,`-dev-2` 等版本后缀发布开发预览版本。这些版本仅用于兼容性测试等目的,无稳定性保证。 + +在大版本开发过程中,Mirai 会以 `-M1`, `-M2` 等版本后缀发布里程碑预览版本。代表一系列功能的完成,但还不稳定。 +这些版本里新增的 API 仍可能还会在下一个 Milestone 版本变化,因此请按需使用。 + +在大版本即将发布前,Mirai 会以 `-RC` 版本后缀发布最终的预览版本。 +`RC` 表示新版本 API 已经确定,离稳定版发布只差最后的一些内部优化或 bug 修复。 + +### 版本选择 + +**稳定性**:稳定 (`x.y.z`) > 发布预览 (`-RC`) > 里程碑预览 (`-M`) > 开发 (`-dev`)。 + +| 目的 | 推荐至少更新到版本 | +|:------------------:|:--------------:| +| 生产环境 | `x.y.z` | +| 希望尽早体验稳定新特性 | `-RC` | +| 无论如何都想体验新特性 | `-M` | +| 为 Mirai 提交 PR | `-dev` | + +## 更新兼容性 + +对于 `x.y.z` 版本号: +- 当 `z` 增加时,只会有 bug 修复,和必要的新函数添加(为了解决某一个问题),不会有破坏性变化。 +- 当 `y` 增加时,可能有新 API 的引入,和旧 API 的弃用。但这些弃用会经过一个弃用周期后才被删除(隐藏)。向下兼容得到保证。 +- 当 `x` 增加时,任何 API 都可能会有变化。无兼容性保证。 + +## 弃用周期 + +一个计划被删除的 API,将会在下一个次版本开始经历弃用周期。 + +如一个 API 在 `1.1.0` 起被弃用,它首先会是 `WARNING` (使用时会得到一个编译警告)弃用级别。 +在 `1.2.0` 上升为 `ERROR`(使用时会得到一个编译错误); +在 `1.3.0` 上升为 `HIDDEN`(使用者无法看到这些 API)。 + +`HIDDEN` 的 API 仍然会保留在代码中并正常编译,以提供二进制兼容性,直到下一个主版本更新。 + + + +> [回到 Mirai 文档索引](README.md) \ No newline at end of file diff --git a/docs/Messages.md b/docs/Messages.md new file mode 100644 index 000000000..f25d0eecf --- /dev/null +++ b/docs/Messages.md @@ -0,0 +1,11 @@ +# Mirai - Messages + +## 消息系统 + +在 Contacts 章节提到,要发送消息,使用 `Contact.sendMessage(Message)`。 + +[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIE1lc3NhZ2VDaGFpblxuTWVzc2FnZUNoYWluIDogTGlzdH5TaW5nbGVNZXNzYWdlflxuXG5NZXNzYWdlPHwtLU1lc3NhZ2VDaGFpblxuTWVzc2FnZTx8LS1TaW5nbGVNZXNzYWdlXG5cbk1lc3NhZ2VDaGFpbiBvLS0gU2luZ2xlTWVzc2FnZVxuXG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VDb250ZW50XG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VNZXRhZGF0YVxuXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIE1lc3NhZ2VDaGFpblxuTWVzc2FnZUNoYWluIDogTGlzdH5TaW5nbGVNZXNzYWdlflxuXG5NZXNzYWdlPHwtLU1lc3NhZ2VDaGFpblxuTWVzc2FnZTx8LS1TaW5nbGVNZXNzYWdlXG5cbk1lc3NhZ2VDaGFpbiBvLS0gU2luZ2xlTWVzc2FnZVxuXG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VDb250ZW50XG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VNZXRhZGF0YVxuXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ) + +`SingleMessage` 表示单个消息元素,`MessageChain`(消息链) 是 `List<SingleMessage>`。主动发送的消息和从服务器接收消息都是 `MessageChain`。 + +mirai 提供大量消息链的扩展:[MessageChain.kt](../mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt#L59)。 \ No newline at end of file diff --git a/docs/mirai-code-specification.md b/docs/MiraiCodeSepecification.md similarity index 95% rename from docs/mirai-code-specification.md rename to docs/MiraiCodeSepecification.md index 7cc07f7e6..db3e87d09 100644 --- a/docs/mirai-code-specification.md +++ b/docs/MiraiCodeSepecification.md @@ -4,12 +4,10 @@ ## mirai 码 mirai 的部分 [消息](../mirai-core-api/src/commonMain/kotlin/message/data/Message.kt) 可以表示为形如 `[mirai:atall]` 的字符串. -模块 `mirai-core` 包含消息到 mirai 码的单向转换; 模块 `mirai-serialization` 提供 mirai 码的解析. - -运行时: [mirai-serialization](../mirai-serialization/) ## 变更记录 - `1.1.0`: 引入 mirai 码于 `mirai-serialization` 模块 +- `1.2.0`: mirai 码集成到 mirai-core。不再需要 `mirai-serialization` 模块。 ## 格式 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..5c5e3bb0f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,71 @@ +# Mirai + +欢迎来到 mirai 文档。 + +## 生态 + +[Mirai 生态概览](mirai-ecology.md) + +## 确定 SDK + +mirai 官方提供 Kotlin/Java 等 JVM 平台语言开发支持。如果你不熟悉该语言,请使用以下社区提供的 SDK: + +### 基于 [`mirai-console`] + +[`mirai-console`]: https://github.com/mamoe/mirai-console + +这些 SDK 基于 [`mirai-console`],意味着需要使用 [`mirai-console`] 框架。[`mirai-console`] 也是 mirai 的官方项目之一。 + +[mamoe/mirai-api-http]: https://github.com/mamoe/mirai-api-http +[iTXTech/mirai-native]: https://github.com/iTXTech/mirai-native +[iTXTech/mirai-js]: https://github.com/iTXTech/mirai-js +[GraiaProject/Application]: https://github.com/GraiaProject/Application +[RedBeanN/node-mirai]: https://github.com/RedBeanN/node-mirai +[Logiase/gomirai]: https://github.com/Logiase/gomirai +[StageGuard/mirai-rhinojs-sdk]: https://github.com/StageGuard/mirai-rhinojs-sdk +[cyanray/mirai-cpp]: https://github.com/cyanray/mirai-cpp +[Chlorie/miraipp]: https://github.com/Chlorie/miraipp-template +[Executor-Cheng/mirai-CSharp]: https://github.com/Executor-Cheng/mirai-CSharp +[HoshinoTented/mirai-rs]: https://github.com/HoshinoTented/mirai-rs +[YunYouJun/mirai-ts]: https://github.com/YunYouJun/mirai-ts +[only52607/e-mirai]: https://github.com/only52607/e-mirai +[theGravityLab/ProjHyperai]: https://github.com/theGravityLab/ProjHyperai +[yyuueexxiinngg/cqhttp-mirai]: https://github.com/yyuueexxiinngg/cqhttp-mirai + +| 技术 | 维护者及项目地址 | 描述 | +|:----------------|:--------------------------------------------|:-----------------------------------------------------------------------| +| *Http* | [mamoe/mirai-api-http] | Mirai 官方维护的 HTTP API 插件 | +| `JavaScript` | [iTXTech/mirai-js] | 支持使用 `JavaScript` 编写插件并**直接**与 mirai 交互 | +| `Python` | [Graia Framework][GraiaProject/Application] | 基于 `mirai-api-http` 的机器人开发框架 | +| `Node.js` | [RedBeanN/node-mirai] | mirai 的 Node.js SDK | +| `Go` | [Logiase/gomirai] | 基于 mirai-api-http 的 GoLang SDK | +| `Mozilla Rhino` | [StageGuard/mirai-rhinojs-sdk] | 为基于 Rhino(如 Auto.js 等安卓 app 或运行环境)的 JavaScript 提供简单易用的 SDK | +| `C++` | [cyanray/mirai-cpp] | mirai-http-api 的 C++ 封装,方便使用 C++ 开发 mirai-http-api 插件 | +| `C++` | [Chlorie/miraipp] | mirai-http-api 的另一个 C++ 封装,使用现代 C++ 特性,并提供了较完善的说明文档 | +| `C#` | [Executor-Cheng/mirai-CSharp] | 基于 mirai-api-http 的 C# SDK | +| `Rust` | [HoshinoTented/mirai-rs] | mirai-http-api 的 Rust 封装 | +| `TypeScript` | [YunYouJun/mirai-ts] | mirai-api-http 的 TypeScript SDK,附带声明文件,拥有良好的注释和类型提示 | +| `易语言` | [only52607/e-mirai] | mirai-api-http 的 易语言 SDK,使用全中文环境开发插件,适合编程新手使用 | +| `.Net/C#` | [Hyperai][theGravityLab/ProjHyperai] | 从 mirai-api-http 对接到机器人开发框架再到开箱即用的插件式机器人程序一应俱全 | +| *酷 Q 插件* | [iTXTech/mirai-native] | 支持酷 Q 插件在 mirai 上运行 | +| *酷 Q HTTP* | [yyuueexxiinngg/cqhttp-mirai] | 在 mirai-console 开启酷 Q HTTP 服务。 | + +### 基于 `mirai-core` 的 SDK + +- `Lua`: [lua-mirai](https://github.com/only52607/lua-mirai) 基于 mirai-core 的 Lua SDK,并提供了 Java 扩展支持,可在 Lua 中调用 Java 代码开发机器人 + +## 配置 JVM 项目使用 mirai + +> 可以首先体验让机器人发送消息:在 IDE 克隆 [mirai-hello-world](https://github.com/project-mirai/mirai-hello-world) 并运行其中入口点。 + +要把 mirai 作为一个依赖库使用,请参考 [Configuring Projects](ConfiguringProjects.md)。 + +要使用 mirai-console 框架,请前往 [mirai-console](https://github.com/mamoe/mirai-console)。 + +## 开发 + +***本文档正在更新中*** + +- [Bots](Bots.md) +- [Contacts](Contacts.md) +- [Events](Events.md) diff --git a/docs/guide_build_for_mirai.md b/docs/guide_build_for_mirai.md deleted file mode 100644 index 23a60607d..000000000 --- a/docs/guide_build_for_mirai.md +++ /dev/null @@ -1,186 +0,0 @@ -# Mirai Guide - Build For Mirai - -由于Mirai项目在快速推进中,因此内容时有变动,本文档的最后更新日期为```2020-02-29```,对应版本```0.23.0``` - -本页面采用Kotlin作为开发语言,**若你希望使用 Java 开发**, 请参阅: [mirai-japt](https://github.com/mamoe/mirai-japt) - -本页面是[Mirai Guide - Subscribe Events](/docs/guide_subscribe_events.md)的后续Guide - -## build.gradle - -我们首先来看一下完整的```build.gradle```文件 - -```groovy -plugins { - id 'java' - id 'org.jetbrains.kotlin.jvm' version '1.3.61' -} - -group 'test' -version '1.0-SNAPSHOT' - -sourceCompatibility = 1.8 - -repositories { - mavenCentral() - jcenter() -} - -dependencies { - implementation 'net.mamoe:mirai-core-qqandroid:0.19.1' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - testCompile group: 'junit', name: 'junit', version: '4.12' -} - -compileKotlin { - kotlinOptions.jvmTarget = "1.8" -} -compileTestKotlin { - kotlinOptions.jvmTarget = "1.8" -} -``` - -使用gradle直接打包,不会将依赖也打包进去 - -因此,我们引入一些插件进行打包 - -### ShadowJar - -shadowJar支持将依赖也打包到Jar内,下面介绍用法。 - -#### 1.buildscript - -首先声明buildScript - -```groovy -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0' - } -} -``` - -在plugin前加入以上语句 - - - -#### 2.在plugins中进行插件的使用 - -将原本的plugins - -```groovy -plugins { - id 'java' - id 'org.jetbrains.kotlin.jvm' version '1.3.61' -} -``` - -覆盖为 - -```groovy -plugins { - id 'java' - id 'org.jetbrains.kotlin.jvm' version '1.3.61' - id 'com.github.johnrengelman.shadow' version '5.2.0'//使用shadow对依赖进行打包 -} - -apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: 'java' -``` - - - -#### 3.添加shadowJar - -在文件底部添加 - -```groovy -shadowJar { - // 生成包的命名规则: baseName-version-classifier.jar - manifest { - attributes( - 'Main-Class': 'net.mamoe.mirai.simpleloader.MyLoaderKt'//入口点 - ) - } - - // 将 build.gradle 打入到 jar 中, 方便查看依赖包版本 - from("./"){ - include 'build.gradle' - } -} -``` - - - -#### 4.运行build - -在IDEA中点击```ShadowJar```左侧的run按钮(绿色小三角),build完成后在```build\libs```中找到jar - - - -至此,build.gradle内的内容是 - -```groovy -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0' - } -} - - -plugins { - id 'java' - id 'org.jetbrains.kotlin.jvm' version '1.3.61' - id 'com.github.johnrengelman.shadow' version '5.2.0'//使用shadow对依赖进行打包 -} - -apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: 'java' - -group 'test' -version '1.0-SNAPSHOT' - -sourceCompatibility = 1.8 - - -repositories { - mavenCentral() - jcenter() -} - -dependencies { - implementation 'net.mamoe:mirai-core-qqandroid:0.23.0' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - testCompile group: 'junit', name: 'junit', version: '4.12' -} - - -compileKotlin { - kotlinOptions.jvmTarget = "1.8" -} -compileTestKotlin { - kotlinOptions.jvmTarget = "1.8" -} - -shadowJar { - // 生成包的命名规则: baseName-version-classifier.jar - manifest { - attributes( - 'Main-Class': 'net.mamoe.mirai.simpleloader.MyLoaderKt' - ) - } - - // 将 build.gradle 打入到 jar 中, 方便查看依赖包版本 - from("./"){ - include 'build.gradle' - } -} - -``` - diff --git a/docs/guide_getting_started.md b/docs/guide_getting_started.md deleted file mode 100644 index 3d07ef82f..000000000 --- a/docs/guide_getting_started.md +++ /dev/null @@ -1,131 +0,0 @@ -# Mirai Guide - Getting Started - -由于Mirai项目在快速推进中,因此内容时有变动,本文档的最后更新日期为```2020-04-01```,对应版本```0.31.4``` - -假如仅仅使用Mirai,不需要对整个项目进行Clone,只需在项目内添加Gradle Dependency或使用即可。 - -下面介绍详细的入门步骤。 - -本页采用Kotlin作为开发语言,**若你希望使用 Java 开发**, 请参阅: [mirai-japt](https://github.com/mamoe/mirai-japt) - - - -## 起步步骤 -通过编写Kotlin程序,以第三方库的形式调用```mirai-core```,并定义你的Mirai Bot行为。 - -假如已经对Gradle有一定了解,可跳过1,2 - -### 1 安装IDEA与JDK - -- JDK 要求6以上 - -### 2 新建Gradle项目 - -*使用gradle项目可能需要代理,在IDEA的```settings```->```proxy settings```中可以设置 - -- 在```File->new project```中选择```Gradle``` -- 在面板中的```Additional Libraries and Frameworks```中勾选```Java```以及```Kotlin/JVM``` -- 点击```next```,填入```GroupId```与```ArtifactId```(对于测试项目来说,可随意填写) -- 点击```next```,点击```Use default gradle wrapper(recommended)``` -- 创建项目完成 - -### 3 添加依赖 - -- 打开项目的```Project```面板,点击编辑```build.gradle``` - -- 首先添加repositories - - ```groovy - //添加jcenter仓库 - /* - repositories { - maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } - mavenCentral() - } - 原文内容,更新为下文 - */ - - repositories { - maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } - mavenCentral() - jcenter() - } - ``` - -- 添加依赖,将dependencies部分覆盖。 `mirai-core` 的最新版本为: [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) - - ```groovy - dependencies { - implementation 'net.mamoe:mirai-core-qqandroid:1.1-EA'//此处版本应替换为当前最新 - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - testCompile group: 'junit', name: 'junit', version: '4.12' - } - ``` - -- 打开右侧Gradle面板,点击刷新按钮 -- 至此,依赖添加完成 - -### 4 Try Bot - -- 在src/main文件夹下新建文件夹,命名为```kotlin``` -- 在```kotlin```下新建包(在```kotlin```文件夹上右键-```New```-```Package```) 包名为```net.mamoe.mirai.simpleloader``` - -- 在包下新建kotlin文件```MyLoader.kt``` - -```kotlin -package net.mamoe.mirai.simpleloader - -import kotlinx.coroutines.* -import net.mamoe.mirai.Bot -import net.mamoe.mirai.alsoLogin -import net.mamoe.mirai.join -import net.mamoe.mirai.message.data.At -import net.mamoe.mirai.contact.Member -import net.mamoe.mirai.event.subscribeMessages - -suspend fun main() { - val qqId = 10000L//Bot的QQ号,需为Long类型,在结尾处添加大写L - val password = "your_password"//Bot的密码 - val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录 - miraiBot.subscribeMessages { - "你好" reply "你好!" - case("at me") { - reply(At(sender as Member) + " 给爷爬 ") - } - - (contains("舔") or contains("刘老板")) { - reply("刘老板太强了") - } - } - miraiBot.join() // 等待 Bot 离线, 避免主线程退出 -} -``` - -- 单击编辑器内第8行(```suspend fun main```)左侧的run按钮(绿色三角),等待,MiraiBot成功登录。 -- 本例的功能中,在任意群内任意成员发送包含“舔”字或“刘老板”字样的消息,MiraiBot会回复“刘老板太强了” - - -至此,简单的入门已经结束,下面可根据不同的需求参阅wiki进行功能的添加。 - -下面,可以尝试对不同事件进行监听[Mirai Guide - Subscribe Events](/docs/guide_subscribe_events.md) - -### 此外,还可以使用Maven作为包管理工具 -本项目推荐使用gradle,因此不提供详细入门指导 - -```xml -<repositories> - <repository> - <id>jcenter</id> - <url>https://jcenter.bintray.com/</url> - </repository> -</repositories> -``` -```xml -<dependencies> - <dependency> - <groupId>net.mamoe</groupId> - <artifactId>mirai-core-qqandroid</artifactId> - <version>0.23.0</version> <!-- 替换版本为最新版本 --> - </dependency> -</dependencies> -``` diff --git a/docs/guide_quick_start.md b/docs/guide_quick_start.md deleted file mode 100644 index cc44d19ce..000000000 --- a/docs/guide_quick_start.md +++ /dev/null @@ -1,103 +0,0 @@ -# Mirai Guide - Quick Start - -由于 mirai 项目在快速推进中,因此内容时有变动,本文档的最后更新日期为```2020/6/22```,对应版本```1.0.2``` - -本文适用于对 Kotlin 较熟悉的开发者, -使用 mirai 作为第三方依赖库引用到任意一个 Kotlin, Java 或其他 JVM 平台的项目 - -**若你希望一份更基础的教程**, 请参阅: [mirai-guide-getting-started](guide_getting_started.md) - -## 构建需求 - -- JDK 6 或更高 - -## 获取 Demo -可在 [mirai-demos](https://github.com/mamoe/mirai-demos) 中获取已经配置好依赖的示例项目. - -## Quick Start - -请将 `VERSION` 替换为 `mirai-core` 的最新版本号(如 `1.0.4`): -[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) - -### 添加依赖 -可通过以下三种方法之一添加 mirai 依赖. - -#### Maven - -```xml -<repositories> - <repository> - <id>jcenter</id> - <url>https://jcenter.bintray.com/</url> - </repository> -</repositories> -``` - -```xml -<dependencies> - <dependency> - <groupId>net.mamoe</groupId> - <artifactId>mirai-core-qqandroid</artifactId> - <version>0.23.0</version> <!-- 替换版本为最新版本 --> - </dependency> -</dependencies> -``` - -#### Gradle (推荐) - -Mirai 只发布在 `jcenter`, 因此请确保添加 `jcenter()` 仓库: - -```kotlin -repositories{ - jcenter() -} -``` - -**注意:** -Mirai 核心由 API 模块(`mirai-core`)和协议模块组成。依赖协议模块时会自动依赖相应版本的 API 模块。 -请参照下文选择目标平台的依赖添加。 - -如果你只用 Java / Kotlin 或其他语言开发 JVM 平台应用,只需要添加下文第一条。 -如果你只开发 Android 应用,只需添加下文第三条。 - -**jvm** (JVM 平台源集) - -```kotlin -implementation("net.mamoe:mirai-core-qqandroid:VERSION") -``` - -**common** (Kotlin 多平台项目的通用源集) - -```kotlin -implementation("net.mamoe:mirai-core-qqandroid-common:VERSION") -``` - -**android** (Android 平台源集) -**注意**: 在 [KT-37152](https://youtrack.jetbrains.com/issue/KT-37152) 修复前, mirai 无法支持 Android 平台目标. -```kotlin -implementation("net.mamoe:mirai-core-qqandroid-android:VERSION") -``` - -#### 直接导入jar包 (不推荐) -下载已经编译好的 Jar 包, 并添加 Jar 依赖: -- [mirai-core](https://github.com/mamoe/mirai-repo/tree/master/shadow/mirai-core) -- [mirai-qqandroid](https://github.com/mamoe/mirai-repo/tree/master/shadow/mirai-core-qqandroid) - - -### 开始使用 - -```kotlin -val bot = Bot(qqId, password).alsoLogin() -bot.subscribeAlways<GroupMessageEvent> { event -> - if (event.message.content.contains("你好")) { - reply("你好!") - } else if (event.message.content.contains("你好")) { - File("C:\\image.png").sendAsImage() - } -} - -bot.subscribeAlways<MemberPermissionChangedEvent> { event -> - if (event.kind == BECOME_OPERATOR) - reply("${event.member.id} 成为了管理员") -} -``` diff --git a/docs/guide_subscribe_events.md b/docs/guide_subscribe_events.md deleted file mode 100644 index 40f8587a3..000000000 --- a/docs/guide_subscribe_events.md +++ /dev/null @@ -1,111 +0,0 @@ -# Mirai Guide - Subscribe Events - -由于Mirai项目在快速推进中,因此内容时有变动,本文档的最后更新日期为`2020-04-01`,对应版本`0.31.4` - -本页面采用Kotlin作为开发语言,**若你希望使用 Java 开发**, 请参阅: [mirai-japt](https://github.com/mamoe/mirai-japt) - -本页面是[Mirai Guide - Getting Started](/docs/guide_getting_started.md)的后续Guide - -## 消息事件-Message Event - -首先我们来回顾上一个Guide的源码 - -```kotlin -suspend fun main() { - val qqId = 10000L//Bot的QQ号,需为Long类型,在结尾处添加大写L - val password = "your_password"//Bot的密码 - val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录 - miraiBot.subscribeMessages { - "你好" reply "你好!" - case("at me") { - reply(sender.at() + " 给爷爬 ") - } - - (contains("舔") or contains("刘老板")) { - "刘老板太强了".reply() - } - } - miraiBot.join() // 等待 Bot 离线, 避免主线程退出 -} -``` - -在本例中,`miraiBot`是一个Bot对象,让其登录,然后对`Message Event`进行了监听。 - -对于`Message Event`,`Mirai`提供了较其他Event更强大的[MessageSubscribersBuilder](https://github.com/mamoe/mirai/wiki/mirai-core-api/src/commonMain/kotlin/event/MessageSubscribers.kt#L140),本例也采用了[MessageSubscribersBuilder](https://github.com/mamoe/mirai/wiki/mirai-core-api/src/commonMain/kotlin/event/MessageSubscribers.kt#L140)。其他具体使用方法可以参考[Wiki:Message Event](https://github.com/mamoe/mirai/wiki/Development-Guide---Kotlin#Message-Event)部分。 - -## 事件-Event - -上一节中提到的`Message Event`仅仅是众多`Event`的这一种,其他`Event`有:群员加入群,离开群,私聊等等... - -具体事件文档暂不提供,可翻阅源码[**BotEvents.kt**](https://github.com/mamoe/mirai/blob/master/mirai-core-api/src/commonMain/kotlin/event/events/BotEvents.kt),查看注释。当前事件仍在扩充中,可能有一定不足。 - -下面我们开始示例对一些事件进行监听。 - -## 尝试监听事件-Try Subscribing Events - -### 监听加群事件 - -在代码中的`miraiBot.join()`前添加 - -```kotlin -miraiBot.subscribeAlways<MemberJoinEvent> { - it.group.sendMessage(PlainText("欢迎 ${it.member.nameCardOrNick} 加入本群!")) -} -``` - -本段语句监听了加入群的事件。 - -### 监听禁言事件 - -在代码中添加 - -```kotlin -miraiBot.subscribeAlways<MemberMuteEvent> { - it.group.sendMessage(PlainText("恭喜老哥 ${it.member.nameCardOrNick} 喜提禁言套餐一份")) -} -``` - -在被禁言后,Bot将发送恭喜语句。 - -### 添加后的可执行代码 - -至此,当前的代码为 - -```kotlin -package net.mamoe.mirai.simpleloader - -import kotlinx.coroutines.* -import net.mamoe.mirai.Bot -import net.mamoe.mirai.alsoLogin -import net.mamoe.mirai.contact.nameCardOrNick -import net.mamoe.mirai.event.events.MemberJoinEvent -import net.mamoe.mirai.event.events.MemberMuteEvent -import net.mamoe.mirai.event.subscribeAlways -import net.mamoe.mirai.event.subscribeMessages -import net.mamoe.mirai.message.data.PlainText - -suspend fun main() { - val qqId = 10000L//Bot的QQ号,需为Long类型,在结尾处添加大写L - val password = "your_password"//Bot的密码 - val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录 - miraiBot.subscribeMessages { - "你好" reply "你好!" - case("at me") { - reply(sender.at() + " 给爷爬 ") - } - - (contains("舔") or contains("刘老板")) { - "刘老板太强了".reply() - } - } - miraiBot.subscribeAlways<MemberJoinEvent> { - it.group.sendMessage(PlainText("欢迎 ${it.member.nameCardOrNick} 加入本群!")) - } - miraiBot.subscribeAlways<MemberMuteEvent> { - it.group.sendMessage(PlainText("恭喜老哥 ${it.member.nameCardOrNick} 喜提禁言套餐一份")) - } - miraiBot.join() // 等待 Bot 离线, 避免主线程退出 -} -``` - -下面可以参阅[Mirai Guide - Build For Mirai](/docs/guide_build_for_mirai.md),对你的Mirai应用进行打包 diff --git a/docs/mirai-ecology.md b/docs/mirai-ecology.md index 6d904cc34..f836fde30 100644 --- a/docs/mirai-ecology.md +++ b/docs/mirai-ecology.md @@ -132,3 +132,5 @@ mirai-console-loader 应运而生,它的工作就是简化 console 启动流 以上就是整个 Mirai 生态的概览,如有疏漏或错误,欢迎提出 Issue 修正。 实体关系图采用 [Mermaid](https://github.com/mermaid-js/mermaid) 绘制。 + +> [回到 Mirai 文档索引](README.md) \ No newline at end of file diff --git a/docs/mirai.md b/docs/mirai.md index 80691a490..5f174b869 100644 --- a/docs/mirai.md +++ b/docs/mirai.md @@ -26,7 +26,7 @@ mirai 项目整体由 核心 (`mirai-core`) 与 控制台(`mirai-console`) 组 `mirai-core` 设计为一个 **`支持库`**, 意味着它可以被独立依赖, 在任意项目中使用. 详见下文. -- `mirai-serialization` 依赖 `mirai-core`, 是 mirai-core 的序列化支持模块. 提供 `Message` 类型的序列化支持与相关 [mirai 码](mirai-code-specification.md) 支持. +- `mirai-serialization` 依赖 `mirai-core`, 是 mirai-core 的序列化支持模块. 提供 `Message` 类型的序列化支持与相关 [mirai 码](MiraiCodeSepecification.md) 支持. 此模块自 mirai `1.1.0` 起可用, 引用方法同 `mirai-core`. - [`mirai-console`](https://github.com/mamoe/mirai-console) 是基于 `mirai-core` 的, 支持插件加载, 指令系统, 和配置等的**控制台框架**. diff --git a/docs/src/Contacts.mermaid.md b/docs/src/Contacts.mermaid.md new file mode 100644 index 000000000..8dc40a7b9 --- /dev/null +++ b/docs/src/Contacts.mermaid.md @@ -0,0 +1,74 @@ +```mermaid +classDiagram + +class Bot { + +friends: ContactList + +groups: ContactList + +getFriend(Long) Friend? + +getFriendOrNull(Long) Friend + +getGroup(Long) Group? + +getGroupOrFail(Long) Group + +login() + +close() +} + +class ContactOrBot { + +id: Int +} + +class UserOrBot { + +nudge() Nudge +} + +class Contact { + +bot: Bot + +sendMessage(Message) MessageReceipt + +sendMessage(String) MessageReceipt + +uploadImage(ExternalImage) Image +} + +class User { + +nick: String + +remark: String + +avatarUrl: String +} + +class Group { + +members: ContactList + +name: String + +settings: GroupSettings + +owner: NormalMember + +botMuteRemaining: Long + +botPermission: MemberPermission + +quit() Boolean + +uploadVoice() Voice +} + +class NormalMember { + +mute() + +kick() +} + +class AnonymousMember { + +anonymousId: String +} + +class Member { + +group: Group +} + +ContactOrBot<|--Contact +ContactOrBot<|--UserOrBot + +UserOrBot<|--Bot +UserOrBot<|--User + +Contact<|--User +Contact<|--Group + +User<|--Member +User<|--Friend + +Member<|--NormalMember +Member<|--AnonymousMember +``` \ No newline at end of file diff --git a/docs/src/Messages.mermaid.md b/docs/src/Messages.mermaid.md new file mode 100644 index 000000000..63d1a500e --- /dev/null +++ b/docs/src/Messages.mermaid.md @@ -0,0 +1,47 @@ +```mermaid +classDiagram + +class MessageChain +MessageChain : List~SingleMessage~ + +Message<|--MessageChain +Message<|--SingleMessage + +MessageChain o-- SingleMessage + +SingleMessage<|--MessageContent +SingleMessage<|--MessageMetadata + + +%%% + + +MessageMetadata<|--QuoteReply +MessageMetadata<|--MessageSource + + +%% + +MessageSource<|--OnlineMessageSource +MessageSource<|--OfflineMessageSource + +MessageContent<|--PlainText +MessageContent<|--Image +MessageContent<|--At +MessageContent<|--AtAll +MessageContent<|--Face +MessageContent<|--ForwardMessage + +MessageContent<|--HummerMessage +HummerMessage<|--PokeMessage +HummerMessage<|--VipFace +HummerMessage<|--FlashImage + +MessageContent<|--RichMessage +RichMessage<|--ServiceMessage +RichMessage<|--LightApp + + +MessageContent<|--PttMessage +PttMessage<|--Voice +``` \ No newline at end of file diff --git a/mirai-core-all/build.gradle.kts b/mirai-core-all/build.gradle.kts index 40b26b4e0..b5287d129 100644 --- a/mirai-core-all/build.gradle.kts +++ b/mirai-core-all/build.gradle.kts @@ -24,6 +24,7 @@ description = "Mirai core shadowed" dependencies { api(project(":mirai-core")) api(project(":mirai-core-api")) + api(project(":mirai-core-utils")) } configurePublishing("mirai-core-all") \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/Bot.kt b/mirai-core-api/src/commonMain/kotlin/Bot.kt index 35a5dd8d3..2432b887d 100644 --- a/mirai-core-api/src/commonMain/kotlin/Bot.kt +++ b/mirai-core-api/src/commonMain/kotlin/Bot.kt @@ -24,8 +24,7 @@ import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.PlannedRemoval -import java.util.* -import kotlin.NoSuchElementException +import java.util.concurrent.ConcurrentHashMap /** * 登录, 返回 [this] @@ -153,14 +152,14 @@ public interface Bot : CoroutineScope, ContactOrBot, UserOrBot { public companion object { @Suppress("ObjectPropertyName") - internal val _instances: WeakHashMap<Long, Bot> = WeakHashMap() + internal val _instances: ConcurrentHashMap<Long, Bot> = ConcurrentHashMap() /** * 复制一份此时的 [Bot] 实例列表. */ @JvmStatic public val instances: List<Bot> - get() = _instances.values.filterNotNull() + get() = _instances.values.toList() /** * 复制一份此时的 [Bot] 实例列表. diff --git a/mirai-core-api/src/commonMain/kotlin/contact/AnonymousMember.kt b/mirai-core-api/src/commonMain/kotlin/contact/AnonymousMember.kt new file mode 100644 index 000000000..65202c36c --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/contact/AnonymousMember.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.contact + +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.utils.MemberDeprecatedApi + +/** + * 匿名 + * + * 代表匿名群成员 + */ +public interface AnonymousMember : Member { + /** 该匿名群成员 ID */ + public val anonymousId: String + + @MemberDeprecatedApi(message = "无法发送信息至 AnonymousMember") + @Deprecated(level = DeprecationLevel.ERROR, message = "无法发送信息至 AnonymousMember") + public override suspend fun sendMessage(message: Message): Nothing + + @Deprecated(level = DeprecationLevel.ERROR, message = "无法发送信息至 AnonymousMember") + @MemberDeprecatedApi(message = "无法发送信息至 AnonymousMember") + public override suspend fun sendMessage(message: String): Nothing + +} diff --git a/mirai-core-api/src/commonMain/kotlin/contact/Group.kt b/mirai-core-api/src/commonMain/kotlin/contact/Group.kt index 04f38c63e..a8522111a 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/Group.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/Group.kt @@ -53,13 +53,13 @@ public interface Group : Contact, CoroutineScope { * * @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员 */ - public val owner: Member + public val owner: NormalMember /** * [Bot] 在群内的 [Member] 实例 */ @MiraiExperimentalApi - public val botAsMember: Member + public val botAsMember: NormalMember /** * 机器人被禁言还剩余多少秒 @@ -89,14 +89,14 @@ public interface Group : Contact, CoroutineScope { * * 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新. */ - public val members: ContactList<Member> + public val members: ContactList<NormalMember> /** * 获取群成员实例. 不存在时返回 `null`. * * 当 [id] 为 [Bot.id] 时返回 [botAsMember]. */ - public operator fun get(id: Long): Member? + public operator fun get(id: Long): NormalMember? @Deprecated("Use get", ReplaceWith("get(id)")) @PlannedRemoval("2.0-M2") @@ -104,14 +104,14 @@ public interface Group : Contact, CoroutineScope { * 获取群成员实例, 不存在则 null * 当 [id] 为 [Bot.id] 时返回 [botAsMember] */ - public fun getOrNull(id: Long): Member? = get(id) + public fun getOrNull(id: Long): NormalMember? = get(id) /** * 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException]. * * 当 [id] 为 [Bot.id] 时返回 [botAsMember]. */ - public fun getOrFail(id: Long): Member = + public fun getOrFail(id: Long): NormalMember = get(id) ?: throw NoSuchElementException("member $id not found in group ${this.id}") @@ -125,7 +125,7 @@ public interface Group : Contact, CoroutineScope { /** * 当 [member] 是本群成员时返回 `true`. 将同时成员 [所属群][Member.group]. 同一个用户在不同群内的 [Member] 对象不相等. */ - public operator fun contains(member: Member): Boolean = member in members + public operator fun contains(member: NormalMember): Boolean = member in members /** diff --git a/mirai-core-api/src/commonMain/kotlin/contact/Member.kt b/mirai-core-api/src/commonMain/kotlin/contact/Member.kt index 3d72160d3..da1144716 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/Member.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/Member.kt @@ -12,33 +12,29 @@ package net.mamoe.mirai.contact import net.mamoe.kjbb.JvmBlockingBridge -import net.mamoe.mirai.Bot -import net.mamoe.mirai.JavaFriendlyAPI -import net.mamoe.mirai.event.events.* +import net.mamoe.mirai.event.events.BotMuteEvent +import net.mamoe.mirai.event.events.MemberMuteEvent +import net.mamoe.mirai.event.events.MemberPermissionChangeEvent import net.mamoe.mirai.message.MessageReceipt -import net.mamoe.mirai.message.MessageReceipt.Companion.recall import net.mamoe.mirai.message.action.MemberNudge -import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.message.data.isContentEmpty -import net.mamoe.mirai.message.data.toPlainText +import net.mamoe.mirai.utils.MemberDeprecatedApi import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.PlannedRemoval import net.mamoe.mirai.utils.WeakRefProperty -import kotlin.time.Duration -import kotlin.time.ExperimentalTime /** * 代表一位群成员. * - * 群成员可能也是好友, 但他们在对象类型上不同. - * 群成员可以通过 [asFriend] 得到相关好友对象. + * 群成员分为 [普通成员][NormalMember] 和 [匿名成员][AnonymousMember] * - * ## 与好友相关的操作 + * 一个群成员可能也是机器人的好友, 但他们在对象类型上不同 ([Member] != [Friend]). 可以通过 [Member.asFriend] 得到相关好友对象. + * + * ## 相关的操作 * [Member.isFriend] 判断此成员是否为好友 + * [Member.isAnonymous] 判断此成员是否为匿名群成员 + * [Member.isNormal] 判断此成员是否为正常群成员 */ -@Suppress("INAPPLICABLE_JVM_NAME", "EXPOSED_SUPER_CLASS") -@OptIn(JavaFriendlyAPI::class) public interface Member : User { /** * 所在的群. @@ -56,125 +52,62 @@ public interface Member : User { /** * 群名片. 可能为空. * - * 管理员和群主都可修改任何人(包括群主)的群名片. - * - * 在修改时将会异步上传至服务器. - * - * @see [nameCardOrNick] 获取非空群名片或昵称 - * - * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. - * @throws PermissionDeniedException 无权限修改时 + * @see [NormalMember.nameCard] + * @see [AnonymousMember.nameCard] */ - public var nameCard: String + public val nameCard: String /** * 群头衔. * - * 仅群主可以修改群头衔. + * 为 [AnonymousMember] 时一定是 `"匿名"` * - * 在修改时将会异步上传至服务器. - * - * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. - * @throws PermissionDeniedException 无权限修改时 + * @see [NormalMember.specialTitle] */ - public var specialTitle: String + public val specialTitle: String - /** - * 被禁言剩余时长. 单位为秒. - * - * @see isMuted 判断改成员是否处于禁言状态 - * @see mute 设置禁言 - * @see unmute 取消禁言 - */ + @MemberDeprecatedApi("仅 NormalMember 支持 muteTimeRemaining. 请先检查类型为 NormalMember.") + @PlannedRemoval("2.0-M2") public val muteTimeRemaining: Int /** - * 禁言. + * 禁言这个群成员 [durationSeconds] 秒, 在机器人无权限操作时抛出 [PermissionDeniedException]. * - * QQ 中最小操作和显示的时间都是一分钟. - * 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间. + * QQ 中最小操作和显示的时间都是一分钟. 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间. * * 管理员可禁言成员, 群主可禁言管理员和群员. * - * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. - * @return 机器人无权限时返回 `false` + * @param durationSeconds 持续时间. 精确到秒. 最短 0 秒, 最长 30 天. 超过范围则会抛出异常 [IllegalStateException]. * - * @see Member.isMuted 判断此成员是否正处于禁言状态中 - * @see unmute 取消禁言此成员 - * - * @see Int.minutesToSeconds - * @see Int.hoursToSeconds - * @see Int.daysToSeconds + * @see NormalMember.isMuted 判断此成员是否正处于禁言状态中 + * @see NormalMember.unmute 取消禁言此成员 * * @see MemberMuteEvent 成员被禁言事件 + * @see BotMuteEvent Bot 被禁言事件 * - * @throws PermissionDeniedException 无权限修改时抛出 + * @see Member.mute 支持 Kotlin [kotlin.time.Duration] 的扩展 */ @JvmBlockingBridge public suspend fun mute(durationSeconds: Int) - /** - * 解除禁言. - * - * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. - * - * @see Member.isMuted 判断此成员是否正处于禁言状态中 - * - * @see MemberUnmuteEvent 成员被取消禁言事件 - * - * @throws PermissionDeniedException 无权限修改时抛出 - */ - @JvmBlockingBridge + @MemberDeprecatedApi("仅 NormalMember 支持 unmute. 请先检查类型为 NormalMember.") + @PlannedRemoval("2.0-M2") public suspend fun unmute() - /** - * 踢出该成员. - * - * 管理员可踢出成员, 群主可踢出管理员和群员. - * - * @see MemberLeaveEvent.Kick 成员被踢出事件. - * @throws PermissionDeniedException 无权限修改时 - */ - @JvmBlockingBridge + @MemberDeprecatedApi("仅 NormalMember 支持 kick. 请先检查类型为 NormalMember.") + @PlannedRemoval("2.0-M2") public suspend fun kick(message: String = "") - /** - * 向群成员发送消息. - * 若群成员同时是好友, 则会发送好友消息. 否则发送临时会话消息. - * - * 单条消息最大可发送 4500 字符或 50 张图片. - * - * @see FriendMessagePreSendEvent 当此成员是好友时发送消息前事件 - * @see FriendMessagePostSendEvent 当此成员是好友时发送消息后事件 - * - * @see TempMessagePreSendEvent 当此成员不是好友时发送消息前事件 - * @see TempMessagePostSendEvent 当此成员不是好友时发送消息后事件 - * - * @throws EventCancelledException 当发送消息事件被取消时抛出 - * @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出 - * @throws MessageTooLargeException 当消息过长时抛出 - * @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty]) - * - * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) - */ - @JvmBlockingBridge + @MemberDeprecatedApi("仅 NormalMember 支持 sendMessage. 请先检查类型为 NormalMember.") public override suspend fun sendMessage(message: Message): MessageReceipt<Member> - /** - * 发送纯文本消息 - * @see sendMessage - */ - @JvmBlockingBridge - public override suspend fun sendMessage(message: String): MessageReceipt<Member> = - this.sendMessage(message.toPlainText()) + @MemberDeprecatedApi("仅 NormalMember 支持 sendMessage. 请先检查类型为 NormalMember.") + public override suspend fun sendMessage(message: String): MessageReceipt<Member> - /** - * 创建一个 "戳一戳" 消息 - * - * @see Nudge.sendTo 发送这个戳一戳消息 - */ + @MemberDeprecatedApi("仅 NormalMember 支持 nudge. 请先检查类型为 NormalMember.") + @PlannedRemoval("2.0-M2") @MiraiExperimentalApi - public override fun nudge(): MemberNudge = MemberNudge(this) + public override fun nudge(): MemberNudge } /** @@ -200,7 +133,8 @@ public inline val Member.isFriend: Boolean */ @Deprecated( "Ambiguous function name and its behaviour. Use asFriendOrNull and let manually.", - ReplaceWith("this.asFriendOrNull()?.let(block)") + ReplaceWith("this.asFriendOrNull()?.let(block)"), + level = DeprecationLevel.ERROR ) @PlannedRemoval("2.0-M2") public inline fun <R> Member.takeIfIsFriend(block: (Friend) -> R): R? { @@ -213,39 +147,5 @@ public inline fun <R> Member.takeIfIsFriend(block: (Friend) -> R): R? { * 若 [群名片][Member.nameCard] 不为空则返回群名片, 为空则返回 [User.nick] */ public val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick - -/** - * 获取非空群名片或昵称. - * - * @return 当 [User] 为 [Member] 时返回 [Member.nameCardOrNick] - * - * 否则返回 [Member.nick] - */ -public val User.nameCardOrNick: String - get() = when (this) { - is Member -> this.nameCardOrNick - else -> this.nick - } - -/** - * 判断群成员是否处于禁言状态. - */ -public val Member.isMuted: Boolean - get() = muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt() - -/** - * @see Member.mute - */ -@ExperimentalTime -public suspend inline fun Member.mute(duration: Duration) { - require(duration.inDays <= 30) { "duration must be at most 1 month" } - require(duration.inSeconds > 0) { "duration must be greater than 0 second" } - this.mute(duration.inSeconds.toInt()) -} - -/** - * @see Member.mute - */ -@Deprecated("Convert duration to int manually.", ReplaceWith("this.mute(durationSeconds.toInt())")) -@PlannedRemoval("2.0-M2") -public suspend inline fun Member.mute(durationSeconds: Long): Unit = this.mute(durationSeconds.toInt()) \ No newline at end of file +public val Member.isNormal: Boolean get() = this is NormalMember +public val Member.isAnonymous: Boolean get() = this is AnonymousMember diff --git a/mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt b/mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt new file mode 100644 index 000000000..b0c7d4eb7 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt @@ -0,0 +1,173 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.contact + +import net.mamoe.kjbb.JvmBlockingBridge +import net.mamoe.mirai.Bot +import net.mamoe.mirai.event.events.* +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.MessageReceipt.Companion.recall +import net.mamoe.mirai.message.action.MemberNudge +import net.mamoe.mirai.message.action.Nudge +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.data.isContentEmpty +import net.mamoe.mirai.message.data.toPlainText +import net.mamoe.mirai.utils.MemberDeprecatedApi +import net.mamoe.mirai.utils.MiraiExperimentalApi +import net.mamoe.mirai.utils.PlannedRemoval +import kotlin.time.Duration +import kotlin.time.ExperimentalTime + +/** + * 代表一位正常的群成员. + * + * 群成员可能也是好友, 但他们在对象类型上不同. + * 群成员可以通过 [asFriend] 得到相关好友对象. + * + * ## 相关的操作 + * [Member.isFriend] 判断此成员是否为好友 + */ +@OptIn(MemberDeprecatedApi::class) +public interface NormalMember : Member { + /** + * 群名片. 可能为空. + * + * 管理员和群主都可修改任何人(包括群主)的群名片. + * + * 在修改时将会异步上传至服务器. + * + * @see [nameCardOrNick] 获取非空群名片或昵称 + * + * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. + * @throws PermissionDeniedException 无权限修改时 + */ + public override var nameCard: String + + /** + * 群头衔. + * + * 仅群主可以修改群头衔. + * + * 在修改时将会异步上传至服务器. + * + * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. + * @throws PermissionDeniedException 无权限修改时 + */ + public override var specialTitle: String + + /** + * 被禁言剩余时长. 单位为秒. + * + * @see isMuted 判断改成员是否处于禁言状态 + * @see mute 设置禁言 + * @see unmute 取消禁言 + */ + public override val muteTimeRemaining: Int + + /** + * 解除禁言. + * + * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. + * + * @see Member.isMuted 判断此成员是否正处于禁言状态中 + * + * @see MemberUnmuteEvent 成员被取消禁言事件 + * + * @throws PermissionDeniedException 无权限修改时抛出 + */ + @JvmBlockingBridge + public override suspend fun unmute() + + /** + * 踢出该成员. + * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * + * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmBlockingBridge + public override suspend fun kick(message: String) + + /** + * 向群成员发送消息. + * 若群成员同时是好友, 则会发送好友消息. 否则发送临时会话消息. + * + * 单条消息最大可发送 4500 字符或 50 张图片. + * + * @see FriendMessagePreSendEvent 当此成员是好友时发送消息前事件 + * @see FriendMessagePostSendEvent 当此成员是好友时发送消息后事件 + * + * @see TempMessagePreSendEvent 当此成员不是好友时发送消息前事件 + * @see TempMessagePostSendEvent 当此成员不是好友时发送消息后事件 + * + * @throws EventCancelledException 当发送消息事件被取消时抛出 + * @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出 + * @throws MessageTooLargeException 当消息过长时抛出 + * @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty]) + * + * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) + */ + @JvmBlockingBridge + public override suspend fun sendMessage(message: Message): MessageReceipt<Member> + + /** + * 发送纯文本消息 + * @see sendMessage + */ + @JvmBlockingBridge + public override suspend fun sendMessage(message: String): MessageReceipt<Member> = + this.sendMessage(message.toPlainText()) + + /** + * 创建一个 "戳一戳" 消息 + * + * @see Nudge.sendTo 发送这个戳一戳消息 + */ + @MiraiExperimentalApi + public override fun nudge(): MemberNudge = MemberNudge(this) +} + + +/** + * 获取非空群名片或昵称. + * + * @return 当 [User] 为 [Member] 时返回 [Member.nameCardOrNick] + * + * 否则返回 [Member.nick] + */ +public val User.nameCardOrNick: String + get() = when (this) { + is NormalMember -> this.nameCardOrNick + else -> this.nick + } + +/** + * 判断群成员是否处于禁言状态. + */ +public val NormalMember.isMuted: Boolean + get() = muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt() + +/** + * @see Member.mute + */ +@ExperimentalTime +public suspend inline fun NormalMember.mute(duration: Duration) { + require(duration.inDays <= 30) { "duration must be at most 1 month" } + require(duration.inSeconds > 0) { "duration must be greater than 0 second" } + this.mute(duration.inSeconds.toInt()) +} + +/** + * @see Member.mute + */ +@Deprecated("Convert duration to int manually.", ReplaceWith("this.mute(durationSeconds.toInt())")) +@PlannedRemoval("2.0-M2") +public suspend inline fun NormalMember.mute(durationSeconds: Long): Unit = this.mute(durationSeconds.toInt()) diff --git a/mirai-core-api/src/commonMain/kotlin/event/JvmMethodListeners.kt b/mirai-core-api/src/commonMain/kotlin/event/JvmMethodListeners.kt index 44ee3837e..4c38c0dc9 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/JvmMethodListeners.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/JvmMethodListeners.kt @@ -14,6 +14,7 @@ package net.mamoe.mirai.event import kotlinx.coroutines.* +import net.mamoe.mirai.utils.EventListenerLikeJava import java.lang.reflect.Method import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -238,6 +239,23 @@ public fun CoroutineScope.registerEvents( } } +private fun Method.isKotlinFunction(): Boolean { + + if (getDeclaredAnnotation(EventListenerLikeJava::class.java) != null) return false + if (declaringClass.getDeclaredAnnotation(EventListenerLikeJava::class.java) != null) return false + + @Suppress("RemoveRedundantQualifierName") // for strict + return declaringClass.getDeclaredAnnotation(kotlin.Metadata::class.java) != null +} + +private fun Method.invokeWithErrorReport(self: Any?, vararg args: Any?): Any? = try { + invoke(self, *args) +} catch (exception: IllegalArgumentException) { + throw IllegalArgumentException( + "Internal Error: $exception, method=${this}, this=$self, arguments=$args, please report to https://github.com/mamoe/mirai", + exception + ) +} @Suppress("UNCHECKED_CAST") private fun Method.registerEvent( @@ -248,7 +266,7 @@ private fun Method.registerEvent( ): Listener<Event> { this.isAccessible = true val kotlinFunction = kotlin.runCatching { this.kotlinFunction }.getOrNull() - return if (kotlinFunction != null) { + return if (kotlinFunction != null && isKotlinFunction()) { // kotlin functions val param = kotlinFunction.parameters @@ -337,7 +355,7 @@ private fun Method.registerEvent( val paramType = this.parameters[0].type check(this.parameterCount == 1 && Event::class.java.isAssignableFrom(paramType)) { - "Illegal method parameter. Required one exact Event subclass. found $paramType" + "Illegal method parameter. Required one exact Event subclass. found ${this.parameters.contentToString()}" } when (this.returnType) { Void::class.java, Void.TYPE, Nothing::class.java -> { @@ -350,11 +368,11 @@ private fun Method.registerEvent( if (annotation.ignoreCancelled) { if ((this as? CancellableEvent)?.isCancelled != true) { withContext(Dispatchers.IO) { - this@registerEvent.invoke(owner, this) + this@registerEvent.invokeWithErrorReport(owner, this@subscribeAlways) } } } else withContext(Dispatchers.IO) { - this@registerEvent.invoke(owner, this) + this@registerEvent.invokeWithErrorReport(owner, this@subscribeAlways) } } } @@ -368,11 +386,11 @@ private fun Method.registerEvent( if (annotation.ignoreCancelled) { if ((this as? CancellableEvent)?.isCancelled != true) { withContext(Dispatchers.IO) { - this@registerEvent.invoke(owner, this) as ListeningStatus + this@registerEvent.invokeWithErrorReport(owner, this@subscribe) as ListeningStatus } } else ListeningStatus.LISTENING } else withContext(Dispatchers.IO) { - this@registerEvent.invoke(owner, this) as ListeningStatus + this@registerEvent.invokeWithErrorReport(owner, this@subscribe) as ListeningStatus } } diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/README.md b/mirai-core-api/src/commonMain/kotlin/event/events/README.md index 02167d9ce..9cf4e000a 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/README.md +++ b/mirai-core-api/src/commonMain/kotlin/event/events/README.md @@ -25,9 +25,10 @@ - (`1.3.0+`) Bot 被戳: BotNudgedEvent ### [消息](message.kt) -- (`1.1.0-`) 主动发送消息: MessageSendEvent - - 群消息: GroupMessageSendEvent - - 好友消息: FriendMessageSendEvent +- 被动收到消息:MessageEvent + - 群消息:GroupMessageEvent + - 好友消息:FriendMessageEvent + - 群临时会话消息:TempMessageEvent - (`1.1.0+`) 主动发送消息前: MessagePreSendEvent - 群消息: GroupMessagePreSendEvent - 好友消息: FriendMessagePreSendEvent @@ -44,6 +45,9 @@ - 图片上传完成: ImageUploadEvent - 图片上传成功: Succeed - 图片上传失败: Failed +- (`1.1.0-`) ~~主动发送消息: MessageSendEvent~~ + - ~~群消息: GroupMessageSendEvent~~ + - ~~好友消息: FriendMessageSendEvent~~ ### [群](group.kt) - 机器人被踢出群或在其他客户端主动退出一个群: BotLeaveEvent @@ -60,7 +64,7 @@ - 入群公告改变: GroupEntranceAnnouncementChangeEvent - 全员禁言状态改变: GroupMuteAllEvent - 匿名聊天状态改变: GroupAllowAnonymousChatEvent - - 坦白说状态改变: GroupAllowConfessTalkEvent + - (`1.3.0-`) ~~坦白说状态改变: GroupAllowConfessTalkEvent~~ - 允许群员邀请好友加群状态改变: GroupAllowMemberInviteEvent #### 群成员 diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/group.kt b/mirai-core-api/src/commonMain/kotlin/event/events/group.kt index 19476157f..a88927f8e 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/group.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/group.kt @@ -16,10 +16,7 @@ package net.mamoe.mirai.event.events import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai -import net.mamoe.mirai.contact.Friend -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.Member -import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.internal.network.Packet @@ -245,14 +242,14 @@ public data class GroupAllowMemberInviteEvent internal constructor( * 成员已经加入群的事件 */ public sealed class MemberJoinEvent( - public override val member: Member + public override val member: NormalMember ) : GroupMemberEvent, BotPassiveEvent, Packet, AbstractEvent() { /** * 被邀请加入群 */ public data class Invite internal constructor( - public override val member: Member + public override val member: NormalMember ) : MemberJoinEvent(member) { public override fun toString(): String = "MemberJoinEvent.Invite(member=${member.id})" } @@ -261,7 +258,7 @@ public sealed class MemberJoinEvent( * 成员主动加入群 */ public data class Active internal constructor( - public override val member: Member + public override val member: NormalMember ) : MemberJoinEvent(member) { public override fun toString(): String = "MemberJoinEvent.Active(member=${member.id})" } @@ -271,7 +268,7 @@ public sealed class MemberJoinEvent( * 此时 [member] 的 [Member.permission] 肯定是 [MemberPermission.OWNER] */ public data class Retrieve internal constructor( - public override val member: Member + public override val member: NormalMember ) : MemberJoinEvent(member) { override fun toString(): String = "MemberJoinEvent.Retrieve(member=${member.id})" } @@ -285,11 +282,11 @@ public sealed class MemberLeaveEvent : GroupMemberEvent, AbstractEvent() { * 成员被踢出群. 成员不可能是机器人自己. */ public data class Kick( - public override val member: Member, + public override val member: NormalMember, /** * 操作人. 为 null 则是机器人操作. */ - public override val operator: Member? + public override val operator: NormalMember? ) : MemberLeaveEvent(), Packet, GroupOperableEvent { public override fun toString(): String = "MemberLeaveEvent.Kick(member=${member.id}, operator=${operator?.id})" } @@ -298,7 +295,7 @@ public sealed class MemberLeaveEvent : GroupMemberEvent, AbstractEvent() { * 成员主动离开 */ public data class Quit( - public override val member: Member + public override val member: NormalMember ) : MemberLeaveEvent(), Packet { public override fun toString(): String = "MemberLeaveEvent.Quit(member=${member.id})" } @@ -439,7 +436,7 @@ public data class MemberSpecialTitleChangeEvent internal constructor( * 不为 null 时一定为群主. 可能与 [member] 引用相同, 此时为群员自己修改. * 为 null 时则是机器人操作. */ - public override val operator: Member? + public override val operator: NormalMember? ) : GroupMemberEvent, GroupOperableEvent, AbstractEvent() // endregion diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/message.kt b/mirai-core-api/src/commonMain/kotlin/event/events/message.kt index 7d98d7e4a..2cc433812 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/message.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/message.kt @@ -440,7 +440,7 @@ public class FriendMessageEvent constructor( public override val sender: Friend, public override val message: MessageChain, public override val time: Int -) : AbstractEvent(), MessageEvent, MessageEventExtensions<User, Contact>, BroadcastControllable, FriendEvent { +) : AbstractMessageEvent(), MessageEvent, MessageEventExtensions<User, Contact>, BroadcastControllable, FriendEvent { init { val source = message[MessageSource] ?: throw IllegalArgumentException("Cannot find MessageSource from message") @@ -480,7 +480,7 @@ public class GroupMessageEvent( public override val sender: Member, public override val message: MessageChain, public override val time: Int -) : AbstractEvent(), GroupAwareMessageEvent, MessageEvent, Event, GroupEvent { +) : AbstractMessageEvent(), GroupAwareMessageEvent, MessageEvent, Event, GroupEvent { init { val source = message[MessageSource] ?: error("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromGroup) { "source provided to a GroupMessage must be an instance of OnlineMessageSource.Incoming.FromGroup" } @@ -512,7 +512,7 @@ public class TempMessageEvent( public override val sender: Member, public override val message: MessageChain, public override val time: Int -) : AbstractEvent(), GroupAwareMessageEvent, MessageEvent, BroadcastControllable { +) : AbstractMessageEvent(), GroupAwareMessageEvent, MessageEvent, BroadcastControllable { init { val source = message[MessageSource] ?: error("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromTemp) { "source provided to a TempMessage must be an instance of OnlineMessageSource.Incoming.FromTemp" } @@ -538,6 +538,75 @@ public interface UserMessageEvent : MessageEvent { public override val subject: User } +@MiraiInternalApi +public abstract class AbstractMessageEvent : MessageEvent, AbstractEvent() { + public override suspend fun reply(message: Message): MessageReceipt<Contact> = + subject.sendMessage(message.asMessageChain()) + + public override suspend fun reply(plain: String): MessageReceipt<Contact> = + subject.sendMessage(PlainText(plain).asMessageChain()) + + public override suspend fun ExternalImage.upload(): Image = this.upload(subject) + + public override suspend fun ExternalImage.send(): MessageReceipt<Contact> = this.sendTo(subject) + + public override suspend fun Image.send(): MessageReceipt<Contact> = this.sendTo(subject) + + public override suspend fun Message.send(): MessageReceipt<Contact> = this.sendTo(subject) + + public override suspend fun String.send(): MessageReceipt<Contact> = PlainText(this).sendTo(subject) + + // region 引用回复 + /** + * 给这个消息事件的主体发送引用回复消息 + * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 + * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 + */ + public override suspend fun quoteReply(message: MessageChain): MessageReceipt<Contact> = + reply(this.message.quote() + message) + + public override suspend fun quoteReply(message: Message): MessageReceipt<Contact> = + reply(this.message.quote() + message) + + public override suspend fun quoteReply(plain: String): MessageReceipt<Contact> = reply(this.message.quote() + plain) + + public override fun At.isBot(): Boolean = target == bot.id + + + /** + * 获取图片下载链接 + * @return "http://gchat.qpic.cn/gchatpic_new/..." + */ + public override suspend fun Image.url(): String = this@url.queryUrl() + + + // region 上传图片 + + public override suspend fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image) + public override suspend fun uploadImage(image: InputStream): Image = subject.uploadImage(image) + public override suspend fun uploadImage(image: File): Image = subject.uploadImage(image) + // endregion + + // region 发送图片 + public override suspend fun sendImage(image: BufferedImage): MessageReceipt<Contact> = subject.sendImage(image) + public override suspend fun sendImage(image: InputStream): MessageReceipt<Contact> = subject.sendImage(image) + public override suspend fun sendImage(image: File): MessageReceipt<Contact> = subject.sendImage(image) + // endregion + + // region 上传图片 (扩展) + public override suspend fun BufferedImage.upload(): Image = upload(subject) + public override suspend fun InputStream.uploadAsImage(): Image = uploadAsImage(subject) + public override suspend fun File.uploadAsImage(): Image = uploadAsImage(subject) + // endregion 上传图片 (扩展) + + // region 发送图片 (扩展) + public override suspend fun BufferedImage.send(): MessageReceipt<Contact> = sendTo(subject) + public override suspend fun InputStream.sendAsImage(): MessageReceipt<Contact> = sendAsImageTo(subject) + public override suspend fun File.sendAsImage(): MessageReceipt<Contact> = sendAsImageTo(subject) + // endregion 发送图片 (扩展) + +} + /** * 一个 (收到的) 消息事件. * @@ -611,29 +680,27 @@ public interface MessageEventExtensions<out TSender : User, out TSubject : Conta * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 */ @JvmBlockingBridge - public suspend fun reply(message: Message): MessageReceipt<TSubject> = - subject.sendMessage(message.asMessageChain()) as MessageReceipt<TSubject> + public suspend fun reply(message: Message): MessageReceipt<TSubject> @JvmBlockingBridge - public suspend fun reply(plain: String): MessageReceipt<TSubject> = - subject.sendMessage(PlainText(plain).asMessageChain()) as MessageReceipt<TSubject> + public suspend fun reply(plain: String): MessageReceipt<TSubject> // endregion @JvmSynthetic - public suspend fun ExternalImage.upload(): Image = this.upload(subject) + public suspend fun ExternalImage.upload(): Image @JvmSynthetic - public suspend fun ExternalImage.send(): MessageReceipt<TSubject> = this.sendTo(subject) + public suspend fun ExternalImage.send(): MessageReceipt<TSubject> @JvmSynthetic - public suspend fun Image.send(): MessageReceipt<TSubject> = this.sendTo(subject) + public suspend fun Image.send(): MessageReceipt<TSubject> @JvmSynthetic - public suspend fun Message.send(): MessageReceipt<TSubject> = this.sendTo(subject) + public suspend fun Message.send(): MessageReceipt<TSubject> @JvmSynthetic - public suspend fun String.send(): MessageReceipt<TSubject> = PlainText(this).sendTo(subject) + public suspend fun String.send(): MessageReceipt<TSubject> // region 引用回复 /** @@ -642,18 +709,16 @@ public interface MessageEventExtensions<out TSender : User, out TSubject : Conta * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 */ @JvmBlockingBridge - public suspend fun quoteReply(message: MessageChain): MessageReceipt<TSubject> = - reply(this.message.quote() + message) + public suspend fun quoteReply(message: MessageChain): MessageReceipt<TSubject> @JvmBlockingBridge - public suspend fun quoteReply(message: Message): MessageReceipt<TSubject> = - reply(this.message.quote() + message) + public suspend fun quoteReply(message: Message): MessageReceipt<TSubject> @JvmBlockingBridge - public suspend fun quoteReply(plain: String): MessageReceipt<TSubject> = reply(this.message.quote() + plain) + public suspend fun quoteReply(plain: String): MessageReceipt<TSubject> @JvmSynthetic - public fun At.isBot(): Boolean = target == bot.id + public fun At.isBot(): Boolean /** @@ -661,7 +726,7 @@ public interface MessageEventExtensions<out TSender : User, out TSubject : Conta * @return "http://gchat.qpic.cn/gchatpic_new/..." */ @JvmSynthetic - public suspend fun Image.url(): String = this@url.queryUrl() + public suspend fun Image.url(): String } /** 一个消息事件在各平台的相关扩展. 请使用 [MessageEventExtensions] */ @@ -679,46 +744,46 @@ public interface MessageEventPlatformExtensions<out TSender : User, out TSubject // region 上传图片 @JvmBlockingBridge - public suspend fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image) + public suspend fun uploadImage(image: BufferedImage): Image @JvmBlockingBridge - public suspend fun uploadImage(image: InputStream): Image = subject.uploadImage(image) + public suspend fun uploadImage(image: InputStream): Image @JvmBlockingBridge - public suspend fun uploadImage(image: File): Image = subject.uploadImage(image) + public suspend fun uploadImage(image: File): Image // endregion // region 发送图片 @JvmBlockingBridge - public suspend fun sendImage(image: BufferedImage): MessageReceipt<TSubject> = subject.sendImage(image) + public suspend fun sendImage(image: BufferedImage): MessageReceipt<TSubject> @JvmBlockingBridge - public suspend fun sendImage(image: InputStream): MessageReceipt<TSubject> = subject.sendImage(image) + public suspend fun sendImage(image: InputStream): MessageReceipt<TSubject> @JvmBlockingBridge - public suspend fun sendImage(image: File): MessageReceipt<TSubject> = subject.sendImage(image) + public suspend fun sendImage(image: File): MessageReceipt<TSubject> // endregion // region 上传图片 (扩展) @JvmSynthetic - public suspend fun BufferedImage.upload(): Image = upload(subject) + public suspend fun BufferedImage.upload(): Image @JvmSynthetic - public suspend fun InputStream.uploadAsImage(): Image = uploadAsImage(subject) + public suspend fun InputStream.uploadAsImage(): Image @JvmSynthetic - public suspend fun File.uploadAsImage(): Image = uploadAsImage(subject) + public suspend fun File.uploadAsImage(): Image // endregion 上传图片 (扩展) // region 发送图片 (扩展) @JvmSynthetic - public suspend fun BufferedImage.send(): MessageReceipt<TSubject> = sendTo(subject) + public suspend fun BufferedImage.send(): MessageReceipt<TSubject> @JvmSynthetic - public suspend fun InputStream.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject) + public suspend fun InputStream.sendAsImage(): MessageReceipt<TSubject> @JvmSynthetic - public suspend fun File.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject) + public suspend fun File.sendAsImage(): MessageReceipt<TSubject> // endregion 发送图片 (扩展) } diff --git a/mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt index b6acd2bef..3353f824e 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt @@ -18,7 +18,7 @@ import net.mamoe.mirai.message.data.* * 使用 `mirai-serialization` 中 `String.parseMiraiCode()` 转回 [Message]. * * ## 规范 - * 可在 [mirai-code-specification.md](https://github.com/mamoe/mirai/blob/dev/docs/mirai-code-specification.md) 查看 mirai 码规范. + * 可在 [MiraiCodeSepecification.md](https://github.com/mamoe/mirai/blob/dev/docs/MiraiCodeSepecification.md) 查看 mirai 码规范. * * @suppress 警告: 此 API 可能在任何时刻被改变 * diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/At.kt b/mirai-core-api/src/commonMain/kotlin/message/data/At.kt index 3bdc7bd93..b44bb75dd 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/At.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/At.kt @@ -16,8 +16,10 @@ package net.mamoe.mirai.message.data import kotlinx.serialization.Serializable import net.mamoe.mirai.LowLevelApi +import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.UserOrBot +import net.mamoe.mirai.contact.nameCardOrNick import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.utils.PlannedRemoval @@ -37,6 +39,22 @@ public data class At( public override fun toString(): String = "[mirai:at:$target]" public override fun contentToString(): String = "@$target" + @Suppress("DeprecatedCallableAddReplaceWith") + @Deprecated("Use getDisplay", ReplaceWith("this.getDisplay()"), DeprecationLevel.ERROR) + @PlannedRemoval("2.0-M2") + val display: Nothing + get() = error("At.display is no longer supported") + + /** + * 获取 [At] 发送于指定 [Group] 时会显示的内容. + * + * 若 [group] 非 `null` 且包含成员 [target], 返回 `"@成员名片或昵称"`. 否则返回 `"@123456"` 其中 123456 表示 [target] + */ + public fun getDisplay(group: Group?): String { + val member = group?.get(this.target) ?: return "@$target" + return "@${member.nameCardOrNick}" + } + public companion object { /** * 构造一个 [At], 仅供内部使用, 否则可能造成消息无法发出的问题. diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt index 88b5c7585..e7f87c61f 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt @@ -17,6 +17,7 @@ import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.message.data.VipFace.Kind +import net.mamoe.mirai.utils.PlannedRemoval import net.mamoe.mirai.utils.castOrNull import net.mamoe.mirai.utils.safeCast @@ -56,7 +57,14 @@ public data class PokeMessage internal constructor( public val id: Int ) : HummerMessage(), CodableMessage { @ExperimentalMessageKey - override val key: MessageKey<HummerMessage> get() = Key + override val key: MessageKey<HummerMessage> + get() = Key + + + @PlannedRemoval("2.0-M2") + @Deprecated("Use pokeType", ReplaceWith("pokeType"), DeprecationLevel.ERROR) + val type: Int + get() = pokeType public companion object Key : AbstractPolymorphicMessageKey<HummerMessage, PokeMessage>(HummerMessage, { it.castOrNull() }) { @@ -206,7 +214,8 @@ public data class VipFace internal constructor( } @ExperimentalMessageKey - override val key: MessageKey<VipFace> get() = Key + override val key: MessageKey<VipFace> + get() = Key @Suppress("DEPRECATION_ERROR", "DEPRECATION", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") public companion object Key : diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt index ecaca5446..478529c40 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt @@ -28,6 +28,7 @@ import net.mamoe.mirai.message.data.MessageSource.Key.isAboutGroup import net.mamoe.mirai.message.data.MessageSource.Key.isAboutTemp import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.utils.LazyProperty +import net.mamoe.mirai.utils.PlannedRemoval import net.mamoe.mirai.utils.safeCast /** @@ -145,7 +146,8 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle { /** * 返回 `"[mirai:source:${ids.contentToString()},${internalIds.contentToString()}]"` */ - public final override fun toString(): String = "[mirai:source:${ids.contentToString()},${internalIds.contentToString()}]" + public final override fun toString(): String = + "[mirai:source:${ids.contentToString()},${internalIds.contentToString()}]" public companion object Key : AbstractMessageKey<MessageSource>({ it.safeCast() }) { /** @@ -397,6 +399,13 @@ public abstract class OfflineMessageSource : MessageSource() { * 消息种类 */ public abstract val kind: MessageSourceKind + + @PlannedRemoval("2.0-M2") + @Deprecated( + "Use MessageSourceKind", + ReplaceWith("MessageSourceKind", "net.mamoe.mirai.message.data.MessageSourceKind") + ) + private enum class Kind } @Serializable diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt b/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt index 9e30794ed..34c1db42f 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt @@ -15,6 +15,7 @@ package net.mamoe.mirai.message.data import kotlinx.serialization.Serializable import net.mamoe.mirai.utils.MiraiExperimentalApi +import net.mamoe.mirai.utils.PlannedRemoval import net.mamoe.mirai.utils.safeCast import kotlin.annotation.AnnotationTarget.* @@ -150,6 +151,15 @@ public interface ServiceMessage : RichMessage { public val serviceId: Int } +@Suppress("FunctionName") +@Deprecated( + "Use SimpleServiceMessage.", + ReplaceWith("SimpleServiceMessage(serviceId, content)", "net.mamoe.mirai.message.data.SimpleServiceMessage") +) +@PlannedRemoval("2.0-M2") +public fun ServiceMessage(serviceId: Int, content: String): SimpleServiceMessage = + SimpleServiceMessage(serviceId, content) + @MiraiExperimentalApi @Serializable public abstract class AbstractServiceMessage : ServiceMessage { diff --git a/mirai-core-api/src/commonMain/kotlin/utils/Annotations.kt b/mirai-core-api/src/commonMain/kotlin/utils/Annotations.kt index 995e4117b..dc3b894a3 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/Annotations.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/Annotations.kt @@ -17,7 +17,7 @@ import kotlin.annotation.AnnotationTarget.* * 这些 API 可能会在任意时刻更改, 且不会发布任何预警. * 非常不建议在发行版本中使用这些 API. */ -@Retention(AnnotationRetention.SOURCE) +@Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.ERROR) @Target( CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, @@ -34,7 +34,7 @@ public annotation class MiraiInternalApi( * 这些 API 不具有稳定性, 且可能会在任意时刻更改. * 不建议在发行版本中使用这些 API. */ -@Retention(AnnotationRetention.SOURCE) +@Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.WARNING) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @MustBeDocumented @@ -48,4 +48,24 @@ public annotation class MiraiExperimentalApi( @Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS) @Retention(AnnotationRetention.SOURCE) @MustBeDocumented -internal annotation class PlannedRemoval(val version: String) \ No newline at end of file +internal annotation class PlannedRemoval(val version: String) + +/** + * 标记已过时的 Member API + */ +@Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS) +@Retention(AnnotationRetention.BINARY) +@RequiresOptIn(level = RequiresOptIn.Level.ERROR) +@MustBeDocumented +@PlannedRemoval("2.0-M2") +internal annotation class MemberDeprecatedApi(val message: String) + +/** + * 该注解仅用于测试 EventHandler + * + * 标注了此注解的意为像处理 java 方法那样处理 kotlin 方法 + */ +@Retention(AnnotationRetention.RUNTIME) +internal annotation class EventListenerLikeJava + + diff --git a/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt b/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt index 226fe0d8e..f897fffe0 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt @@ -208,24 +208,22 @@ public open class BotConfiguration { // open for Java } @Suppress("ACTUAL_WITHOUT_EXPECT") - public enum class MiraiProtocol constructor( - /** 协议模块使用的 ID */ - @JvmField internal val id: Long - ) { + public enum class MiraiProtocol { /** * Android 手机. */ - ANDROID_PHONE(537066439), + ANDROID_PHONE, /** * Android 平板. */ - ANDROID_PAD(537062409), + ANDROID_PAD, /** * Android 手表. * */ - ANDROID_WATCH(537061176) + ANDROID_WATCH, + } public companion object { diff --git a/mirai-core-api/src/jvmTest/java/net/mamoe/mirai/event/JvmMethodEventsTestJava.java b/mirai-core-api/src/jvmTest/java/net/mamoe/mirai/event/JvmMethodEventsTestJava.java deleted file mode 100644 index b97fc8a9d..000000000 --- a/mirai-core-api/src/jvmTest/java/net/mamoe/mirai/event/JvmMethodEventsTestJava.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.event; - -import org.junit.jupiter.api.Test; - -import java.util.concurrent.atomic.AtomicInteger; - -import static kotlin.test.AssertionsKt.assertEquals; - -public class JvmMethodEventsTestJava extends SimpleListenerHost { - private final AtomicInteger called = new AtomicInteger(0); - - @EventHandler - public void ev(TestEvent event) { - called.incrementAndGet(); - } - - @EventHandler - public Void ev2(TestEvent event) { - called.incrementAndGet(); - return null; - } - - @EventHandler - public ListeningStatus ev3(TestEvent event) { - called.incrementAndGet(); - return ListeningStatus.LISTENING; - } - - @EventHandler - public void ev(TestEvent event, TestEvent event2) { - called.incrementAndGet(); - } - - @EventHandler - public Void ev2(TestEvent event, TestEvent event2) { - called.incrementAndGet(); - return null; - } - - @EventHandler - public ListeningStatus ev3(TestEvent event, TestEvent event2) { - called.incrementAndGet(); - return ListeningStatus.LISTENING; - } - - @Test - public void test() { - Events.registerEvents(this); - EventKt.broadcast(new TestEvent()); - assertEquals(6, called.get(), null); - } -} \ No newline at end of file diff --git a/mirai-core-api/src/jvmTest/kotlin/event/JvmMethodEventsTestJava.kt b/mirai-core-api/src/jvmTest/kotlin/event/JvmMethodEventsTestJava.kt new file mode 100644 index 000000000..4f5b94ab8 --- /dev/null +++ b/mirai-core-api/src/jvmTest/kotlin/event/JvmMethodEventsTestJava.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.event + +import net.mamoe.mirai.JavaFriendlyAPI +import net.mamoe.mirai.utils.EventListenerLikeJava +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicInteger +import kotlin.test.assertEquals + +@EventListenerLikeJava +@JavaFriendlyAPI +internal class JvmMethodEventsTestJava : SimpleListenerHost() { + private val called = AtomicInteger(0) + + @EventHandler + fun ev(event: TestEvent?) { + called.incrementAndGet() + } + + @EventHandler + fun ev2(event: TestEvent?): Void? { + called.incrementAndGet() + return null + } + + @EventHandler + fun ev3(event: TestEvent?): ListeningStatus? { + called.incrementAndGet() + return ListeningStatus.LISTENING + } + + @Test + fun test() { + this.registerEvents() + TestEvent().__broadcastJava() + assertEquals(3, called.get(), null) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index afc7a5118..43f8a16bc 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -46,6 +46,7 @@ import net.mamoe.mirai.message.data.Image.Key.FRIEND_IMAGE_ID_REGEX_2 import net.mamoe.mirai.message.data.Image.Key.GROUP_IMAGE_ID_REGEX import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.MiraiExperimentalApi +import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.currentTimeSeconds import java.util.concurrent.atomic.AtomicBoolean import kotlin.math.absoluteValue @@ -733,7 +734,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { override val nick: String get() = fromNick override val remark: String get() = "" - })) + }).cast()) } } diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index c30fc4185..f9b2f1c30 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -77,23 +77,23 @@ internal class GroupImpl( val uin: Long = groupInfo.uin - override lateinit var owner: Member + override lateinit var owner: NormalMember - override lateinit var botAsMember: Member + override lateinit var botAsMember: NormalMember override val botPermission: MemberPermission get() = botAsMember.permission // e.g. 600 override val botMuteRemaining: Int get() = botAsMember.muteTimeRemaining - override val members: ContactList<Member> = ContactList(members.mapNotNull { + override val members: ContactList<NormalMember> = ContactList(members.mapNotNull { if (it.uin == bot.id) { - botAsMember = newMember(it) + botAsMember = newMember(it).cast() if (it.permission == MemberPermission.OWNER) { owner = botAsMember } null - } else newMember(it).also { member -> + } else newMember(it).cast<NormalMember>().also { member -> if (member.permission == MemberPermission.OWNER) { owner = member } @@ -250,7 +250,7 @@ internal class GroupImpl( } ) - override operator fun get(id: Long): Member? { + override operator fun get(id: Long): NormalMember? { if (id == bot.id) { return botAsMember } @@ -298,7 +298,7 @@ internal class GroupImpl( throw EventCancelledException("exception thrown when broadcasting GroupMessagePreSendEvent", it) }.message.asMessageChain() - val length = chain.estimateLength(703) // 阈值为700左右,限制到3的倍数 + val length = chain.estimateLength(this, 703) // 阈值为700左右,限制到3的倍数 var imageCnt = 0 // 通过下方逻辑短路延迟计算 if (length > 5000 || chain.count { it is Image }.apply { imageCnt = this } > 50) { diff --git a/mirai-core/src/commonMain/kotlin/contact/MemberImpl.kt b/mirai-core/src/commonMain/kotlin/contact/MemberImpl.kt index 11a137ff2..66488aba7 100644 --- a/mirai-core/src/commonMain/kotlin/contact/MemberImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/MemberImpl.kt @@ -44,7 +44,7 @@ internal class MemberImpl constructor( group: GroupImpl, coroutineContext: CoroutineContext, memberInfo: MemberInfo -) : Member { +) : NormalMember { override val group: GroupImpl by group.unsafeWeakRef() override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob(coroutineContext[Job]) diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt index 0d91a9164..e35226563 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt @@ -45,6 +45,7 @@ import net.mamoe.mirai.network.RetryLaterException import net.mamoe.mirai.network.UnsupportedSMSLoginException import net.mamoe.mirai.network.WrongPasswordException import net.mamoe.mirai.utils.* +import java.util.concurrent.ConcurrentLinkedQueue import kotlin.coroutines.CoroutineContext @Suppress("MemberVisibilityCanBePrivate") @@ -220,8 +221,8 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo @JvmField @Volatile - internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? = - LockFreeLinkedList() + internal var pendingIncomingPackets: ConcurrentLinkedQueue<KnownPacketFactories.IncomingPacket<*>>? = + ConcurrentLinkedQueue() private var initFriendOk = false private var initGroupOk = false @@ -322,7 +323,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo } if (!pendingEnabled) { - pendingIncomingPackets = LockFreeLinkedList() + pendingIncomingPackets = ConcurrentLinkedQueue() _pendingEnabled.value = true } diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt index f2370fd84..14f6ace1d 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt @@ -76,9 +76,10 @@ internal open class QQAndroidClient( val device: DeviceInfo, bot: QQAndroidBot ) { - @Suppress("INVISIBLE_MEMBER") + val protocol = MiraiProtocolInternal[bot.configuration.protocol] + val subAppId: Long - get() = bot.configuration.protocol.id + get() = protocol.id internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList() @@ -118,9 +119,10 @@ internal open class QQAndroidClient( var tgtgtKey: ByteArray = generateTgtgtKey(device.guid) val randomKey: ByteArray = getRandomByteArray(16) - var miscBitMap: Int = 184024956 // 也可能是 150470524 ? - private var mainSigMap: Int = 16724722 - var subSigMap: Int = 0x10400 //=66,560 + + val miscBitMap: Int get() = protocol.miscBitMap // 184024956 // 也可能是 150470524 ? + private val mainSigMap: Int = protocol.mainSigMap + var subSigMap: Int = protocol.subSigMap // 0x10400 //=66,560 private val _ssoSequenceId: AtomicInt = atomic(85600) @@ -157,9 +159,12 @@ internal open class QQAndroidClient( var openAppId: Long = 715019303L - val apkVersionName: ByteArray get() = "8.4.18".toByteArray() + val apkVersionName: ByteArray get() = protocol.ver.toByteArray() //"8.4.18".toByteArray() val buildVer: String get() = "8.4.18.4810" // 8.2.0.1296 // 8.4.8.4810 // 8.2.7.4410 + val buildTime: Long get() = protocol.buildTime + val sdkVersion: String get() = protocol.sdkVer + private val messageSequenceId: AtomicInt = atomic(22911) internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2) @@ -194,7 +199,7 @@ internal open class QQAndroidClient( var networkType: NetworkType = NetworkType.WIFI - val apkSignatureMd5: ByteArray = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D".hexToBytes() + val apkSignatureMd5: ByteArray get() = protocol.sign.hexToBytes() // "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D".hexToBytes() /** * 协议版本?, 8.2.7 的为 8001 diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt index d6aa5c276..3c6f9b938 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt @@ -184,60 +184,59 @@ internal object KnownPacketFactories { bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer<T> - ) = - with(rawInput) { - // login - val flag1 = readInt() + ): Unit = with(rawInput) { + // login + val flag1 = readInt() - PacketLogger.verbose { "开始处理一个包" } + PacketLogger.verbose { "开始处理一个包" } - val flag2 = readByte().toInt() - val flag3 = readByte().toInt() - check(flag3 == 0) { - "Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. " + - "Remaining=${this.readBytes().toUHexString()}" - } + val flag2 = readByte().toInt() + val flag3 = readByte().toInt() + check(flag3 == 0) { + "Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. " + + "Remaining=${this.readBytes().toUHexString()}" + } - readString(readInt() - 4)// uinAccount + readString(readInt() - 4)// uinAccount - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - ByteArrayPool.useInstance(this.remaining.toInt()) { data -> - val size = this.readAvailable(data) + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + ByteArrayPool.useInstance(this.remaining.toInt()) { data -> + val size = this.readAvailable(data) - kotlin.runCatching { - when (flag2) { - 2 -> TEA.decrypt(data, DECRYPTER_16_ZERO, size) - 1 -> TEA.decrypt(data, bot.client.wLoginSigInfo.d2Key, size) - 0 -> data - else -> error("") - } - }.getOrElse { - bot.client.tryDecryptOrNull(data, size) { it } - }?.toReadPacket()?.let { decryptedData -> - when (flag1) { - 0x0A -> parseSsoFrame(bot, decryptedData) - 0x0B -> parseSsoFrame(bot, decryptedData) // 这里可能是 uni?? 但测试时候发现结构跟 sso 一样. - else -> error("unknown flag1: ${flag1.toByte().toUHexString()}") - } - }?.let { - it as IncomingPacket<T> - - if (it.packetFactory is IncomingPacketFactory<T> && it.packetFactory.canBeCached && bot.network.pendingEnabled) { - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - bot.network.pendingIncomingPackets?.addLast(it.also { - it.consumer = consumer - it.flag2 = flag2 - PacketLogger.info { "Cached ${it.commandName} #${it.sequenceId}" } - }) ?: handleIncomingPacket(it, bot, flag2, consumer) - } else { - handleIncomingPacket(it, bot, flag2, consumer) - } - } ?: kotlin.run { - PacketLogger.error { "任何key都无法解密: ${data.take(size).toUHexString()}" } - return + kotlin.runCatching { + when (flag2) { + 2 -> TEA.decrypt(data, DECRYPTER_16_ZERO, size) + 1 -> TEA.decrypt(data, bot.client.wLoginSigInfo.d2Key, size) + 0 -> data + else -> error("") } + }.getOrElse { + bot.client.tryDecryptOrNull(data, size) { it } + }?.toReadPacket()?.let { decryptedData -> + when (flag1) { + 0x0A -> parseSsoFrame(bot, decryptedData) + 0x0B -> parseSsoFrame(bot, decryptedData) // 这里可能是 uni?? 但测试时候发现结构跟 sso 一样. + else -> error("unknown flag1: ${flag1.toByte().toUHexString()}") + } + }?.let { + it as IncomingPacket<T> + + if (it.packetFactory is IncomingPacketFactory<T> && it.packetFactory.canBeCached && bot.network.pendingEnabled) { + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + bot.network.pendingIncomingPackets?.add(it.also { + it.consumer = consumer + it.flag2 = flag2 + PacketLogger.info { "Cached ${it.commandName} #${it.sequenceId}" } + }) ?: handleIncomingPacket(it, bot, flag2, consumer) + } else { + handleIncomingPacket(it, bot, flag2, consumer) + } + } ?: kotlin.run { + PacketLogger.error { "任何key都无法解密: ${data.take(size).toUHexString()}" } + return } } + } internal suspend fun <T : Packet?> handleIncomingPacket( it: IncomingPacket<T>, diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/Tlv.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/Tlv.kt index 0c22aadfd..20d86e608 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/Tlv.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/Tlv.kt @@ -120,7 +120,7 @@ internal fun BytePacketBuilder.t106( writeByte(isSavePassword.toByte()) writeFully(passwordMd5) writeFully(tgtgtKey) - writeInt(0) + writeInt(0) // wtf writeByte(isGuidAvailable.toByte()) if (isGuidAvailable) { require(guid != null) { "Guid must not be null when isGuidAvailable==true" } @@ -193,7 +193,7 @@ internal fun BytePacketBuilder.t107( internal fun BytePacketBuilder.t108( ksid: ByteArray ) { - require(ksid.size == 16) { "ksid should length 16" } + // require(ksid.size == 16) { "ksid should length 16" } writeShort(0x108) writeShortLVPacket { writeFully(ksid) diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt index 8b4ab278d..cf9cdbcab 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt @@ -22,6 +22,7 @@ import kotlinx.io.core.discardExact import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.Event @@ -51,6 +52,7 @@ import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.internal.utils.read import net.mamoe.mirai.internal.utils.toInt import net.mamoe.mirai.internal.utils.toUHexString +import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.debug import net.mamoe.mirai.utils.warning import kotlin.random.Random @@ -235,10 +237,12 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re readByte().toInt().and(0xff) } == 0x83) { return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo()) + .cast<NormalMember>() .also { group.members.delegate.add(it) }) } return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo()) + .cast<NormalMember>() .also { group.members.delegate.add(it) }) } } diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt index 8fb77cd5e..fcfada40c 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt @@ -128,7 +128,7 @@ internal class WtLogin { writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) { writeShort(9) // subCommand - writeShort(17) // count of TLVs, probably ignored by server? + writeShort(0x18) // count of TLVs, probably ignored by server? //writeShort(LoginType.PASSWORD.value.toShort()) t18(appId, client.appClientVersion, client.uin) @@ -161,7 +161,8 @@ internal class WtLogin { */ t116(client.miscBitMap, client.subSigMap) t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion) - t107(6) + t107(0) + t108(client.device.imei.toByteArray()) // t108(byteArrayOf()) // ignored: t104() @@ -192,9 +193,11 @@ internal class WtLogin { t145(client.device.guid) t147(appId, client.apkVersionName, client.apkSignatureMd5) + /* if (client.miscBitMap and 0x80 != 0) { t166(1) } + */ // ignored t16a because array5 is null @@ -210,14 +213,14 @@ internal class WtLogin { "connect.qq.com", "qzone.qq.com", "vip.qq.com", + "gamecenter.qq.com", "qun.qq.com", "game.qq.com", "qqweb.qq.com", "office.qq.com", "ti.qq.com", "mail.qq.com", - "qzone.com", - "mma.qq.com" + "mma.qq.com", ) ) @@ -243,7 +246,10 @@ internal class WtLogin { t202(bssid, ssid) } - t177() + t177( + buildTime = client.buildTime, + buildVersion = client.sdkVersion, + ) t516() t521() diff --git a/mirai-core/src/commonMain/kotlin/utils/MiraiProtocolInternal.kt b/mirai-core/src/commonMain/kotlin/utils/MiraiProtocolInternal.kt new file mode 100644 index 000000000..9035fcf7e --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/utils/MiraiProtocolInternal.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.internal.utils + +import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol +import java.util.* + +internal class MiraiProtocolInternal( + @JvmField internal val apkId: String, + @JvmField internal val id: Long, + @JvmField internal val ver: String, + @JvmField internal val sdkVer: String, + @JvmField internal val miscBitMap: Int, + @JvmField internal val subSigMap: Int, + @JvmField internal val mainSigMap: Int, + @JvmField internal val sign: String, + @JvmField internal val buildTime: Long, +) { + internal companion object { + internal val protocols = EnumMap<MiraiProtocol, MiraiProtocolInternal>( + MiraiProtocol::class.java + ) + + operator fun get(protocol: MiraiProtocol): MiraiProtocolInternal = + protocols[protocol] ?: error("Internal Error: Missing protocol $protocol") + + init { + protocols[MiraiProtocol.ANDROID_PHONE] = MiraiProtocolInternal( + "com.tencent.mobileqq", + 537066419, + "8.4.18", + "6.0.0.2454", + 184024956, + 0x10400, + 34869472, + "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D", + 1604580615L, + ) + protocols[MiraiProtocol.ANDROID_PAD] = MiraiProtocolInternal( + "com.tencent.mobileqq", + 537062409, "8.4.18", + "6.0.0.2454", + 184024956, + 0x10400, + 34869472, + "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D", + 1604580615L, + ) + protocols[MiraiProtocol.ANDROID_WATCH] = MiraiProtocolInternal( + "com.tencent.mobileqq", + 537061176, + "8.2.7", + "6.0.0.2413", + 184024956, + 0x10400, + 34869472, + "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D", + 1571193922L + ) + } + } +} diff --git a/mirai-core/src/commonMain/kotlin/utils/type.kt b/mirai-core/src/commonMain/kotlin/utils/type.kt index adbb7e7ad..6775f0efd 100644 --- a/mirai-core/src/commonMain/kotlin/utils/type.kt +++ b/mirai-core/src/commonMain/kotlin/utils/type.kt @@ -12,10 +12,10 @@ package net.mamoe.mirai.internal.utils +import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.AtAll.display -import kotlin.jvm.JvmMultifileClass -import kotlin.jvm.JvmName +import net.mamoe.mirai.utils.safeCast internal fun Int.toIpV4AddressString(): String { @@ -43,17 +43,17 @@ internal fun String.chineseLength(upTo: Int): Int { } } -internal fun MessageChain.estimateLength(upTo: Int): Int = +internal fun MessageChain.estimateLength(target: ContactOrBot, upTo: Int): Int = sumUpTo(upTo) { it, up -> - it.estimateLength(up) + it.estimateLength(target, up) } -internal fun SingleMessage.estimateLength(upTo: Int): Int { +internal fun SingleMessage.estimateLength(target: ContactOrBot, upTo: Int): Int { return when (this) { - is QuoteReply -> 444 + this.source.originalMessage.estimateLength(upTo) // Magic number + is QuoteReply -> 444 + this.source.originalMessage.estimateLength(target, upTo) // Magic number is Image -> 260 // Magic number is PlainText -> content.chineseLength(upTo) - is At -> display.chineseLength(upTo) + is At -> this.getDisplay(target.safeCast()).chineseLength(upTo) is AtAll -> display.chineseLength(upTo) else -> this.toString().chineseLength(upTo) }