├── .gitignore ├── LICENSE ├── build.gradle.kts ├── core ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ ├── com │ │ │ └── icecreamqaq │ │ │ │ └── yuq │ │ │ │ ├── Bot.kt │ │ │ │ ├── BotService.kt │ │ │ │ ├── YuQ.kt │ │ │ │ ├── YuQStarter.kt │ │ │ │ ├── annotation │ │ │ │ └── ans.kt │ │ │ │ ├── contact │ │ │ │ ├── Account.kt │ │ │ │ ├── Contact.kt │ │ │ │ ├── ContactSession.kt │ │ │ │ ├── Friend.kt │ │ │ │ ├── Group.kt │ │ │ │ ├── GroupMember.kt │ │ │ │ ├── Guild.kt │ │ │ │ ├── UserList.kt │ │ │ │ └── contacts.kt │ │ │ │ ├── controller │ │ │ │ ├── BotActionContext.kt │ │ │ │ ├── BotActionInvoker.kt │ │ │ │ ├── BotControllerLoader.kt │ │ │ │ ├── BotMethodInvoker.kt │ │ │ │ ├── MatcherItem.kt │ │ │ │ ├── MessageChannel.kt │ │ │ │ ├── router │ │ │ │ │ ├── BotRootRouter.kt │ │ │ │ │ ├── BotRouter.kt │ │ │ │ │ ├── RouterMatcher.kt │ │ │ │ │ └── matchers.kt │ │ │ │ └── type.kt │ │ │ │ ├── entity │ │ │ │ └── at.kt │ │ │ │ ├── error │ │ │ │ └── err.kt │ │ │ │ ├── event │ │ │ │ ├── BotEvent.kt │ │ │ │ ├── BotStatusEvent.kt │ │ │ │ ├── YuQEvent.kt │ │ │ │ └── message.kt │ │ │ │ ├── message │ │ │ │ ├── Message.kt │ │ │ │ ├── MessageFactory.kt │ │ │ │ ├── MessageItem.kt │ │ │ │ ├── MessageItemBase.kt │ │ │ │ ├── MessageItemChain.kt │ │ │ │ ├── MessageItemFactory.kt │ │ │ │ ├── MessagePackage.kt │ │ │ │ ├── MessageSource.kt │ │ │ │ └── items.kt │ │ │ │ ├── rainCode │ │ │ │ └── RainCode.kt │ │ │ │ └── util │ │ │ │ ├── MessageUtil.kt │ │ │ │ ├── fun.kt │ │ │ │ ├── type.kt │ │ │ │ └── val.kt │ │ └── yuq │ │ │ ├── Bot.kt │ │ │ ├── YuQ.kt │ │ │ ├── annotation │ │ │ └── ans.kt │ │ │ ├── contact │ │ │ ├── Account.kt │ │ │ ├── AccountList.kt │ │ │ ├── Contact.kt │ │ │ ├── ContactSession.kt │ │ │ ├── Friend.kt │ │ │ ├── Group.kt │ │ │ └── GroupMember.kt │ │ │ ├── controller │ │ │ ├── BotActionContext.kt │ │ │ ├── BotActionInvoker.kt │ │ │ ├── BotControllerLoader.kt │ │ │ ├── BotMethodInvoker.kt │ │ │ ├── MatcherItem.kt │ │ │ ├── MessageChannel.kt │ │ │ ├── router │ │ │ │ ├── BotRootRouter.kt │ │ │ │ ├── BotRouter.kt │ │ │ │ ├── RouterMatcher.kt │ │ │ │ └── matchers.kt │ │ │ └── type.kt │ │ │ ├── error │ │ │ └── err.kt │ │ │ ├── event │ │ │ ├── AtBotEvent.kt │ │ │ ├── BotEvent.kt │ │ │ ├── BotStatusEvent.kt │ │ │ ├── MessageEvent.kt │ │ │ └── SendMessageEvent.kt │ │ │ ├── internal │ │ │ ├── BotService.kt │ │ │ └── FrameworkInfo.kt │ │ │ └── message │ │ │ ├── Message.kt │ │ │ ├── MessageAt.kt │ │ │ ├── MessageItem.kt │ │ │ ├── MessageItemChain.kt │ │ │ ├── MessagePlusAble.kt │ │ │ ├── MessageSource.kt │ │ │ ├── SendAble.kt │ │ │ ├── chain │ │ │ ├── ChainItem.kt │ │ │ └── MessageBody.kt │ │ │ ├── items │ │ │ ├── At.kt │ │ │ ├── Face.kt │ │ │ ├── Image.kt │ │ │ └── Text.kt │ │ │ └── source │ │ │ ├── MessageFailByCancel.kt │ │ │ └── MessageFailByReadTimeOut.kt │ └── resources │ │ └── conf │ │ └── module │ │ └── com.IceCreamQAQ.YuQ.yml │ └── test │ └── java │ └── com │ └── IceCreamQAQ │ └── YuQ │ └── Test.java ├── devtools ├── build.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── icecreamqaq │ │ │ └── yuq │ │ │ └── devtools │ │ │ ├── DevBot.kt │ │ │ ├── DevYuQ.kt │ │ │ └── contact │ │ │ ├── DevAccount.kt │ │ │ ├── DevContact.kt │ │ │ ├── DevFriend.kt │ │ │ ├── DevGroup.kt │ │ │ ├── DevGroupMember.kt │ │ │ └── Type.kt │ └── resources │ │ └── conf │ │ └── module │ │ └── com.IceCreamQAQ.YuQ.DevTools.properties │ └── test │ ├── java │ └── yuq │ │ └── test │ │ └── java │ │ └── TestJavaController.java │ ├── kotlin │ └── yuq │ │ └── test │ │ ├── Index.kt │ │ ├── TestController.kt │ │ └── Ts.kt │ └── resources │ ├── conf │ └── DevTools.yml │ ├── ehcache-YuQ-Mirai-test.xml │ └── logback.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── platforms └── qq │ ├── build.gradle.kts │ └── src │ └── main │ └── kotlin │ └── yuq │ └── qq │ └── contact │ └── QAccount.kt ├── readme.md ├── readme └── img │ ├── 73.png │ └── Context.png └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | /.idea 3 | /.gradle 4 | 5 | /build 6 | /target 7 | 8 | /log 9 | /tmp 10 | 11 | /DevTools/build 12 | 13 | mavenInfo.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | kotlin("jvm") version "1.9.10" 4 | `java-library` 5 | `maven-publish` 6 | } 7 | val baseVersion = "1.0.0" 8 | 9 | group = "com.IceCreamQAQ.YuQ" 10 | version = baseVersion 11 | 12 | allprojects { 13 | repositories { 14 | mavenLocal() 15 | mavenCentral() 16 | maven("https://maven.icecreamqaq.com/repository/maven-public/") 17 | } 18 | } 19 | 20 | subprojects { 21 | apply { 22 | plugin("java") 23 | plugin("java-library") 24 | plugin("maven-publish") 25 | } 26 | 27 | dependencies { 28 | implementation(kotlin("stdlib")) 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | } 4 | 5 | val rainVersion = "1.0.0-DEV1" 6 | dependencies { 7 | api("com.IceCreamQAQ.Rain:application:$rainVersion") 8 | api("com.IceCreamQAQ.Rain:event:$rainVersion") 9 | api("com.IceCreamQAQ.Rain:controller:$rainVersion") 10 | api("com.IceCreamQAQ.Rain:job:$rainVersion") 11 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/Bot.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq 2 | 3 | import com.icecreamqaq.yuq.contact.Account 4 | import java.io.Closeable 5 | 6 | interface Bot : Closeable { 7 | 8 | // 机器人运行的平台 9 | val platform: String 10 | 11 | // 机器人的 QQ 号码 12 | val botId: Long 13 | get() = botInfo.id 14 | 15 | // 机器人的个人信息 16 | val botInfo: Account 17 | 18 | // 好友列表 19 | val friends: FriendList 20 | 21 | // 群列表 22 | val groups: GroupList 23 | 24 | // 频道列表 25 | val guilds: GuildList 26 | 27 | // 刷新好友列表 28 | fun refreshFriends(): FriendList 29 | 30 | // 刷新群列表 31 | fun refreshGroups(): GroupList 32 | 33 | // 刷新频道列表 34 | fun refreshGuilds(): GuildList 35 | 36 | // 通过 ID 获取 Platform ID。 37 | fun id2platformId(id: Long): String 38 | 39 | // 通过 Platform ID 获取 ID。 40 | fun platformId2id(platformId: String): Long 41 | 42 | 43 | fun login() 44 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/BotService.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq 2 | 3 | import com.icecreamqaq.yuq.annotation.Internal 4 | import org.slf4j.LoggerFactory 5 | import rain.api.event.EventBus 6 | 7 | @Internal 8 | class BotService( 9 | private val eventBus: EventBus, 10 | ) { 11 | companion object { 12 | private val log = LoggerFactory.getLogger(BotService::class.java) 13 | } 14 | 15 | 16 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/YuQ.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq 2 | 3 | import com.icecreamqaq.yuq.message.MessageItemFactory 4 | 5 | interface YuQ { 6 | 7 | val messageItemFactory: MessageItemFactory 8 | 9 | val bots: List 10 | 11 | fun findByPlatformAndPlatformId(platform: String, platformId: String): Bot? = 12 | bots.firstOrNull { it.platform == platform && it.botInfo.platformId == platformId } 13 | 14 | /*** 动态创建 Bot 实例。 15 | * @param id 登录账户 ID。 16 | * @param pwd 登录账户密码。 17 | * @param botName 机器人名 18 | * @param extData 目标 Runtime 所需要的扩展数据。 19 | * @return 创建后的机器人对象。 20 | * 21 | * 根据目标平台不同,创建 Bot 具体所需要的参数与结果并不相同。 22 | */ 23 | fun createBot(id: String, pwd: String, botName: String? = null, extData: String? = null): Bot 24 | 25 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/YuQStarter.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq 2 | 3 | import org.slf4j.LoggerFactory 4 | import rain.application.FullStackApplicationLauncher 5 | 6 | class YuQStarter { 7 | 8 | 9 | companion object { 10 | private val log = LoggerFactory.getLogger(YuQStarter::class.java) 11 | 12 | @JvmStatic 13 | fun start() { 14 | val startTime = System.currentTimeMillis() 15 | 16 | FullStackApplicationLauncher.launch() 17 | 18 | val overTime = System.currentTimeMillis() 19 | 20 | log.info("Done! ${(overTime - startTime).toDouble() / 1000}s.") 21 | 22 | println(" __ __ ____ \n" + 23 | " \\ \\/ /_ __/ __ \\\n" + 24 | " \\ / // / /_/ /\n" + 25 | " /_/\\_,_/\\___\\_\\\n") 26 | println("感谢您使用 YuQ 进行开发,在您使用中如果遇到任何问题,可以到 Github,Gitee 提出 issue,您也可以添加 YuQ 的开发交流群(787049553)进行交流。") 27 | } 28 | 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/annotation/ans.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.annotation 2 | 3 | import yuq.controller.BotControllerLoader 4 | import yuq.controller.MessageChannel 5 | import rain.api.annotation.LoadBy 6 | import rain.classloader.enchant.EnchantBy 7 | import rain.classloader.enchant.MethodParaNamedEnchanter 8 | import javax.inject.Named 9 | 10 | 11 | // 被此注解标记的内容仍在开发状态,相关类型以及名字可能随时变动,请自行评估使用价值。 12 | annotation class Dev 13 | 14 | // 被此注解标记的内容不推荐使用。 15 | annotation class NoRecommendation 16 | 17 | // 此注解标记的内容为 YuQ 内部实现,如果您不能完全理解,请不要轻易修改内容。 18 | annotation class Internal 19 | 20 | @LoadBy(BotControllerLoader::class) 21 | @EnchantBy(MethodParaNamedEnchanter::class) 22 | @Named("group") 23 | annotation class GroupController 24 | 25 | @LoadBy(BotControllerLoader::class) 26 | @EnchantBy(MethodParaNamedEnchanter::class) 27 | @Named("priv") 28 | annotation class PrivateController 29 | 30 | @LoadBy(BotControllerLoader::class) 31 | @EnchantBy(MethodParaNamedEnchanter::class) 32 | @Named("guild") 33 | annotation class GuildController 34 | 35 | @Target(AnnotationTarget.CLASS) 36 | @LoadBy(BotControllerLoader::class) 37 | @EnchantBy(MethodParaNamedEnchanter::class) 38 | annotation class BotController 39 | 40 | @Target(AnnotationTarget.FUNCTION) 41 | annotation class BotAction( 42 | val value: String, 43 | vararg val channel: MessageChannel = [MessageChannel.Friend, MessageChannel.GroupTemporary, MessageChannel.Group] 44 | ) 45 | 46 | @Target(AnnotationTarget.FUNCTION) 47 | annotation class GroupAction(val value: String) 48 | 49 | @Target(AnnotationTarget.FUNCTION) 50 | annotation class FriendAction(val value: String) 51 | 52 | @Target(AnnotationTarget.FUNCTION) 53 | annotation class TemporaryAction(val value: String) 54 | 55 | @Target(AnnotationTarget.FUNCTION) 56 | annotation class PrivateAction(val value: String) 57 | 58 | // 59 | //@LoadBy(BotContextControllerLoader::class) 60 | //@EnchantBy(MethodParaNamedEnchanter::class) 61 | //annotation class ContextController 62 | //annotation class ContextAction(val value: String) 63 | 64 | //annotation class ContextTips(val value: Array) 65 | 66 | annotation class RainCodeString 67 | 68 | //@Repeatable(ContextTips::class) 69 | //annotation class ContextTip(val value: String, val status: Int = 0) 70 | //annotation class NextContext(val value: String, val status: Int = 0) 71 | //annotation class Save(val value: String = "") 72 | 73 | //annotation class QMsg( 74 | // val at: Boolean = false, 75 | // val reply: Boolean = false, 76 | // val atNewLine: Boolean = false, 77 | // val mastAtBot: Boolean = false, 78 | // val recall: Long = 0, 79 | // val forceMatch: Boolean = false, 80 | //) 81 | // 82 | //annotation class PathVar(val value: Int, val type: Type = Type.String) { 83 | // enum class Type { 84 | // Source, String, Integer, Switch, Long, Double, Contact, Friend, Group, Member, User 85 | // } 86 | //} 87 | // 88 | //annotation class AsyncAction 89 | //annotation class TaskLimit( 90 | // val value: Long, 91 | // val type: TaskLimitSource = TaskLimitSource.SENDER, 92 | // val extraPermission: String = "", 93 | // val coldDownTip: String = "冷却中。", 94 | //) { 95 | // enum class TaskLimitSource { 96 | // SENDER, SOURCE, ALL 97 | // } 98 | //} 99 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/contact/Account.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.contact 2 | 3 | /*** 账号 4 | * 用于描述一个标准用户,可能由添加好友、群成员等渠道获得。 5 | * 6 | * 账号可能存在与之关联的联系人,也可能不存在。 7 | */ 8 | interface Account { 9 | 10 | // 账号 ID 11 | val id: Long 12 | // 账号 Platform 实际用户 ID 13 | val platformId: String 14 | 15 | // 账号昵称 16 | val nickname: String 17 | // 账号头像 URL 链接 18 | val avatar: String 19 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/contact/Contact.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.contact 2 | 3 | import com.icecreamqaq.yuq.Bot 4 | import com.icecreamqaq.yuq.botService 5 | import com.icecreamqaq.yuq.message.Image 6 | import com.icecreamqaq.yuq.message.Message 7 | import com.icecreamqaq.yuq.message.Message.Companion.toMessage 8 | import com.icecreamqaq.yuq.message.MessageSource 9 | import com.icecreamqaq.yuq.message.SendAble 10 | import java.io.File 11 | 12 | /*** 联系人 13 | * 该对象描述一个标准联系人,联系人可以用来发送消息。 14 | * 联系人可能是实际存在的好友,群,群成员,也可能是来自陌生人,添加好友临时会话时的。 15 | * 16 | * 联系人对象每个 Bot 唯一,可以保存。 17 | */ 18 | interface Contact : Account { 19 | 20 | // 该联系人所属的机器人 21 | val bot: Bot 22 | 23 | // YuQ 框架内对联系人的唯一识别码,全局唯一 24 | val guid: String 25 | 26 | // 获取联系人的上下文会话 27 | val session: ContactSession 28 | get() = botService.getContextSession(bot, guid) 29 | 30 | // 通过 Message 发送一条消息 31 | fun sendMessage(message: Message): MessageSource 32 | 33 | // 通过 SendAble 发送一条消息 34 | fun sendMessage(message: SendAble): MessageSource = sendMessage(message.toMessage()) 35 | 36 | // 通过 String 发送一条消息 37 | fun sendMessage(message: String): MessageSource = sendMessage(message.toMessage()) 38 | 39 | // 上传一张图片,返回 Image(MessageItem) 对象。 40 | fun uploadImage(imageFile: File): Image 41 | 42 | // 发送文件,当 Contact 为 Group 时,表现为上传文件。 43 | fun sendFile(file: File) 44 | 45 | // 当前联系人是否能发送消息 46 | fun canSendMessage(): Boolean = true 47 | 48 | // 用于描述输出到日志中的内容。 49 | val logString: String 50 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/contact/ContactSession.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.contact 2 | 3 | import com.icecreamqaq.yuq.error.WaitNextMessageTimeoutException 4 | import com.icecreamqaq.yuq.message.Message 5 | import kotlinx.coroutines.CompletableDeferred 6 | import kotlinx.coroutines.withTimeout 7 | import java.util.concurrent.ConcurrentHashMap 8 | import kotlin.collections.set 9 | 10 | /*** 联系人上下文会话 11 | * 该对象描述一个联系人的上下文会话,可以用来保存会话数据。 12 | * 上下文对象将保存一段时间,若联系人一段时间内没有发送消息,上下文对象将被销毁。 13 | */ 14 | class ContactSession(val id: String, private val saves: MutableMap = ConcurrentHashMap()) { 15 | 16 | internal var suspendCoroutineIt: CompletableDeferred? = null 17 | 18 | /*** 获取会话数据 19 | * @param name 数据名 20 | * @return 返回数据 21 | */ 22 | operator fun get(name: String) = saves[name] 23 | /*** 设置会话数据 24 | * @param name 数据名 25 | * @param value 数据 26 | */ 27 | operator fun set(name: String, value: Any) { 28 | saves[name] = value 29 | } 30 | /*** 移除会话数据 31 | * @param name 数据名 32 | * @return 返回数据 33 | */ 34 | fun remove(name: String) = saves.remove(name) 35 | 36 | /*** 等待下一条消息 37 | * 联系人的下一条消息将被捕获。 38 | * 被捕获的消息不会再进入 Controller 链路处理流程。 39 | * 但依旧会执行消息事件,若消息事件被取消,则不会捕获本条消息,将会继续等待。 40 | * 41 | * @param maxTime 最大等待时间,单位毫秒,默认为 30000 毫秒。 42 | * @return 返回下一条消息。 43 | * @throws WaitNextMessageTimeoutException 等待超时时抛出该异常。 44 | */ 45 | suspend fun waitNextMessage(maxTime: Long = 30000): Message = 46 | try { 47 | suspendCoroutineIt = CompletableDeferred() 48 | withTimeout(maxTime) { suspendCoroutineIt!!.await() } 49 | } catch (e: Exception) { 50 | throw WaitNextMessageTimeoutException() 51 | } finally { 52 | suspendCoroutineIt = null 53 | } 54 | 55 | 56 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/contact/Friend.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.contact 2 | 3 | /*** 好友 4 | * 该对象描述一个好友。 5 | * 好友每个 Bot 唯一。 6 | */ 7 | interface Friend : Contact { 8 | 9 | // 删除好友 10 | fun delete() 11 | 12 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/contact/Group.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.contact 2 | 3 | import com.icecreamqaq.yuq.GroupMemberList 4 | import com.icecreamqaq.yuq.error.PermissionDeniedException 5 | 6 | interface Group : Contact { 7 | 8 | // 群成员列表,该列表中不包含机器人自己。 9 | val members: GroupMemberList 10 | // 群最大成员数量 11 | val maxCount: Int 12 | 13 | // 机器人在本群的成员对象 14 | val botMember: GroupMember 15 | 16 | // 群主 17 | val owner: GroupMember 18 | // 管理员列表 19 | val admins: List 20 | 21 | /*** 群公告列表 22 | * 向列表中新增/移除对象会实时同步操作。 23 | */ 24 | val notices: GroupNoticeList 25 | 26 | override fun canSendMessage() = !botMember.isBan() 27 | 28 | // 获取群成员,当群成员不存在时抛出异常。 29 | operator fun get(qq: Long) = getOrNull(qq) ?: error("Member $qq Not Found!") 30 | // 获取群成员,当群成员不存在时返回 null。 31 | fun getOrNull(qq: Long): GroupMember? = if (qq == botMember.id) botMember else members[qq] 32 | 33 | /*** 离开本群 34 | * 当机器人为群主的时候将解散群聊。 35 | */ 36 | fun leave() 37 | 38 | /*** 打开全体禁言 39 | * @throws [PermissionDeniedException] 当权限不足时抛出 40 | */ 41 | fun banAll() 42 | 43 | /*** 关闭全体禁言 44 | * @throws [PermissionDeniedException] 当权限不足时抛出 45 | */ 46 | fun unBanAll() 47 | 48 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/contact/GroupMember.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.contact 2 | 3 | import com.icecreamqaq.yuq.error.PermissionDeniedException 4 | import com.icecreamqaq.yuq.message.At 5 | import com.icecreamqaq.yuq.mif 6 | 7 | /*** 群成员对象 8 | * 该对象描述一个群成员。 9 | * 群成员对象每个 Bot 每个群唯一。 10 | * 11 | * @see AnonymousMember 匿名成员 12 | */ 13 | interface GroupMember : Contact { 14 | 15 | // 该成员所属的群 16 | val group: Group 17 | 18 | /*** 群成员权限 19 | * 0: 普通成员 20 | * 1: 管理员 21 | * 2: 群主 22 | */ 23 | val permission: Int 24 | 25 | /*** 群成员群名片 26 | * 当群成员没有设置群名片时,该值为空字符串。 27 | * 向参数内写入值,会实时同步修改群名片。 28 | * @throws [PermissionDeniedException] 当权限不足时抛出 29 | */ 30 | var namecard: String 31 | 32 | /*** 群成员群头衔 33 | * 当群成员没有设置群头衔时,该值为空字符串。 34 | * 向参数内写入值,会实时同步修改群头衔。 35 | * @throws [PermissionDeniedException] 当权限不足时抛出 36 | */ 37 | var title: String 38 | 39 | /*** 群成员禁言时间 40 | * 该值为禁言到期时间戳,单位毫秒。 41 | */ 42 | val ban: Long 43 | 44 | // 群成员最后发言时间,单位毫秒 45 | val lastMessageTime: Long 46 | 47 | // 该成员是否被禁言 48 | fun isBan() = ban > (System.currentTimeMillis() / 1000).toInt() 49 | 50 | /*** 禁言该成员,单位秒 51 | * @throws [PermissionDeniedException] 当权限不足时抛出 52 | */ 53 | fun ban(time: Int) 54 | 55 | /*** 取消禁言该成员 56 | * @throws [PermissionDeniedException] 当权限不足时抛出 57 | */ 58 | fun unBan() 59 | 60 | // 在群名片为空时返回昵称,否则返回群名片 61 | fun nameCardOrName() = if (namecard == "") nickname else namecard 62 | 63 | // 返回一个 @群成员 的消息内容 64 | fun at(): At = mif.at(this) 65 | 66 | /*** 该成员是否具有管理员权限 67 | * 该成员是管理员,或是群主都会返回 true。 68 | * 如果需要精确判断,请判断 [permission] 值。 69 | */ 70 | fun isAdmin() = permission > 0 71 | 72 | // 该成员是否为群主 73 | fun isOwner() = permission == 2 74 | 75 | /*** 移除群成员 76 | * @throws [PermissionDeniedException] 当权限不足时抛出 77 | */ 78 | fun kick() = kick("") 79 | 80 | /*** 移除群成员 81 | * @param message 移除成员时的提示消息 82 | * @throws [PermissionDeniedException] 当权限不足时抛出 83 | */ 84 | fun kick(message: String = "") 85 | 86 | val logStringSingle: String 87 | 88 | companion object { 89 | @JvmStatic 90 | fun GroupMember.toFriend(): Friend? = bot.friends[id] 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/contact/Guild.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.contact 2 | 3 | import com.icecreamqaq.yuq.GuildChannelList 4 | import com.icecreamqaq.yuq.GuildMemberList 5 | 6 | interface Guild : Account { 7 | 8 | // val id: Long 9 | // val platformId: Long 10 | // 11 | // val name: String 12 | // val avatar: String 13 | 14 | val defaultChannel: Channel 15 | val channels: GuildChannelList 16 | 17 | // 该列表并不提供完整的成员列表! 18 | val member: GuildMemberList 19 | 20 | } 21 | 22 | interface Channel : Contact { 23 | val guild: Guild 24 | override val avatar: String 25 | get() = "" 26 | } 27 | 28 | interface GuildMember : Contact { 29 | val guild: Guild 30 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/contact/UserList.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.contact 2 | 3 | import kotlin.collections.Map.Entry 4 | 5 | /*** 6 | * 注意,这虽然名字叫 List,但是他并不是一个按下标维护的 List! 7 | */ 8 | interface UserList : Map { 9 | 10 | fun containsKey(platformId: String): Boolean 11 | operator fun get(platformId: String): E? 12 | 13 | fun getOrDefault(platformId: String, defaultValue: E): E 14 | 15 | val platformIds: Set 16 | val platformEntries: Set> 17 | } 18 | 19 | class UserListImpl : UserList { 20 | 21 | class ProEntry(var user: E) { 22 | inline val id get() = user.id 23 | inline val platformId get() = user.platformId 24 | 25 | val idEntry: Entry = IdEntry() 26 | val platformIdEntry: Entry = PlatformIdEntry() 27 | 28 | inner class IdEntry : Entry { 29 | override val key: Long 30 | get() = id 31 | override val value: E 32 | get() = user 33 | } 34 | 35 | inner class PlatformIdEntry : Entry { 36 | override val key: String 37 | get() = platformId 38 | override val value: E 39 | get() = user 40 | } 41 | 42 | override fun toString(): String { 43 | return "ProEntry(id=$id, platformId=$platformId, User=$user)" 44 | } 45 | } 46 | 47 | abstract inner class ProSet : AbstractSet() { 48 | override val size: Int 49 | get() = list.size 50 | 51 | abstract fun getByIndex(index: Int): R 52 | 53 | inner class ProIter : Iterator { 54 | 55 | var i = 0 56 | 57 | override fun hasNext() = i < size 58 | 59 | override fun next() = getByIndex(i++) 60 | 61 | } 62 | 63 | override fun iterator() = ProIter() 64 | 65 | } 66 | 67 | 68 | private val list = ArrayList>() 69 | 70 | override val size: Int 71 | get() = list.size 72 | 73 | override val entries: Set> = proSet { list[it].idEntry } 74 | override val platformEntries: Set> = proSet { list[it].platformIdEntry } 75 | override val keys: Set = proSet { list[it].id } 76 | override val platformIds: Set = proSet { list[it].platformId } 77 | override val values: Collection = proSet { list[it].user } 78 | 79 | override fun get(key: Long): E? { 80 | for (entry in list) { 81 | if (entry.id == key) return entry.user 82 | } 83 | return null 84 | } 85 | 86 | override fun get(platformId: String): E? { 87 | for (entry in list) { 88 | if (entry.platformId == platformId) return entry.user 89 | } 90 | return null 91 | } 92 | 93 | override fun getOrDefault(key: Long, defaultValue: E) = this[key] ?: defaultValue 94 | 95 | override fun getOrDefault(platformId: String, defaultValue: E) = this[platformId] ?: defaultValue 96 | 97 | override fun containsKey(key: Long): Boolean { 98 | for (e in list) { 99 | if (e.id == key) return true 100 | } 101 | return false 102 | } 103 | 104 | override fun containsKey(platformId: String): Boolean { 105 | for (e in list) { 106 | if (e.platformId == platformId) return true 107 | } 108 | return false 109 | } 110 | 111 | override fun containsValue(value: E): Boolean { 112 | for (entry in list) { 113 | if (entry.user == value) return true 114 | } 115 | return false 116 | } 117 | 118 | override fun isEmpty() = size == 0 119 | 120 | fun add(user: E) { 121 | list.add(ProEntry(user)) 122 | } 123 | 124 | fun getOrPut(id: Long, value: E): E { 125 | val r = get(id) 126 | if (r != null) return r 127 | add(value) 128 | return value 129 | } 130 | 131 | fun getOrPut(id: Long, value: () -> E) = getOrPut(id, value()) 132 | 133 | operator fun set(id: Long, user: E) { 134 | for (i in 0 until list.size) { 135 | val u = list[i] 136 | if (u.id == id) { 137 | u.user = user 138 | return 139 | } 140 | } 141 | add(user) 142 | } 143 | 144 | fun remove(id: Long): E? { 145 | for (i in 0 until list.size) { 146 | val u = list[i] 147 | if (u.id == id) { 148 | list.removeAt(i) 149 | return u.user 150 | } 151 | } 152 | return null 153 | } 154 | 155 | fun remove(platformId: String): E? { 156 | for (i in 0 until list.size) { 157 | val u = list[i] 158 | if (u.platformId == platformId) { 159 | list.removeAt(i) 160 | return u.user 161 | } 162 | } 163 | return null 164 | } 165 | 166 | fun remove(user: E): E? { 167 | return remove(user.id) 168 | } 169 | 170 | private fun proSet(body: (Int) -> T): ProSet { 171 | return object : ProSet() { 172 | override fun getByIndex(index: Int) = body(index) 173 | 174 | } 175 | } 176 | 177 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/contact/contacts.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.contact 2 | 3 | 4 | /*** 匿名群成员对象 5 | * 匿名群成员对象是一个临时对象,不保证稳定,不保证唯一。 6 | * 匿名群成员无法用于创建临时会话,也无法发送消息,或发送戳一戳等。 7 | */ 8 | interface AnonymousMember : GroupMember 9 | 10 | enum class UserSex { 11 | MAN, WOMAN, NONE 12 | } 13 | 14 | data class UserInfo( 15 | override val id: Long, 16 | override val platformId: String = id.toString(), 17 | override val avatar: String, 18 | override val nickname: String, 19 | val sex: UserSex, 20 | val age: Int, 21 | val qqAge: Int, 22 | val level: Int, 23 | val loginDays: Int, 24 | val vips: List, 25 | ) : Account 26 | 27 | data class GroupInfo( 28 | val id: Long, 29 | val name: String, 30 | val maxCount: Int, 31 | 32 | val owner: UserInfo, 33 | val admin: List 34 | ) 35 | 36 | open class GroupNotice { 37 | 38 | protected var id: Long? = null 39 | fun getId() = id ?: -1 40 | 41 | var text: String = "" 42 | 43 | var isTop = false 44 | var popWindow = false 45 | var confirmRequired = false 46 | 47 | } 48 | 49 | abstract class GroupNoticeList(protected val group: Group) { 50 | 51 | internal val noticeList = arrayListOf() 52 | 53 | operator fun get(id: Int) { 54 | TODO("Feature not supported") 55 | } 56 | 57 | abstract fun add(notice: GroupNotice) 58 | 59 | } 60 | 61 | data class UserVip( 62 | val id: Int, 63 | val name: Int, 64 | val ipSuper: Int, 65 | val desc: String, 66 | val level: Int, 67 | val yearFlag: Boolean, 68 | val para: String 69 | ) -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/controller/BotActionContext.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.controller 2 | 3 | import com.icecreamqaq.yuq.Bot 4 | import com.icecreamqaq.yuq.contact.Contact 5 | import com.icecreamqaq.yuq.message.Message 6 | import rain.controller.ActionContext 7 | import yuq.controller.BotActionInvoker 8 | import yuq.controller.MessageChannel 9 | 10 | class BotActionContext( 11 | val bot: Bot, 12 | val channel: MessageChannel, 13 | val sender: Contact, 14 | val source: Contact, 15 | val message: Message 16 | ) : ActionContext { 17 | 18 | internal val matcherItem = MatcherItem(message.body) 19 | 20 | private val saved = HashMap() 21 | 22 | override var result: Any? = null 23 | override var runtimeError: Throwable? = null 24 | 25 | 26 | var actionInvoker: BotActionInvoker? = null 27 | 28 | 29 | override fun get(name: String): Any? { 30 | return saved[name] 31 | } 32 | 33 | override fun remove(name: String): Any? { 34 | return saved.remove(name) 35 | } 36 | 37 | override fun set(name: String, obj: Any?) { 38 | saved[name] = obj 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/controller/BotActionInvoker.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.controller 2 | 3 | import yuq.controller.router.RouterMatcher 4 | import com.icecreamqaq.yuq.message.Message 5 | import com.icecreamqaq.yuq.message.Message.Companion.toMessage 6 | import com.icecreamqaq.yuq.message.MessageItem 7 | import com.icecreamqaq.yuq.message.MessageItemChain 8 | import com.icecreamqaq.yuq.message.MessageLineQ 9 | import kotlinx.coroutines.coroutineScope 10 | import kotlinx.coroutines.delay 11 | import kotlinx.coroutines.launch 12 | import rain.controller.ProcessInvoker 13 | import rain.controller.simple.SimpleActionInvoker 14 | import yuq.controller.BotActionContext 15 | import yuq.controller.MessageChannel 16 | 17 | 18 | class BotActionInvoker( 19 | val channels: Array, 20 | val matchers: List, 21 | action: ProcessInvoker, 22 | beforeProcesses: Array>, 23 | aftersProcesses: Array>, 24 | catchsProcesses: Array> 25 | ) : SimpleActionInvoker(action, beforeProcesses, aftersProcesses, catchsProcesses) { 26 | 27 | 28 | override suspend fun onActionResult(context: BotActionContext, result: Any?): Boolean { 29 | if (result == null) return false 30 | if (checkResult(context, result)) return true 31 | 32 | context.result = when (result) { 33 | is String -> result.toMessage() 34 | is Message -> result 35 | is MessageItem -> result.toMessage() 36 | is MessageItemChain -> result.toMessage() 37 | is MessageLineQ -> result.toMessage() 38 | is Array<*> -> 39 | coroutineScope { 40 | launch { 41 | result.forEach { 42 | when (it) { 43 | is Int -> delay(it.toLong()) 44 | is Long -> delay(it) 45 | is Message -> context.source.sendMessage(it) 46 | } 47 | } 48 | } 49 | } 50 | 51 | else -> result.toString().toMessage() 52 | } 53 | return false 54 | } 55 | 56 | override suspend fun checkChannel(context: BotActionContext): Boolean = 57 | context.channel in channels 58 | 59 | } 60 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/controller/BotControllerLoader.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.controller 2 | 3 | import com.icecreamqaq.yuq.annotation.* 4 | import yuq.controller.router.BotRouter 5 | import yuq.controller.router.RouterMatcher 6 | import rain.api.di.DiContext 7 | import rain.controller.* 8 | import rain.controller.annotation.After 9 | import rain.controller.annotation.Before 10 | import rain.controller.annotation.Catch 11 | import rain.controller.annotation.Path 12 | import rain.controller.simple.SimpleCatchMethodInvoker 13 | import rain.function.annotation 14 | import yuq.controller.BotActionContext 15 | import yuq.controller.BotMethodInvoker 16 | import yuq.controller.MessageChannel 17 | import java.lang.reflect.Method 18 | import kotlin.reflect.KProperty1 19 | 20 | open class BotControllerLoader( 21 | context: DiContext 22 | ) : ControllerLoader(context) { 23 | 24 | private val rootInfo = BotRootInfo(BotRouter { true }) 25 | 26 | override fun findRootRouter(name: String) = rootInfo 27 | 28 | fun readPath(path: String): List { 29 | TODO() 30 | } 31 | 32 | override fun controllerInfo( 33 | root: BotRootInfo, 34 | annotation: Annotation?, 35 | controllerClass: Class<*>, 36 | instanceGetter: ControllerInstanceGetter 37 | ): ControllerProcessFlowInfo? { 38 | val channels = controllerChannel(annotation, controllerClass) ?: return null 39 | var controllerRouter = root.router 40 | controllerClass.annotation { 41 | readPath(value).forEach { m -> 42 | controllerRouter = controllerRouter.subRouters.firstOrNull { it.matcher == m } 43 | ?: BotRouter(m).also { controllerRouter.subRouters.add(it) } 44 | } 45 | } 46 | 47 | return ControllerProcessFlowInfo(controllerClass, channels, controllerRouter) 48 | } 49 | 50 | private fun controllerChannel(annotation: Annotation?, controllerClass: Class<*>): List? { 51 | if (annotation == null) return null 52 | return when (annotation) { 53 | is GroupController -> arrayListOf(MessageChannel.Group.channel) 54 | is PrivateController -> arrayListOf(MessageChannel.Friend.channel, MessageChannel.GroupTemporary.channel) 55 | else -> arrayListOf() 56 | } 57 | } 58 | 59 | override fun makeAfter( 60 | afterAnnotation: After, 61 | controllerClass: Class<*>, 62 | afterMethod: Method, 63 | instanceGetter: ControllerInstanceGetter 64 | ): ProcessInfo? = 65 | ProcessInfo( 66 | afterAnnotation.weight, 67 | afterAnnotation.except, 68 | afterAnnotation.only, 69 | BotMethodInvoker(afterMethod, instanceGetter) 70 | ) 71 | 72 | override fun makeBefore( 73 | beforeAnnotation: Before, 74 | controllerClass: Class<*>, 75 | beforeMethod: Method, 76 | instanceGetter: ControllerInstanceGetter 77 | ): ProcessInfo? = 78 | ProcessInfo( 79 | beforeAnnotation.weight, 80 | beforeAnnotation.except, 81 | beforeAnnotation.only, 82 | BotMethodInvoker(beforeMethod, instanceGetter) 83 | ) 84 | 85 | override fun makeCatch( 86 | catchAnnotation: Catch, 87 | controllerClass: Class<*>, 88 | catchMethod: Method, 89 | instanceGetter: ControllerInstanceGetter 90 | ): ProcessInfo? = 91 | ProcessInfo( 92 | catchAnnotation.weight, 93 | catchAnnotation.except, 94 | catchAnnotation.only, 95 | SimpleCatchMethodInvoker(catchAnnotation.error.java, BotMethodInvoker(catchMethod, instanceGetter)) 96 | ) 97 | 98 | override fun postLoad() { 99 | val actionList = ArrayList>() 100 | rootInfo.controllers.forEach { 101 | it.actions.forEach { 102 | actionList.add( 103 | ActionInfo( 104 | it.actionClass, 105 | it.actionMethod, 106 | it.creator() 107 | ) 108 | ) 109 | } 110 | } 111 | 112 | // botService.rootRouter = BotRootRouter(rootInfo.router, actionList) 113 | } 114 | 115 | override fun makeAction( 116 | rootRouter: BotRootInfo, 117 | controllerFlow: ControllerProcessFlowInfo, 118 | controllerClass: Class<*>, 119 | actionMethod: Method, 120 | instanceGetter: ControllerInstanceGetter 121 | ): ActionProcessFlowInfo? { 122 | var path = "" 123 | var channels: Array = emptyArray() 124 | actionMethod.annotation { 125 | path = value 126 | channels = arrayOf(MessageChannel.Group) 127 | } 128 | actionMethod.annotation { 129 | path = value 130 | channels = arrayOf(MessageChannel.GroupTemporary, MessageChannel.Friend) 131 | } 132 | actionMethod.annotation { 133 | path = value 134 | channels = arrayOf(MessageChannel.Friend) 135 | } 136 | actionMethod.annotation { 137 | path = value 138 | channels = arrayOf(MessageChannel.GroupTemporary) 139 | } 140 | 141 | if (channels.isEmpty()) return null 142 | 143 | val actionName = actionMethod.name 144 | 145 | val actionFlow = ActionProcessFlowInfo(controllerClass, actionMethod) 146 | val matchers = readPath(path) 147 | fun checkPf(property: KProperty1, MutableList>>): Array> = 148 | ArrayList>() 149 | .apply { 150 | val checkPi = { it: ProcessInfo -> 151 | if (actionName !in it.except && it.only.isNotEmpty() && actionName in it.only) add(it) 152 | } 153 | property.get(rootRouter).forEach(checkPi) 154 | property.get(controllerFlow).forEach(checkPi) 155 | property.get(actionFlow).forEach(checkPi) 156 | sortBy { it.priority } 157 | } 158 | .map { it.invoker } 159 | .toTypedArray() 160 | 161 | // actionFlow.creator = ActionInvokerCreator { 162 | // BotActionInvoker( 163 | // channels, 164 | // matchers, 165 | // BotMethodInvoker(actionMethod, instanceGetter), 166 | // checkPf(ProcessFlowInfo::beforeProcesses), 167 | // checkPf(ProcessFlowInfo::afterProcesses), 168 | // checkPf(ProcessFlowInfo::catchProcesses) 169 | // ) 170 | // } 171 | return actionFlow 172 | } 173 | 174 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/controller/BotMethodInvoker.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.controller 2 | 3 | import rain.controller.ControllerInstanceGetter 4 | import rain.controller.simple.SimpleKJReflectMethodInvoker 5 | import yuq.controller.BotActionContext 6 | import java.lang.reflect.Method 7 | 8 | open class BotMethodInvoker( 9 | method: Method, 10 | instance: ControllerInstanceGetter 11 | ) : SimpleKJReflectMethodInvoker Any?>(method, instance) { 12 | 13 | override fun getParam(param: MethodParam<(BotActionContext) -> Any?>, context: BotActionContext): Any? { 14 | 15 | TODO("Not yet implemented") 16 | } 17 | 18 | override fun initParam(method: Method, params: Array Any?>>) { 19 | TODO("Not yet implemented") 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/controller/MatcherItem.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.controller 2 | 3 | import com.icecreamqaq.yuq.message.MessageItem 4 | import com.icecreamqaq.yuq.message.MessageItemChain 5 | import com.icecreamqaq.yuq.message.Text 6 | 7 | class MatcherItem(private val items: MessageItemChain) { 8 | 9 | var end = false 10 | 11 | var currentItemIndex = 0 12 | var currentItemMatchIndex = 0 13 | 14 | var currentItem: MessageItem = items[0] 15 | var currentString: String? = (currentItem as? Text)?.text 16 | 17 | fun changeItem(index: Int) { 18 | if (index >= items.size) { 19 | end = true 20 | return 21 | } 22 | currentItem = items[index] 23 | currentString = (currentItem as? Text)?.text 24 | currentItemMatchIndex = 0 25 | } 26 | 27 | fun nextItem() { 28 | changeItem(currentItemIndex + 1) 29 | } 30 | 31 | fun mark() = currentItemIndex to currentItemMatchIndex 32 | 33 | fun reMark(mark: Pair) { 34 | changeItem(mark.first) 35 | 36 | currentItemMatchIndex = mark.second 37 | } 38 | 39 | fun isNext(item: String): Boolean { 40 | if (end) return false 41 | if (currentString == null) return false 42 | if (item.length > currentString!!.length - currentItemMatchIndex) return false 43 | if (currentString!!.substring(currentItemMatchIndex, currentItemMatchIndex + item.length) == item) { 44 | currentItemMatchIndex += item.length 45 | 46 | if (currentItemMatchIndex == currentString!!.length) nextItem() 47 | 48 | return true 49 | } 50 | 51 | return true 52 | } 53 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/controller/MessageChannel.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.controller 2 | 3 | enum class MessageChannel(val channel: String) { 4 | // 群聊消息 5 | Group("Group"), 6 | // 好友消息 7 | Friend("Friend"), 8 | // 群临时会话 9 | GroupTemporary("GroupTemporary"), 10 | // 频道消息 11 | Guild("Guild"), 12 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/controller/router/BotRootRouter.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.controller.router 2 | 3 | import yuq.controller.BotActionContext 4 | import rain.controller.ActionInfo 5 | import rain.controller.RootRouter 6 | import yuq.controller.router.BotRouter 7 | 8 | class BotRootRouter( 9 | router: BotRouter, 10 | actions: List> 11 | ) : RootRouter(router, actions) -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/controller/router/BotRouter.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.controller.router 2 | 3 | import yuq.controller.BotActionContext 4 | import yuq.controller.BotActionInvoker 5 | import yuq.controller.MatcherItem 6 | import rain.controller.Router 7 | import yuq.controller.router.BotRouter 8 | import yuq.controller.router.RouterMatcher 9 | 10 | class BotRouter(val matcher: RouterMatcher) : Router { 11 | 12 | val subRouters = ArrayList() 13 | val actions = ArrayList() 14 | 15 | suspend operator fun invoke(context: BotActionContext): Boolean { 16 | val mi = context.matcherItem 17 | val mark = mi.mark() 18 | 19 | if (subRouters.any(mi, mark) { it.matcher(context) && it(context) }) return true 20 | // return actions.any(mi, mark) { it(context) } 21 | return false 22 | } 23 | 24 | inline fun Iterable.any(mi: MatcherItem, mark: Pair, predicate: (T) -> Boolean): Boolean { 25 | if (this is Collection && isEmpty()) return false 26 | for (element in this) { 27 | mi.reMark(mark) 28 | if (predicate(element)) return true 29 | } 30 | return false 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/controller/router/RouterMatcher.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.controller.router 2 | 3 | import yuq.controller.BotActionContext 4 | 5 | fun interface RouterMatcher { 6 | operator fun invoke(context: BotActionContext): Boolean 7 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/controller/router/matchers.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.controller.router 2 | 3 | import yuq.controller.BotActionContext 4 | import yuq.controller.router.RouterMatcher 5 | 6 | class StaticMatcher(val str: String) : RouterMatcher { 7 | override fun invoke(context: BotActionContext): Boolean { 8 | return context.matcherItem.isNext(str) 9 | } 10 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/controller/type.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.controller 2 | 3 | import yuq.controller.router.BotRouter 4 | import rain.controller.RootRouterProcessFlowInfo 5 | import yuq.controller.BotActionContext 6 | 7 | typealias BotRootInfo = RootRouterProcessFlowInfo -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/entity/at.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.entity 2 | 3 | data class MessageAt(val id: Long, val newLine: Boolean = false) -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/error/err.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.error 2 | 3 | import com.icecreamqaq.yuq.message.Message 4 | 5 | open class YuQException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) 6 | open class YuQRuntimeException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause) 7 | 8 | open class ImageTypedException(message: String) : YuQRuntimeException(message) 9 | 10 | open class WaitNextMessageTimeoutException : YuQRuntimeException() 11 | 12 | open class SendMessageFailedByCancel : YuQRuntimeException("消息发送失败,发送消息被拦截。") 13 | open class SendMessageFailedByTimeout : YuQRuntimeException("消息发送完成,但是接收消息失败,可能被服务端拒绝广播。") 14 | 15 | open class PermissionDeniedException : YuQRuntimeException("权限不足。") 16 | 17 | class MessageThrowable(val c: Message) : RuntimeException() -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/event/BotEvent.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.event 2 | 3 | import com.icecreamqaq.yuq.Bot 4 | import com.icecreamqaq.yuq.contact.* 5 | import yuq.controller.BotActionContext 6 | import com.icecreamqaq.yuq.message.Message 7 | import com.icecreamqaq.yuq.message.MessageSource 8 | import rain.api.event.Event 9 | import rain.event.events.AbstractCancelAbleEvent 10 | import rain.event.events.CancelAbleEvent 11 | 12 | interface BotEvent : Event { 13 | val bot: Bot 14 | } 15 | 16 | 17 | open class MessageRecallEvent( 18 | open val sender: Contact, open val operator: Contact, val messageId: Int 19 | ) : BotEvent { 20 | override val bot: Bot 21 | get() = sender.bot 22 | } 23 | 24 | open class PrivateRecallEvent(sender: Contact, operator: Contact, messageId: Int) : 25 | MessageRecallEvent(sender, operator, messageId) 26 | 27 | open class GroupRecallEvent( 28 | val group: Group, 29 | override val sender: GroupMember, 30 | override val operator: GroupMember, 31 | messageId: Int 32 | ) : MessageRecallEvent(sender, operator, messageId) 33 | 34 | open class FriendListEvent(override val bot: Bot) : BotEvent 35 | open class FriendAddEvent(val friend: Friend) : FriendListEvent(friend.bot) 36 | open class FriendDeleteEvent(val friend: Friend) : FriendListEvent(friend.bot) 37 | 38 | open class GroupListEvent(override val bot: Bot) : BotEvent 39 | open class BotJoinGroupEvent(val group: Group) : GroupListEvent(group.bot) 40 | 41 | /*** 42 | * com.icecreamqaq.bot.Bot 从某个群离开。 43 | * 当事件响应前,group 就已经从列表中被移出。 44 | */ 45 | open class BotLeaveGroupEvent(val group: Group) : GroupListEvent(group.bot) { 46 | /*** 47 | * com.icecreamqaq.bot.Bot 主动退出某群。 48 | */ 49 | open class Leave(group: Group) : BotLeaveGroupEvent(group) 50 | 51 | /*** 52 | * com.icecreamqaq.bot.Bot 因为某些特殊原因离开某群(其他客户端主动退出,群解散,群被强制解散等等) 53 | */ 54 | open class Other(group: Group) : BotLeaveGroupEvent(group) 55 | 56 | /*** 57 | * com.icecreamqaq.bot.Bot 被某群移出。 58 | */ 59 | open class Kick(val operator: GroupMember) : BotLeaveGroupEvent(operator.group) 60 | } 61 | 62 | open class NewRequestEvent(override val bot: Bot, val message: String) : AbstractCancelAbleEvent(), BotEvent { 63 | var accept: Boolean? = null 64 | var rejectMessage: String = "" 65 | } 66 | 67 | open class NewFriendRequestEvent( 68 | bot: Bot, 69 | val qq: UserInfo, 70 | val group: Group?, 71 | message: String 72 | ) : NewRequestEvent(bot, message) 73 | 74 | open class GroupInviteEvent(bot: Bot, val group: GroupInfo, val qq: UserInfo, message: String) : 75 | NewRequestEvent(bot, message) 76 | 77 | open class GroupMemberRequestEvent( 78 | val group: Group, val qq: UserInfo, message: String 79 | ) : NewRequestEvent(group.bot, message) { 80 | val blackList = false 81 | } 82 | 83 | open class GroupMemberEvent(val group: Group, val member: GroupMember) : BotEvent { 84 | override val bot: Bot 85 | get() = group.bot 86 | } 87 | 88 | open class GroupMemberJoinEvent(group: Group, member: GroupMember) : GroupMemberEvent(group, member) { 89 | open class Join(group: Group, member: GroupMember) : GroupMemberJoinEvent(group, member) 90 | open class Invite(group: Group, member: GroupMember, val inviter: GroupMember) : GroupMemberJoinEvent(group, member) 91 | } 92 | 93 | @Deprecated("群事件结构调整,使得命名语义更加清晰。", ReplaceWith("GroupMemberJoinEvent.Invite")) 94 | open class GroupMemberInviteEvent(group: Group, member: GroupMember, inviter: GroupMember) : 95 | GroupMemberJoinEvent.Invite(group, member, inviter) 96 | 97 | 98 | open class GroupMemberLeaveEvent(group: Group, member: GroupMember) : GroupMemberEvent(group, member) { 99 | open class Leave(group: Group, member: GroupMember) : GroupMemberLeaveEvent(group, member) 100 | open class Kick(group: Group, member: GroupMember, val operator: GroupMember) : GroupMemberLeaveEvent(group, member) 101 | } 102 | 103 | @Deprecated("群事件结构调整,使得命名语义更加清晰。", ReplaceWith("GroupMemberLeaveEvent.Kick")) 104 | open class GroupMemberKickEvent(group: Group, member: GroupMember, operator: GroupMember) : 105 | GroupMemberLeaveEvent.Kick(group, member, operator) 106 | 107 | open class GroupBanMemberEvent(group: Group, member: GroupMember, val operator: GroupMember, val time: Int) : 108 | GroupMemberEvent(group, member) 109 | 110 | open class GroupUnBanMemberEvent(group: Group, member: GroupMember, val operator: GroupMember) : 111 | GroupMemberEvent(group, member) 112 | 113 | open class GroupBanBotEvent(group: Group, member: GroupMember, val operator: GroupMember, val time: Int) : 114 | GroupMemberEvent(group, member) 115 | 116 | open class GroupUnBanBotEvent(group: Group, member: GroupMember, val operator: GroupMember) : 117 | GroupMemberEvent(group, member) 118 | 119 | open class ContextSessionCreateEvent(override val bot: Bot, session: ContactSession) : BotEvent 120 | open class ActionContextInvokeEvent(val context: BotActionContext) : AbstractCancelAbleEvent() { 121 | open class Per(context: BotActionContext) : ActionContextInvokeEvent(context) 122 | 123 | open class Post(context: BotActionContext, val routerMatchFlag: Boolean) : ActionContextInvokeEvent(context) 124 | } 125 | 126 | open class SendMessageEvent(val sendTo: Contact, val message: Message) : BotEvent { 127 | open class Per(sendTo: Contact, message: Message) : SendMessageEvent(sendTo, message), CancelAbleEvent { 128 | override var isCanceled: Boolean = false 129 | } 130 | 131 | open class Post(sendTo: Contact, message: Message, val messageSource: MessageSource) : 132 | SendMessageEvent(sendTo, message) 133 | 134 | override val bot: Bot 135 | get() = sendTo.bot 136 | } 137 | 138 | // 消息发送未达预期事件。 139 | open class SendMessageInvalidEvent( 140 | val sendTo: Contact, 141 | val message: Message 142 | ) : BotEvent { 143 | // 发送消息被取消事件。 144 | class ByCancel(sendTo: Contact, message: Message) : SendMessageInvalidEvent(sendTo, message) 145 | 146 | /*** 读取发送消息超时事件。 147 | * 消息发送成功了,但是消息被拒发。 148 | * 他有别于消息发送失败,因为消息发送这个过程完成了。 149 | * 一般指消息正常上报给了服务端,但服务端拒绝广播本消息。 150 | * 也就是俗称的吞消息。 151 | */ 152 | class ByReadTimeout(sendTo: Contact, message: Message) : SendMessageInvalidEvent(sendTo, message) 153 | 154 | override val bot: Bot 155 | get() = sendTo.bot 156 | } 157 | 158 | 159 | open class ClickEvent(open val operator: Contact, val action: String, val suffix: String) : BotEvent { 160 | override val bot: Bot 161 | get() = operator.bot 162 | } 163 | 164 | open class ClickBotEvent(operator: Contact, action: String, suffix: String) : ClickEvent(operator, action, suffix) { 165 | open class Private(operator: Contact, action: String, suffix: String) : ClickBotEvent(operator, action, suffix) { 166 | open class FriendClick(override val operator: Friend, action: String, suffix: String) : 167 | Private(operator, action, suffix) 168 | 169 | open class TempClick(override val operator: GroupMember, action: String, suffix: String) : 170 | Private(operator, action, suffix) 171 | } 172 | 173 | open class Group(override val operator: GroupMember, action: String, suffix: String) : 174 | ClickBotEvent(operator, action, suffix) 175 | } 176 | 177 | open class AtBotEvent(open val type: Int, open val sender: Contact, open val source: Contact) : BotEvent { 178 | open class ByGroup(type: Int, override val sender: GroupMember, override val source: Group) : 179 | AtBotEvent(type, sender, source) 180 | 181 | open class ByGuild(type: Int, override val sender: GuildMember, override val source: Channel, val guild: Guild) : 182 | AtBotEvent(type, sender, source) 183 | 184 | open class ByPrivate(type: Int, sender: Contact, source: Contact) : AtBotEvent(type, sender, source) { 185 | open class ByFriend(type: Int, override val sender: Friend, override val source: Friend) : 186 | ByPrivate(type, sender, source) 187 | 188 | open class ByTemp(type: Int, override val sender: GroupMember, override val source: GroupMember) : 189 | ByPrivate(type, sender, source) 190 | } 191 | 192 | override val bot: Bot 193 | get() = sender.bot 194 | } 195 | 196 | open class ClickSomeBodyEvent(operator: Contact, open val target: Contact, action: String, suffix: String) : 197 | ClickEvent(operator, action, suffix) { 198 | open class Private(operator: Contact, target: Contact, action: String, suffix: String) : 199 | ClickSomeBodyEvent(operator, target, action, suffix) 200 | 201 | open class Group( 202 | override val operator: GroupMember, 203 | override val target: GroupMember, 204 | action: String, 205 | suffix: String 206 | ) : ClickSomeBodyEvent(operator, target, action, suffix) 207 | } 208 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/event/BotStatusEvent.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.event 2 | 3 | import com.icecreamqaq.yuq.Bot 4 | 5 | open class BotStatusEvent(override val bot: Bot) : BotEvent{ 6 | open class Online(bot: Bot) : BotStatusEvent(bot) 7 | open class Offline(bot: Bot) : BotStatusEvent(bot) 8 | open class ReOnline(bot: Bot) : BotStatusEvent(bot) 9 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/event/YuQEvent.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.event 2 | 3 | import rain.api.event.Event 4 | 5 | interface YuQApplicationStatusChanged : Event { 6 | open class Started : YuQApplicationStatusChanged 7 | open class Stopping : YuQApplicationStatusChanged 8 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/event/message.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.event 2 | 3 | import com.icecreamqaq.yuq.Bot 4 | import com.icecreamqaq.yuq.contact.* 5 | import com.icecreamqaq.yuq.message.Message 6 | import rain.event.events.AbstractCancelAbleEvent 7 | 8 | 9 | open class MessageEvent(open val sender: Contact, val message: Message) : AbstractCancelAbleEvent(), BotEvent { 10 | override val bot: Bot 11 | get() = sender.bot 12 | } 13 | 14 | open class GroupMessageEvent(override val sender: GroupMember, val group: Group, message: Message) : 15 | MessageEvent(sender, message) 16 | 17 | open class GuildMessageEvent( 18 | override val sender: GuildMember, 19 | val guild: Guild, 20 | val channel: Channel, 21 | message: Message 22 | ) : MessageEvent(sender, message) 23 | 24 | open class PrivateMessageEvent(sender: Contact, message: Message) : MessageEvent(sender, message) { 25 | open class FriendMessage(override val sender: Friend, message: Message) : PrivateMessageEvent(sender, message) 26 | open class TempMessage(override val sender: GroupMember, message: Message) : PrivateMessageEvent(sender, message) 27 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/message/Message.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.message 2 | 3 | import com.icecreamqaq.yuq.annotation.NoRecommendation 4 | import com.icecreamqaq.yuq.entity.MessageAt 5 | import com.icecreamqaq.yuq.error.MessageThrowable 6 | import com.icecreamqaq.yuq.message.Image.Companion.toFlash 7 | import com.icecreamqaq.yuq.message.Text.Companion.toText 8 | import com.icecreamqaq.yuq.mif 9 | import java.io.File 10 | 11 | interface MessagePlus { 12 | operator fun plus(item: MessageItem): MessageItemChain 13 | operator fun plus(item: String): MessageItemChain 14 | operator fun plus(item: Message): MessageItemChain 15 | operator fun plus(item: MessageItemChain): MessageItemChain 16 | } 17 | 18 | interface SendAble { 19 | fun toMessage(): Message 20 | fun toThrowable(): MessageThrowable = MessageThrowable(toMessage()) 21 | } 22 | 23 | 24 | open class Message(val body: MessageItemChain = MessageItemChain()) : SendAble, IMessageItemChain by body { 25 | 26 | var id: Int? = null 27 | 28 | /*** 29 | * 消息源信息。 30 | * 与消息本身无关,如果这条消息是收到的消息,则会附带本参数。 31 | * 消息源是定位消息在腾讯所在位置的记录,用于消息撤回,回复等操作。 32 | * 当你将消息发出时,并不会将发出消息的消息源写到本参数,而是 sendMessage 方法返回的消息源。 33 | */ 34 | var source: MessageSource? = null 35 | 36 | var codeStr: String = "" 37 | get() { 38 | if (field == "") field = toCodeString() 39 | return field 40 | } 41 | var reply: MessageSource? = null 42 | var at: MessageAt? = null 43 | 44 | @NoRecommendation 45 | lateinit var sourceMessage: Any 46 | 47 | lateinit var path: List 48 | 49 | private var lineQ_: MessageLineQ? = null 50 | fun lineQ(): MessageLineQ { 51 | if (lineQ_ == null) lineQ_ = MessageLineQ(this) 52 | return lineQ_!! 53 | } 54 | 55 | /*** 56 | * 本条消息在发出后一段时间撤回,单位:毫秒。 57 | */ 58 | var recallDelay: Long? = null 59 | 60 | fun toLogString(): String { 61 | val sb = StringBuilder("(") 62 | if (reply != null) sb.append("Reply To: ${reply!!.id}, ") 63 | if (at != null) sb.append("At them${if (at!!.newLine) " \\n" else ""}, ") 64 | if (body.size > 0) { 65 | sb.append("[ ${body[0].logString}") 66 | for (i in 1 until body.size) { 67 | sb.append(", ${body[i].logString}") 68 | } 69 | sb.append(" ]") 70 | } 71 | sb.append(")") 72 | return sb.toString() 73 | } 74 | 75 | fun recall(): Int { 76 | return source!!.recall() 77 | } 78 | 79 | override fun toString(): String { 80 | return toLogString() 81 | } 82 | 83 | open fun bodyEquals(other: Any?): Boolean { 84 | if (other !is Message) return false 85 | if (body.size != other.body.size) return false 86 | for ((i, item) in body.withIndex()) { 87 | val oi = other.body[i] 88 | if (item != oi) return false 89 | } 90 | return true 91 | } 92 | 93 | override fun toMessage() = this 94 | 95 | operator fun plus(item: MessageItem): Message { 96 | body.append(item) 97 | return this 98 | } 99 | 100 | operator fun plus(item: String): Message { 101 | body.append(item.toText()) 102 | return this 103 | } 104 | 105 | operator fun plus(item: Message): Message { 106 | body.append(item.body) 107 | return this 108 | } 109 | 110 | operator fun plus(item: MessageItemChain): Message { 111 | body.append(item) 112 | return this 113 | } 114 | 115 | companion object { 116 | 117 | fun Message.firstString(): String { 118 | for (item in body) { 119 | if (item is Text) return item.text 120 | } 121 | error("消息不包含任何一个文本串。") 122 | } 123 | 124 | fun Message.toCodeString(): String { 125 | val sb = StringBuilder() 126 | if (reply != null) sb.append("") 127 | 128 | for (item in body) { 129 | sb.append( 130 | when (item) { 131 | is Text -> item.text 132 | is At -> "" 133 | is Face -> "" 134 | is Image -> "" else ">"}" 135 | is XmlEx -> "", "\\>") 137 | }>" 138 | is JsonEx -> "" 139 | is Voice -> "" 140 | else -> "" 141 | } 142 | ) 143 | } 144 | return sb.toString() 145 | } 146 | 147 | fun String.toMessageByRainCode(): Message { 148 | val codeStart = "") 170 | m.clear() 171 | rf = false 172 | rc = false 173 | continue 174 | } 175 | val type = code[1] 176 | val data = code[2] 177 | message += when (type) { 178 | "At" -> mif.at(data.toLong()) 179 | "Face" -> mif.face(data.toInt()) 180 | "Image" -> { 181 | val ps = data.split(",") 182 | var url = false 183 | var file = false 184 | var flash = false 185 | val id = ps[0] 186 | for (i in 1 until ps.size) { 187 | val p = ps[i] 188 | if (p == "url") url = true 189 | if (p == "file") file = true 190 | if (p == "flash") flash = true 191 | } 192 | val p = if (file) mif.imageByFile(File(id)) 193 | else if (id.startsWith("http", true) || url) mif.imageByUrl(id) 194 | else mif.imageById(id) 195 | if (flash) p.toFlash() 196 | else p 197 | } 198 | "Xml" -> { 199 | val a = data.split(",", ignoreCase = false, limit = 2) 200 | val id = a[0].toInt() 201 | val value = a[1].replace("&&<&&&", "<").replace("&&>&&&", ">") 202 | mif.xmlEx(id, value) 203 | } 204 | "Json" -> mif.jsonEx(data) 205 | else -> mif.text("$codeStr>") 206 | } 207 | 208 | m.clear() 209 | rf = false 210 | rc = false 211 | } 212 | } else { 213 | if (c == '<') { 214 | t.append(m.toString()) 215 | m.clear() 216 | m.append('<') 217 | continue 218 | } 219 | m.append(c) 220 | if (m.length >= 6) { 221 | val ms = m.toString() 222 | if (codeStart == ms) rc = true 223 | else { 224 | t.append(ms) 225 | m.clear() 226 | rf = false 227 | rc = false 228 | continue 229 | } 230 | } 231 | } 232 | } else { 233 | if (c != '<') t.append(c) 234 | else { 235 | m.append(c) 236 | rf = true 237 | } 238 | } 239 | 240 | } 241 | 242 | if (m.isNotEmpty()) t.append(m) 243 | if (t.isNotEmpty()) message += mif.text(t.toString()) 244 | 245 | return message.toMessage() 246 | } 247 | 248 | fun String.toMessage() = this.toText().toMessage() 249 | 250 | fun String.toChain() = MessageItemChain().append(this.toText()) 251 | 252 | } 253 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/message/MessageFactory.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.message 2 | 3 | import com.icecreamqaq.yuq.contact.GroupMember 4 | import com.icecreamqaq.yuq.message.Message.Companion.toMessageByRainCode 5 | import com.icecreamqaq.yuq.mif 6 | import java.awt.image.BufferedImage 7 | import java.io.File 8 | import java.io.InputStream 9 | 10 | open class MessageLineQ(val message: Message = Message()) : SendAble { 11 | fun plus(item: MessageItem): MessageLineQ { 12 | message.plus(item) 13 | return this 14 | } 15 | 16 | fun plus(chain: MessageItemChain): MessageLineQ { 17 | message.plus(chain) 18 | return this 19 | } 20 | 21 | fun plus(chain: Message): MessageLineQ { 22 | message.plus(chain) 23 | return this 24 | } 25 | 26 | fun line() = plus(mif.text("\n")) 27 | fun text(text: String) = plus(mif.text(text)) 28 | fun textLine(text: String) = plus(mif.text("$text\n")) 29 | fun rainCode(codeString: String) = plus(codeString.toMessageByRainCode()) 30 | 31 | fun at(qq: Long) = plus(mif.at(qq)) 32 | fun at(member: GroupMember) = plus(mif.at(member)) 33 | 34 | fun face(id: Int) = plus(mif.face(id)) 35 | 36 | fun imageByFile(file: File) = plus(mif.imageByFile(file)) 37 | 38 | fun imageByUrl(url: String) = plus(mif.imageByUrl(url)) 39 | 40 | fun imageById(id: String) = plus(mif.imageById(id)) 41 | 42 | fun imageByBufferedImage(bufferedImage: BufferedImage) = plus(mif.imageByBufferedImage(bufferedImage)) 43 | 44 | fun imageByInputStream(inputStream: InputStream) = plus(mif.imageByInputStream(inputStream)) 45 | 46 | fun imageToFlash(image: Image) = plus(mif.imageToFlash(image)) 47 | 48 | fun voiceByInputStream(inputStream: InputStream) = plus(mif.voiceByInputStream(inputStream)) 49 | 50 | fun xmlEx(serviceId: Int, value: String) = plus(mif.xmlEx(serviceId, value)) 51 | 52 | fun jsonEx(value: String) = plus(mif.jsonEx(value)) 53 | 54 | fun recallDelay(time: Long): MessageLineQ { 55 | message.recallDelay = time 56 | return this 57 | } 58 | 59 | override fun toMessage() = message 60 | } 61 | 62 | fun buildMessage(body: MessageLineQ.() -> Unit): Message = MessageLineQ().apply(body).message -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/message/MessageItem.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.message 2 | 3 | interface MessageItem : MessagePlus, SendAble { 4 | 5 | val logString: String 6 | 7 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/message/MessageItemBase.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.message 2 | 3 | abstract class MessageItemBase : MessageItem, SendAble { 4 | 5 | override operator fun plus(item: MessageItem): MessageItemChain = toItemChain() + item 6 | override operator fun plus(item: String): MessageItemChain = toItemChain() + item 7 | override operator fun plus(item: Message): MessageItemChain = toItemChain() + item 8 | override fun plus(item: MessageItemChain): MessageItemChain = item.unshift(this) 9 | override fun toMessage(): Message = this.toItemChain().toMessage() 10 | 11 | fun toItemChain() = MessageItemChain().append(this) 12 | 13 | abstract fun equal(other: MessageItem): Boolean 14 | 15 | override fun equals(other: Any?): Boolean { 16 | if (other == null) return false 17 | if (this === other) return true 18 | if (other !is MessageItem) return false 19 | return equal(other) 20 | } 21 | 22 | override fun hashCode(): Int { 23 | return javaClass.hashCode() 24 | } 25 | 26 | override fun toString() = logString 27 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/message/MessageItemChain.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.message 2 | 3 | 4 | data class ChainItem(var item: MessageItem, var next: ChainItem? = null, var previous: ChainItem? = null) 5 | 6 | interface IMessageItemChain { 7 | 8 | operator fun get(index: Int): MessageItem 9 | fun unshift(item: MessageItem): MessageItemChain 10 | fun append(item: MessageItem): MessageItemChain 11 | fun append(chain: MessageItemChain): MessageItemChain 12 | 13 | fun first(type: Class): T? 14 | fun last(type: Class): T? 15 | fun find(type: Class): List 16 | 17 | fun first(): MessageItem? 18 | fun last(): MessageItem? 19 | 20 | } 21 | 22 | inline fun IMessageItemChain.firstBy() = first(T::class.java) 23 | inline fun IMessageItemChain.lastBy() = last(T::class.java) 24 | inline fun IMessageItemChain.findBy() = find(T::class.java) 25 | 26 | class MessageItemChain : IMessageItemChain, List, MessagePlus, SendAble { 27 | 28 | private var _size = 0 29 | override val size get() = _size 30 | private var first: ChainItem? = null 31 | private var last: ChainItem? = null 32 | 33 | override fun first() = first?.item 34 | override fun last() = last?.item 35 | 36 | override fun first(type: Class): T? = firstOrNull { type.isInstance(it) } as? T 37 | override fun last(type: Class): T? = lastOrNull { type.isInstance(it) } as? T 38 | override fun find(type: Class): List = filter { type.isInstance(it) } as List 39 | 40 | override fun toMessage() = Message(this) 41 | 42 | override fun unshift(item: MessageItem): MessageItemChain { 43 | first = ChainItem(item, first) 44 | _size++ 45 | return this 46 | } 47 | 48 | override fun append(item: MessageItem): MessageItemChain { 49 | if (first == null) { 50 | val i = ChainItem(item) 51 | first = i 52 | last = i 53 | _size = 1 54 | } else { 55 | val i = ChainItem(item, null, last) 56 | last!!.next = i 57 | last = i 58 | _size++ 59 | } 60 | return this 61 | } 62 | 63 | override fun append(chain: MessageItemChain): MessageItemChain { 64 | if (chain.first != null) 65 | if (first == null) { 66 | first = chain.first 67 | last = chain.last 68 | _size = chain._size 69 | } else { 70 | last!!.next = chain.first 71 | last = chain.last 72 | _size += chain._size 73 | } 74 | return this 75 | } 76 | 77 | override fun plus(item: MessageItem) = append(item) 78 | 79 | override fun plus(item: String) = TODO() 80 | 81 | override fun plus(item: Message) = append(item.body) 82 | 83 | override fun plus(item: MessageItemChain) = append(item) 84 | 85 | override fun contains(element: MessageItem): Boolean { 86 | var item = first 87 | while (item != null) if (item.item == element) return true else item = item.next 88 | return false 89 | } 90 | 91 | override fun containsAll(elements: Collection): Boolean { 92 | TODO("Not yet implemented") 93 | } 94 | 95 | override fun get(index: Int): MessageItem { 96 | if (index >= size) throw ArrayIndexOutOfBoundsException(index) 97 | if (index == 0) return first!!.item 98 | var item = first 99 | for (i in 1..index) { 100 | item = item!!.next 101 | } 102 | return item!!.item 103 | } 104 | 105 | 106 | override fun indexOf(element: MessageItem): Int { 107 | var item = first 108 | for (i in 0 until size) { 109 | if (item!!.item == element) return i else item = item.next 110 | } 111 | return -1 112 | } 113 | 114 | override fun isEmpty() = first == null 115 | 116 | class ChainIterator(private val chain: MessageItemChain, i: Int = 0) : MutableListIterator { 117 | var index = 0 118 | var next = chain.first 119 | var previous: ChainItem? = null 120 | 121 | init { 122 | for (j in 1..i) next() 123 | } 124 | 125 | override fun hasNext() = next != null 126 | 127 | override fun nextIndex() = index 128 | override fun next(): MessageItem { 129 | val next = next ?: throw NoSuchElementException() 130 | 131 | val i = next.item 132 | this.index++ 133 | this.previous = next 134 | this.next = next.next 135 | 136 | return i 137 | } 138 | 139 | override fun hasPrevious() = previous != null 140 | override fun previousIndex() = if (previous == null) -1 else index - 1 141 | override fun previous(): MessageItem { 142 | val previous = previous ?: throw NoSuchElementException() 143 | 144 | val i = previous.item 145 | this.index-- 146 | this.next = previous 147 | this.previous = previous.previous 148 | 149 | return i 150 | } 151 | 152 | override fun add(element: MessageItem) { 153 | val chainItem = ChainItem(element, next, previous) 154 | if (next == null && previous == null) { 155 | chain._size = 1 156 | chain.first = chainItem 157 | chain.last = chainItem 158 | } else chain._size++ 159 | next?.previous = chainItem 160 | previous?.next = chainItem 161 | next = chainItem 162 | } 163 | 164 | override fun remove() { 165 | if (next == null) return 166 | val next = next!!.next 167 | if (previous == null) chain.first = next 168 | if (next == null) chain.last = previous 169 | previous?.next = next 170 | next?.previous = previous 171 | chain._size -= 1 172 | } 173 | 174 | override fun set(element: MessageItem) { 175 | next!!.item = element 176 | } 177 | 178 | } 179 | 180 | override fun iterator(): MutableIterator = ChainIterator(this) 181 | 182 | override fun lastIndexOf(element: MessageItem): Int { 183 | var item = first 184 | var index = -1 185 | for (i in 0 until size) { 186 | if (item!!.item == element) index = i else item = item.next 187 | } 188 | return index 189 | } 190 | 191 | override fun listIterator(): MutableListIterator = ChainIterator(this) 192 | 193 | override fun listIterator(index: Int): MutableListIterator = ChainIterator(this, index) 194 | 195 | override fun subList(fromIndex: Int, toIndex: Int): MessageItemChain { 196 | if (fromIndex < 0 || fromIndex > size) throw ArrayIndexOutOfBoundsException(fromIndex) 197 | if (toIndex < fromIndex || fromIndex > size) throw ArrayIndexOutOfBoundsException(toIndex) 198 | val list = MessageItemChain() 199 | var item = first 200 | for (i in 0..toIndex) { 201 | if (i in fromIndex..toIndex) list.append(item!!.item) 202 | item = item!!.next 203 | } 204 | return list 205 | } 206 | 207 | 208 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/message/MessageItemFactory.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.message 2 | 3 | import com.icecreamqaq.yuq.contact.GroupMember 4 | import rain.function.IO 5 | import java.awt.image.BufferedImage 6 | import java.io.File 7 | import java.io.InputStream 8 | import javax.imageio.ImageIO 9 | 10 | /*** MessageItemFactory 为具体的消息体工厂。 11 | * @author IceCream 12 | */ 13 | class MessageItemFactory { 14 | 15 | /*** 创建一段纯文本消息体。 16 | * @param text 纯文本内容。 17 | */ 18 | fun text(text: String): Text = Text(text) 19 | 20 | /*** 创建一个 @ 内容 21 | * @param qq 欲 At 的目标 QQ 号码。 22 | */ 23 | fun at(qq: Long): At = At(qq) 24 | 25 | /*** 创建一个 @ 内容 26 | * @param member 欲 At 的目标 QQ 号码。 27 | */ 28 | fun at(member: GroupMember): At = AtByMember(member) 29 | 30 | /*** 创建一个基础的 QQ 表情。 31 | * @param id 表情Id。 32 | */ 33 | fun face(id: Int): Face = Face(id) 34 | 35 | /*** 使用文件发送一个图片。 36 | * @param file 图片的位置(File 对象)。 37 | */ 38 | fun imageByFile(file: File): Image = OfflineImage(file) 39 | 40 | /*** 发送一个网络图片 41 | * @param url 图片的下载地址。 42 | */ 43 | // fun imageByUrl(url: String): Image = OfflineImage(web.download(url, file = null)) 44 | 45 | 46 | fun imageByBufferedImage(bufferedImage: BufferedImage, format: String = "PNG"): Image = 47 | OfflineImage(IO.tmpFile().also { ImageIO.write(bufferedImage, format, it) }) 48 | 49 | fun imageByInputStream(inputStream: InputStream): Image = 50 | OfflineImage(IO.tmpFile().also { IO.copy(inputStream, it.outputStream()) }) 51 | 52 | fun imageByByteArray(byteArray: ByteArray) = 53 | OfflineImage(IO.tmpFile().also { it.writeBytes(byteArray) }) 54 | 55 | fun imageToFlash(image: Image): FlashImage = 56 | FlashImage(image) 57 | 58 | fun imageById(id: String): Image = TODO() 59 | 60 | 61 | /*** 发送一段语音 62 | * @param file 语音的位置(File 对象)。 63 | */ 64 | fun voiceByFile(file: File): Voice = OfflineVoice(file) 65 | fun voiceByInputStream(inputStream: InputStream): Voice = 66 | voiceByFile(IO.tmpFile().also { IO.copy(inputStream, it.outputStream()) }) 67 | 68 | fun voiceByByteArray(byteArray: ByteArray) = 69 | voiceByFile(IO.tmpFile().also { it.writeBytes(byteArray) }) 70 | 71 | /*** 发送一个 Xml 消息(卡片消息) 72 | * @param serviceId serviceId 73 | * @param value 具体的 xml 内容。 74 | */ 75 | fun xmlEx(serviceId: Int, value: String): XmlEx = XmlEx(serviceId, value) 76 | 77 | /*** 发送一个 Json 消息。 78 | * @param value Json 文本。 79 | */ 80 | fun jsonEx(value: String): JsonEx = JsonEx(value) 81 | 82 | // fun messagePackage(flag: Int, body: MutableList): MessagePackage 83 | 84 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/message/MessagePackage.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.message 2 | 3 | interface MessagePackage : MessageItem { 4 | /*** 5 | * 发送方式 6 | * 0 -> 通过转发多条消息方式发送。 7 | * 10 -> 将所有内容做为一条消息通过分片消息发送。 8 | * 20 -> 将所有内容组合为一条消息,通过长消息方式发送。 9 | * 注: 10 | * 当 Runtime 不支持分片消息时会转为普通消息发送。 11 | * 当 Runtime 不支持转发多条消息时,将发送失败。 12 | * 当发送方式为转发多条时,如果 Message 未提供 source 或是内容物为 MessageItemChain 则将为本条消息发送者设置为机器人。 13 | */ 14 | var type: Int 15 | 16 | val body: MutableList 17 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/message/MessageSource.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.message 2 | 3 | import com.icecreamqaq.yuq.contact.* 4 | import com.icecreamqaq.yuq.yuq 5 | 6 | interface MessageSource { 7 | val id: Int 8 | val sender: Long 9 | val sendTime: Long 10 | val sendTo: Long 11 | val liteMsg: String 12 | 13 | fun recall(): Int 14 | } 15 | 16 | interface FriendMessageSource : MessageSource { 17 | 18 | } 19 | 20 | interface GroupMessageSource : MessageSource { 21 | val groupCode: Long 22 | } 23 | 24 | interface TempMessageSource : MessageSource { 25 | val groupCode: Long 26 | } 27 | 28 | interface GuildMessageSource : MessageSource { 29 | val guildId: Long 30 | val channelId: Long 31 | } 32 | 33 | 34 | data class MessageFailByCancel( 35 | ) : FriendMessageSource, GroupMessageSource, TempMessageSource, GuildMessageSource { 36 | override fun recall(): Int { 37 | return 0 38 | } 39 | 40 | constructor(contact: Friend, liteMsg: String) : this( 41 | -1, 42 | yuq.botId, 43 | System.currentTimeMillis(), 44 | contact.id, 45 | liteMsg, 46 | -1, 47 | -1, 48 | -1 49 | ) 50 | 51 | constructor(contact: Group, liteMsg: String) : this( 52 | -1, 53 | yuq.botId, 54 | System.currentTimeMillis(), 55 | contact.id, 56 | liteMsg, 57 | contact.id, 58 | -1, 59 | -1 60 | ) 61 | 62 | constructor(contact: GroupMember, liteMsg: String) : this( 63 | -1, 64 | yuq.botId, 65 | System.currentTimeMillis(), 66 | contact.id, 67 | liteMsg, 68 | contact.group.id, 69 | -1, 70 | -1 71 | ) 72 | 73 | constructor(contact: Channel, liteMsg: String) : this( 74 | -1, 75 | yuq.botId, 76 | System.currentTimeMillis(), 77 | contact.id, 78 | liteMsg, 79 | -1, 80 | contact.guild.id, 81 | contact.id 82 | ) 83 | 84 | companion object { 85 | fun create(contact: Contact, liteMsg: String): MessageFailByCancel = 86 | when(contact){ 87 | is Friend -> MessageFailByCancel(contact, liteMsg) 88 | is Group -> MessageFailByCancel(contact, liteMsg) 89 | is GroupMember -> MessageFailByCancel(contact, liteMsg) 90 | is Channel -> MessageFailByCancel(contact, liteMsg) 91 | else -> error("联系人 $contact 可能无法创建消息。") 92 | } 93 | 94 | } 95 | } 96 | 97 | data class MessageFailByReadTimeOut( 98 | override val id: Int, 99 | override val sender: Long, 100 | override val sendTime: Long, 101 | override val sendTo: Long, 102 | override val liteMsg: String, 103 | override val groupCode: Long, 104 | override val guildId: Long, 105 | override val channelId: Long, 106 | ) : FriendMessageSource, GroupMessageSource, TempMessageSource, GuildMessageSource { 107 | override fun recall(): Int { 108 | return 0 109 | } 110 | 111 | constructor(contact: Friend, liteMsg: String) : this( 112 | -1, 113 | yuq.botId, 114 | System.currentTimeMillis(), 115 | contact.id, 116 | liteMsg, 117 | -1, 118 | -1, 119 | -1 120 | ) 121 | 122 | constructor(contact: Group, liteMsg: String) : this( 123 | -1, 124 | yuq.botId, 125 | System.currentTimeMillis(), 126 | contact.id, 127 | liteMsg, 128 | contact.id, 129 | -1, 130 | -1 131 | ) 132 | 133 | constructor(contact: GroupMember, liteMsg: String) : this( 134 | -1, 135 | yuq.botId, 136 | System.currentTimeMillis(), 137 | contact.id, 138 | liteMsg, 139 | contact.group.id, 140 | -1, 141 | -1 142 | ) 143 | 144 | constructor(contact: Channel, liteMsg: String) : this( 145 | -1, 146 | yuq.botId, 147 | System.currentTimeMillis(), 148 | contact.id, 149 | liteMsg, 150 | -1, 151 | contact.guild.id, 152 | contact.id 153 | ) 154 | 155 | companion object { 156 | fun create(contact: Contact, liteMsg: String): MessageFailByReadTimeOut = 157 | when(contact){ 158 | is Friend -> MessageFailByReadTimeOut(contact, liteMsg) 159 | is Group -> MessageFailByReadTimeOut(contact, liteMsg) 160 | is GroupMember -> MessageFailByReadTimeOut(contact, liteMsg) 161 | is Channel -> MessageFailByReadTimeOut(contact, liteMsg) 162 | else -> error("联系人 $contact 可能无法创建消息。") 163 | } 164 | 165 | } 166 | } 167 | 168 | data class FakeMessageSource( 169 | override val id: Int, 170 | override val sender: Long, 171 | override val sendTime: Long, 172 | override val sendTo: Long, 173 | override val liteMsg: String, 174 | override val groupCode: Long, 175 | override val guildId: Long, 176 | override val channelId: Long, 177 | ) : FriendMessageSource, GroupMessageSource, TempMessageSource, GuildMessageSource { 178 | override fun recall(): Int { 179 | return 0 180 | } 181 | 182 | constructor(contact: Friend, liteMsg: String) : this( 183 | -1, 184 | yuq.botId, 185 | System.currentTimeMillis(), 186 | contact.id, 187 | liteMsg, 188 | -1, 189 | -1, 190 | -1 191 | ) 192 | 193 | constructor(contact: Group, liteMsg: String) : this( 194 | -1, 195 | yuq.botId, 196 | System.currentTimeMillis(), 197 | contact.id, 198 | liteMsg, 199 | contact.id, 200 | -1, 201 | -1 202 | ) 203 | 204 | constructor(contact: GroupMember, liteMsg: String) : this( 205 | -1, 206 | yuq.botId, 207 | System.currentTimeMillis(), 208 | contact.id, 209 | liteMsg, 210 | contact.group.id, 211 | -1, 212 | -1 213 | ) 214 | 215 | constructor(contact: Channel, liteMsg: String) : this( 216 | -1, 217 | yuq.botId, 218 | System.currentTimeMillis(), 219 | contact.id, 220 | liteMsg, 221 | -1, 222 | contact.guild.id, 223 | contact.id 224 | ) 225 | 226 | companion object { 227 | fun create(contact: Contact, liteMsg: String): FakeMessageSource = 228 | when(contact){ 229 | is Friend -> FakeMessageSource(contact, liteMsg) 230 | is Group -> FakeMessageSource(contact, liteMsg) 231 | is GroupMember -> FakeMessageSource(contact, liteMsg) 232 | is Channel -> FakeMessageSource(contact, liteMsg) 233 | else -> error("联系人 $contact 可能无法创建消息。") 234 | } 235 | 236 | } 237 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/message/items.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.message 2 | 3 | import com.icecreamqaq.yuq.contact.GroupMember 4 | import com.icecreamqaq.yuq.mif 5 | import java.io.File 6 | 7 | 8 | class Text(val text: String) : MessageItemBase() { 9 | override val logString: String 10 | get() = text 11 | 12 | override fun equal(other: MessageItem): Boolean { 13 | if (other !is Text) return false 14 | return text == other.text 15 | } 16 | 17 | companion object { 18 | fun String.toText() = mif.text(this) 19 | } 20 | } 21 | 22 | open class At(val user: Long) : MessageItemBase() { 23 | 24 | override val logString: String 25 | get() = "@$user" 26 | 27 | override fun equal(other: MessageItem): Boolean { 28 | if (other !is At) return false 29 | return user == other.user 30 | } 31 | } 32 | 33 | class AtByMember(val member: GroupMember) : At(member.id) { 34 | 35 | override val logString: String 36 | get() = "@${member.nameCardOrName()}($user)" 37 | } 38 | 39 | class Face(val faceId: Int) : MessageItemBase() { 40 | 41 | override val logString: String 42 | get() = "表情: $faceId" 43 | 44 | override fun equal(other: MessageItem): Boolean { 45 | if (other !is Face) return false 46 | return faceId == other.faceId 47 | } 48 | } 49 | 50 | abstract class Image( 51 | val platform: String, 52 | val id: String, 53 | val url: String 54 | ) : MessageItemBase() { 55 | 56 | override val logString: String 57 | get() = "图片: $id" 58 | 59 | override fun equal(other: MessageItem): Boolean { 60 | if (other !is Image) return false 61 | return id == other.id 62 | } 63 | 64 | companion object { 65 | fun Image.toFlash() = mif.imageToFlash(this) 66 | } 67 | } 68 | 69 | class OnlineImage(platform: String, id: String, url: String) : Image(platform, id, url) 70 | class OfflineImage(val imageFile: File) : Image("universal", imageFile.name, "") 71 | 72 | class FlashImage(val image: Image) : Image(image.platform, image.id, image.url) { 73 | 74 | override val logString: String 75 | get() = "闪照: $id" 76 | 77 | } 78 | 79 | class XmlEx( 80 | val serviceId: Int, 81 | val value: String 82 | ) : MessageItemBase() { 83 | 84 | override val logString: String 85 | get() = "Xml: $serviceId" 86 | 87 | override fun equal(other: MessageItem): Boolean { 88 | if (other !is XmlEx) return false 89 | return value == other.value && serviceId == other.serviceId 90 | } 91 | } 92 | 93 | class JsonEx(val value: String) : MessageItemBase() { 94 | 95 | override val logString: String 96 | get() = "JSON" 97 | 98 | override fun equal(other: MessageItem): Boolean { 99 | if (other !is JsonEx) return false 100 | return value == other.value 101 | } 102 | } 103 | 104 | 105 | 106 | abstract class Voice( 107 | val platform: String, 108 | val id: String, 109 | val url: String 110 | ) : MessageItemBase() { 111 | 112 | override val logString: String 113 | get() = "语音: $id" 114 | 115 | override fun equal(other: MessageItem): Boolean { 116 | if (other !is Voice) return false 117 | return id == other.id 118 | } 119 | } 120 | 121 | class OnlineVoice(platform: String, id: String, url: String) : Voice(platform, id, url) 122 | class OfflineVoice(val voiceFile: File) : Voice("universal", voiceFile.name, "") 123 | 124 | class NoImplItem( 125 | val platformIdentifier: String, 126 | val source: Any 127 | ) : MessageItemBase() { 128 | 129 | override val logString: String 130 | get() = "NotImpl" 131 | 132 | override fun equal(other: MessageItem): Boolean { 133 | if (other !is NoImplItem) return false 134 | return source == other.source 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/rainCode/RainCode.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.rainCode 2 | 3 | import com.icecreamqaq.yuq.message.Message 4 | import com.icecreamqaq.yuq.message.MessageItem 5 | 6 | object RainCode { 7 | 8 | private val decoders: Map = HashMap() 9 | 10 | 11 | @JvmStatic 12 | @JvmName("decodeRainCodeString") 13 | fun String.decodeRainCode():Message { 14 | val message = Message() 15 | return message 16 | } 17 | 18 | @JvmStatic 19 | @JvmName("encodeMessage") 20 | fun Message.encode():String { 21 | val sb = StringBuilder() 22 | return sb.toString() 23 | } 24 | 25 | } 26 | 27 | interface RainCodeDecoder { 28 | 29 | fun RainCodeItem.decode(): MessageItem 30 | 31 | } 32 | 33 | class RainCodeItem -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/util/MessageUtil.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.util 2 | 3 | import com.icecreamqaq.yuq.message.Message 4 | import com.icecreamqaq.yuq.message.Message.Companion.firstString 5 | import com.icecreamqaq.yuq.message.Message.Companion.toChain 6 | import com.icecreamqaq.yuq.message.Message.Companion.toMessage 7 | import com.icecreamqaq.yuq.message.Message.Companion.toMessageByRainCode 8 | import com.icecreamqaq.yuq.message.Text.Companion.toText 9 | 10 | class MessageUtil { 11 | 12 | companion object{ 13 | 14 | @JvmStatic 15 | fun stringToText(string: String) = string.toText() 16 | 17 | @JvmStatic 18 | fun stringToChain(string: String) = string.toChain() 19 | 20 | @JvmStatic 21 | fun stringToMessageByRainCode(rainCodeString: String) = rainCodeString.toMessageByRainCode() 22 | 23 | @JvmStatic 24 | fun stringToMessage(string: String) = string.toMessage() 25 | 26 | @JvmStatic 27 | fun firstString(message: Message) = message.firstString() 28 | } 29 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/util/fun.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq 2 | 3 | import kotlinx.coroutines.coroutineScope 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | 7 | suspend inline fun asyncDelay(time: Long, crossinline body: () -> Unit) { 8 | coroutineScope { 9 | launch { 10 | delay(time) 11 | body() 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/util/type.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq 2 | 3 | import com.icecreamqaq.yuq.contact.* 4 | 5 | typealias FriendList = UserList 6 | typealias GroupList = UserList 7 | typealias GroupMemberList = UserList 8 | typealias GuildList = UserList 9 | typealias GuildChannelList = UserList 10 | typealias GuildMemberList = UserList -------------------------------------------------------------------------------- /core/src/main/kotlin/com/icecreamqaq/yuq/util/val.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.util 2 | 3 | import com.icecreamqaq.yuq.message.* 4 | 5 | val Message.liteMessage: String get(){ 6 | val sb = StringBuilder() 7 | 8 | if (this.at != null) sb.append("@${this.at!!.id} ") 9 | for (item in body) { 10 | sb.append( 11 | when (item) { 12 | is Text -> item.text 13 | is At -> "@${item.user} " 14 | is Face -> "[表情]" 15 | is Image -> "[图片]" 16 | else -> "暂不支持" 17 | } 18 | ) 19 | } 20 | return sb.toString() 21 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/Bot.kt: -------------------------------------------------------------------------------- 1 | package yuq 2 | 3 | interface Bot { 4 | 5 | val platform: String 6 | val runtime: String 7 | 8 | val id: String 9 | val guid: String 10 | 11 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/YuQ.kt: -------------------------------------------------------------------------------- 1 | package yuq 2 | 3 | class YuQ { 4 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/annotation/ans.kt: -------------------------------------------------------------------------------- 1 | package yuq.annotation 2 | 3 | import yuq.controller.BotControllerLoader 4 | import yuq.controller.MessageChannel 5 | import rain.api.annotation.LoadBy 6 | import rain.classloader.enchant.EnchantBy 7 | import rain.classloader.enchant.MethodParaNamedEnchanter 8 | import javax.inject.Named 9 | 10 | 11 | // 被此注解标记的内容仍在开发状态,相关类型以及名字可能随时变动,请自行评估使用价值。 12 | annotation class Dev 13 | 14 | // 被此注解标记的内容不推荐使用。 15 | annotation class NoRecommendation 16 | 17 | // 此注解标记的内容为 YuQ 内部实现,如果您不能完全理解,请不要轻易修改内容。 18 | annotation class Internal 19 | 20 | @LoadBy(BotControllerLoader::class) 21 | @EnchantBy(MethodParaNamedEnchanter::class) 22 | @Named("group") 23 | annotation class GroupController 24 | 25 | @LoadBy(BotControllerLoader::class) 26 | @EnchantBy(MethodParaNamedEnchanter::class) 27 | @Named("priv") 28 | annotation class PrivateController 29 | 30 | @LoadBy(BotControllerLoader::class) 31 | @EnchantBy(MethodParaNamedEnchanter::class) 32 | @Named("guild") 33 | annotation class GuildController 34 | 35 | @Target(AnnotationTarget.CLASS) 36 | @LoadBy(BotControllerLoader::class) 37 | @EnchantBy(MethodParaNamedEnchanter::class) 38 | annotation class BotController 39 | 40 | @Target(AnnotationTarget.FUNCTION) 41 | annotation class BotAction( 42 | val value: String, 43 | vararg val channel: MessageChannel = [MessageChannel.Friend, MessageChannel.GroupTemporary, MessageChannel.Group] 44 | ) 45 | 46 | @Target(AnnotationTarget.FUNCTION) 47 | annotation class GroupAction(val value: String) 48 | 49 | @Target(AnnotationTarget.FUNCTION) 50 | annotation class FriendAction(val value: String) 51 | 52 | @Target(AnnotationTarget.FUNCTION) 53 | annotation class TemporaryAction(val value: String) 54 | 55 | @Target(AnnotationTarget.FUNCTION) 56 | annotation class PrivateAction(val value: String) 57 | 58 | // 59 | //@LoadBy(BotContextControllerLoader::class) 60 | //@EnchantBy(MethodParaNamedEnchanter::class) 61 | //annotation class ContextController 62 | //annotation class ContextAction(val value: String) 63 | 64 | //annotation class ContextTips(val value: Array) 65 | 66 | annotation class RainCodeString 67 | 68 | //@Repeatable(ContextTips::class) 69 | //annotation class ContextTip(val value: String, val status: Int = 0) 70 | //annotation class NextContext(val value: String, val status: Int = 0) 71 | //annotation class Save(val value: String = "") 72 | 73 | //annotation class QMsg( 74 | // val at: Boolean = false, 75 | // val reply: Boolean = false, 76 | // val atNewLine: Boolean = false, 77 | // val mastAtBot: Boolean = false, 78 | // val recall: Long = 0, 79 | // val forceMatch: Boolean = false, 80 | //) 81 | // 82 | //annotation class PathVar(val value: Int, val type: Type = Type.String) { 83 | // enum class Type { 84 | // Source, String, Integer, Switch, Long, Double, Contact, Friend, Group, Member, User 85 | // } 86 | //} 87 | // 88 | //annotation class AsyncAction 89 | //annotation class TaskLimit( 90 | // val value: Long, 91 | // val type: TaskLimitSource = TaskLimitSource.SENDER, 92 | // val extraPermission: String = "", 93 | // val coldDownTip: String = "冷却中。", 94 | //) { 95 | // enum class TaskLimitSource { 96 | // SENDER, SOURCE, ALL 97 | // } 98 | //} 99 | -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/contact/Account.kt: -------------------------------------------------------------------------------- 1 | package yuq.contact 2 | 3 | import rain.api.permission.IUser 4 | 5 | /*** 账号 6 | * 用于描述一个标准用户,可能由添加好友、群成员等渠道获得。 7 | * 8 | * 账号可能存在与之关联的联系人,也可能不存在。 9 | */ 10 | interface Account: IUser { 11 | 12 | // 提供的平台名称 13 | val platform: String 14 | 15 | // 提供的运行时名称 16 | val runtime: String 17 | 18 | // 账号 ID 19 | val id: String 20 | 21 | // 账号昵称 22 | val nickname: String 23 | 24 | // 账号头像 URL 链接 25 | val avatar: String 26 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/contact/AccountList.kt: -------------------------------------------------------------------------------- 1 | package yuq.contact 2 | 3 | /*** 账号列表 4 | * 一般用于好友列表,群列表,及群成员列表等情况。 5 | * 虽然名称是一个 List,但他并不是一个按成员下标维护的 List! 6 | */ 7 | interface AccountList : Map { 8 | 9 | val platformIds: Set

10 | val platformEntries: Set> 11 | 12 | operator fun get(platformId: P): E? 13 | fun containsKey(platformId: P): Boolean 14 | 15 | fun getOrDefault(platformId: String, defaultValue: E): E 16 | 17 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/contact/Contact.kt: -------------------------------------------------------------------------------- 1 | package yuq.contact 2 | 3 | import yuq.Bot 4 | import yuq.annotation.Internal 5 | import yuq.annotation.NoRecommendation 6 | import yuq.message.Message 7 | import yuq.message.MessageSource 8 | import yuq.message.SendAble 9 | import yuq.message.items.Image 10 | import yuq.message.items.Text 11 | import java.io.File 12 | 13 | /*** 联系人 14 | * 该对象描述一个标准联系人,联系人可以用来发送消息。 15 | * 联系人可能是实际存在的好友,群,群成员,也可能是来自陌生人,添加好友临时会话时的。 16 | * 17 | * 联系人对象每个 Bot 唯一,可以保存。 18 | */ 19 | interface Contact : Account { 20 | 21 | // 该联系人所属的机器人 22 | val bot: Bot 23 | 24 | // YuQ 框架内对联系人的唯一识别码,全局唯一 25 | val guid: String 26 | 27 | // 获取联系人的上下文会话 28 | val session: ContactSession 29 | 30 | // 通过 Message 发送一条消息 31 | fun sendMessage(message: Message): MessageSource 32 | 33 | // 通过 SendAble 发送一条消息 34 | fun sendMessage(message: SendAble): MessageSource = sendMessage(message.toMessage()) 35 | 36 | // 通过 String 发送一条消息 37 | fun sendMessage(message: String): MessageSource = sendMessage(Text(message)) 38 | 39 | // 上传一张图片,返回 Image(MessageItem) 对象。 40 | fun uploadImage(imageFile: File): Image 41 | 42 | // 发送文件,当 Contact 为 Group 时,表现为上传文件。 43 | fun sendFile(file: File) 44 | 45 | // 当前联系人是否能发送消息 46 | fun canSendMessage(): Boolean = true 47 | 48 | // 用于描述输出到日志中的内容。 49 | @Internal 50 | @NoRecommendation 51 | val logString: String 52 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/contact/ContactSession.kt: -------------------------------------------------------------------------------- 1 | package yuq.contact 2 | 3 | import kotlinx.coroutines.CompletableDeferred 4 | import kotlinx.coroutines.withTimeout 5 | import yuq.error.WaitNextMessageTimeoutException 6 | import yuq.message.Message 7 | import java.util.concurrent.ConcurrentHashMap 8 | import kotlin.collections.set 9 | 10 | /*** 联系人上下文会话 11 | * 该对象描述一个联系人的上下文会话,可以用来保存会话数据。 12 | * 上下文对象将保存一段时间,若联系人一段时间内没有发送消息,上下文对象将被销毁。 13 | */ 14 | class ContactSession(val id: String, private val saves: MutableMap = ConcurrentHashMap()) { 15 | 16 | internal var suspendCoroutineIt: CompletableDeferred? = null 17 | 18 | /*** 获取会话数据 19 | * @param name 数据名 20 | * @return 返回数据 21 | */ 22 | operator fun get(name: String) = saves[name] 23 | /*** 设置会话数据 24 | * @param name 数据名 25 | * @param value 数据 26 | */ 27 | operator fun set(name: String, value: Any) { 28 | saves[name] = value 29 | } 30 | /*** 移除会话数据 31 | * @param name 数据名 32 | * @return 返回数据 33 | */ 34 | fun remove(name: String) = saves.remove(name) 35 | 36 | /*** 等待下一条消息 37 | * 联系人的下一条消息将被捕获。 38 | * 被捕获的消息不会再进入 Controller 链路处理流程。 39 | * 但依旧会执行消息事件,若消息事件被取消,则不会捕获本条消息,将会继续等待。 40 | * 41 | * @param maxTime 最大等待时间,单位毫秒,默认为 30000 毫秒。 42 | * @return 返回下一条消息。 43 | * @throws WaitNextMessageTimeoutException 等待超时时抛出该异常。 44 | */ 45 | suspend fun waitNextMessage(maxTime: Long = 30000): Message = 46 | try { 47 | suspendCoroutineIt = CompletableDeferred() 48 | withTimeout(maxTime) { suspendCoroutineIt!!.await() } 49 | } catch (e: Exception) { 50 | throw WaitNextMessageTimeoutException() 51 | } finally { 52 | suspendCoroutineIt = null 53 | } 54 | 55 | 56 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/contact/Friend.kt: -------------------------------------------------------------------------------- 1 | package yuq.contact 2 | 3 | /*** 好友 4 | * 该对象描述一个好友。 5 | * 好友每个 Bot 唯一。 6 | */ 7 | interface Friend : Contact { 8 | 9 | // 删除好友 10 | fun delete() 11 | 12 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/contact/Group.kt: -------------------------------------------------------------------------------- 1 | package yuq.contact 2 | 3 | import com.icecreamqaq.yuq.GroupMemberList 4 | import com.icecreamqaq.yuq.error.PermissionDeniedException 5 | 6 | interface Group : Contact { 7 | 8 | // 群成员列表,该列表中不包含机器人自己。 9 | val members: GroupMemberList 10 | // 群最大成员数量 11 | val maxCount: Int 12 | 13 | // 机器人在本群的成员对象 14 | val botMember: GroupMember 15 | 16 | // 群主 17 | val owner: GroupMember 18 | // 管理员列表 19 | val admins: List 20 | 21 | /*** 群公告列表 22 | * 向列表中新增/移除对象会实时同步操作。 23 | */ 24 | // val notices: GroupNoticeList 25 | 26 | override fun canSendMessage() = !botMember.isBan() 27 | 28 | // 获取群成员,当群成员不存在时抛出异常。 29 | // operator fun get(qq: Long) = getOrNull(qq) ?: error("Member $qq Not Found!") 30 | // 获取群成员,当群成员不存在时返回 null。 31 | // fun getOrNull(qq: Long): GroupMember? = if (qq == botMember.id) botMember else members[qq] 32 | 33 | /*** 离开本群 34 | * 当机器人为群主的时候将解散群聊。 35 | */ 36 | fun leave() 37 | 38 | /*** 打开全体禁言 39 | * @throws [PermissionDeniedException] 当权限不足时抛出 40 | */ 41 | fun banAll() 42 | 43 | /*** 关闭全体禁言 44 | * @throws [PermissionDeniedException] 当权限不足时抛出 45 | */ 46 | fun unBanAll() 47 | 48 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/contact/GroupMember.kt: -------------------------------------------------------------------------------- 1 | package yuq.contact 2 | 3 | import yuq.message.items.At 4 | 5 | 6 | /*** 群成员对象 7 | * 该对象描述一个群成员。 8 | * 群成员对象每个 Bot 每个群唯一。 9 | * 10 | * @see AnonymousMember 匿名成员 11 | */ 12 | interface GroupMember : Contact { 13 | 14 | // 该成员所属的群 15 | val group: Group 16 | 17 | /*** 群成员权限 18 | * 0: 普通成员 19 | * 1: 管理员 20 | * 2: 群主 21 | */ 22 | val permission: Int 23 | 24 | /*** 群成员群名片 25 | * 当群成员没有设置群名片时,该值为空字符串。 26 | * 向参数内写入值,会实时同步修改群名片。 27 | * @throws [PermissionDeniedException] 当权限不足时抛出 28 | */ 29 | var namecard: String 30 | 31 | /*** 群成员群头衔 32 | * 当群成员没有设置群头衔时,该值为空字符串。 33 | * 向参数内写入值,会实时同步修改群头衔。 34 | * @throws [PermissionDeniedException] 当权限不足时抛出 35 | */ 36 | var title: String 37 | 38 | /*** 群成员禁言时间 39 | * 该值为禁言到期时间戳,单位毫秒。 40 | */ 41 | val ban: Long 42 | 43 | // 群成员最后发言时间,单位毫秒 44 | val lastMessageTime: Long 45 | 46 | // 该成员是否被禁言 47 | fun isBan() = ban > (System.currentTimeMillis() / 1000).toInt() 48 | 49 | /*** 禁言该成员,单位秒 50 | * @throws [PermissionDeniedException] 当权限不足时抛出 51 | */ 52 | fun ban(time: Int) 53 | 54 | /*** 取消禁言该成员 55 | * @throws [PermissionDeniedException] 当权限不足时抛出 56 | */ 57 | fun unBan() 58 | 59 | // 在群名片为空时返回昵称,否则返回群名片 60 | fun nameCardOrName() = if (namecard == "") nickname else namecard 61 | 62 | // 返回一个 @群成员 的消息内容 63 | fun at(): At = At(id) 64 | 65 | /*** 该成员是否具有管理员权限 66 | * 该成员是管理员,或是群主都会返回 true。 67 | * 如果需要精确判断,请判断 [permission] 值。 68 | */ 69 | fun isAdmin() = permission > 0 70 | 71 | // 该成员是否为群主 72 | fun isOwner() = permission == 2 73 | 74 | /*** 移除群成员 75 | * @throws [PermissionDeniedException] 当权限不足时抛出 76 | */ 77 | fun kick() = kick("") 78 | 79 | /*** 移除群成员 80 | * @param message 移除成员时的提示消息 81 | * @throws [PermissionDeniedException] 当权限不足时抛出 82 | */ 83 | fun kick(message: String = "") 84 | 85 | val logStringSingle: String 86 | 87 | // companion object { 88 | // @JvmStatic 89 | // fun GroupMember.toFriend(): Friend? = bot.friends[id] 90 | // } 91 | 92 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/controller/BotActionContext.kt: -------------------------------------------------------------------------------- 1 | package yuq.controller 2 | 3 | import rain.api.permission.IUser 4 | import rain.controller.ActionContext 5 | import yuq.Bot 6 | import yuq.contact.Contact 7 | import yuq.message.Message 8 | 9 | class BotActionContext( 10 | val bot: Bot, 11 | val channel: MessageChannel, 12 | val sender: Contact, 13 | val source: Contact, 14 | val message: Message 15 | ) : ActionContext { 16 | 17 | override val user: IUser 18 | get() = sender 19 | 20 | internal val matcherItem = MatcherItem(message.body) 21 | 22 | private val saved = HashMap() 23 | 24 | override var result: Any? = null 25 | override var runtimeError: Throwable? = null 26 | 27 | 28 | var actionInvoker: BotActionInvoker? = null 29 | 30 | 31 | override fun get(name: String): Any? { 32 | return saved[name] 33 | } 34 | 35 | override fun remove(name: String): Any? { 36 | return saved.remove(name) 37 | } 38 | 39 | override fun set(name: String, obj: Any?) { 40 | saved[name] = obj 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/controller/BotActionInvoker.kt: -------------------------------------------------------------------------------- 1 | package yuq.controller 2 | 3 | import kotlinx.coroutines.coroutineScope 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import rain.controller.ProcessInvoker 7 | import rain.controller.simple.SimpleActionInvoker 8 | import yuq.controller.router.RouterMatcher 9 | import yuq.message.Message 10 | import yuq.message.MessageItem 11 | import yuq.message.MessageItemChain 12 | import yuq.message.chain.MessageBody 13 | import yuq.message.items.Text 14 | 15 | 16 | class BotActionInvoker( 17 | val channels: Array, 18 | val matchers: List, 19 | action: ProcessInvoker, 20 | beforeProcesses: Array>, 21 | aftersProcesses: Array>, 22 | catchsProcesses: Array> 23 | ) : SimpleActionInvoker(action, beforeProcesses, aftersProcesses, catchsProcesses) { 24 | 25 | 26 | override suspend fun onActionResult(context: BotActionContext, result: Any?): Boolean { 27 | if (result == null) return false 28 | if (checkResult(context, result)) return true 29 | 30 | context.result = when (result) { 31 | is String -> Text(result).toMessage() 32 | is Message -> result 33 | is MessageItem -> result.toMessage() 34 | is MessageBody -> result.toMessage() 35 | // is MessageLineQ -> result.toMessage() 36 | is Array<*> -> 37 | coroutineScope { 38 | launch { 39 | result.forEach { 40 | when (it) { 41 | is Int -> delay(it.toLong()) 42 | is Long -> delay(it) 43 | is Message -> context.source.sendMessage(it) 44 | } 45 | } 46 | } 47 | } 48 | 49 | else -> Text(result.toString()).toMessage() 50 | } 51 | return false 52 | } 53 | 54 | override suspend fun checkChannel(context: BotActionContext): Boolean = 55 | context.channel in channels 56 | 57 | } 58 | -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/controller/BotControllerLoader.kt: -------------------------------------------------------------------------------- 1 | package yuq.controller 2 | 3 | import com.icecreamqaq.yuq.annotation.* 4 | import com.icecreamqaq.yuq.controller.BotRootInfo 5 | import yuq.controller.router.BotRouter 6 | import yuq.controller.router.RouterMatcher 7 | import rain.api.di.DiContext 8 | import rain.controller.* 9 | import rain.controller.annotation.After 10 | import rain.controller.annotation.Before 11 | import rain.controller.annotation.Catch 12 | import rain.controller.annotation.Path 13 | import rain.controller.simple.SimpleCatchMethodInvoker 14 | import rain.function.annotation 15 | import java.lang.reflect.Method 16 | import kotlin.reflect.KProperty1 17 | 18 | open class BotControllerLoader( 19 | context: DiContext 20 | ) : ControllerLoader(context) { 21 | 22 | private val rootInfo = BotRootInfo(BotRouter { true }) 23 | 24 | override fun findRootRouter(name: String) = rootInfo 25 | 26 | fun readPath(path: String): List { 27 | TODO() 28 | } 29 | 30 | override fun controllerInfo( 31 | root: BotRootInfo, 32 | annotation: Annotation?, 33 | controllerClass: Class<*>, 34 | instanceGetter: ControllerInstanceGetter 35 | ): ControllerProcessFlowInfo? { 36 | val channels = controllerChannel(annotation, controllerClass) ?: return null 37 | var controllerRouter = root.router 38 | controllerClass.annotation { 39 | readPath(value).forEach { m -> 40 | controllerRouter = controllerRouter.subRouters.firstOrNull { it.matcher == m } 41 | ?: BotRouter(m).also { controllerRouter.subRouters.add(it) } 42 | } 43 | } 44 | 45 | return ControllerProcessFlowInfo(controllerClass, channels, controllerRouter) 46 | } 47 | 48 | private fun controllerChannel(annotation: Annotation?, controllerClass: Class<*>): List? { 49 | if (annotation == null) return null 50 | return when (annotation) { 51 | is GroupController -> arrayListOf(MessageChannel.Group.channel) 52 | is PrivateController -> arrayListOf(MessageChannel.Friend.channel, MessageChannel.GroupTemporary.channel) 53 | else -> arrayListOf() 54 | } 55 | } 56 | 57 | override fun makeAfter( 58 | afterAnnotation: After, 59 | controllerClass: Class<*>, 60 | afterMethod: Method, 61 | instanceGetter: ControllerInstanceGetter 62 | ): ProcessInfo? = 63 | ProcessInfo( 64 | afterAnnotation.weight, 65 | afterAnnotation.except, 66 | afterAnnotation.only, 67 | BotMethodInvoker(afterMethod, instanceGetter) 68 | ) 69 | 70 | override fun makeBefore( 71 | beforeAnnotation: Before, 72 | controllerClass: Class<*>, 73 | beforeMethod: Method, 74 | instanceGetter: ControllerInstanceGetter 75 | ): ProcessInfo? = 76 | ProcessInfo( 77 | beforeAnnotation.weight, 78 | beforeAnnotation.except, 79 | beforeAnnotation.only, 80 | BotMethodInvoker(beforeMethod, instanceGetter) 81 | ) 82 | 83 | override fun makeCatch( 84 | catchAnnotation: Catch, 85 | controllerClass: Class<*>, 86 | catchMethod: Method, 87 | instanceGetter: ControllerInstanceGetter 88 | ): ProcessInfo? = 89 | ProcessInfo( 90 | catchAnnotation.weight, 91 | catchAnnotation.except, 92 | catchAnnotation.only, 93 | SimpleCatchMethodInvoker(catchAnnotation.error.java, BotMethodInvoker(catchMethod, instanceGetter)) 94 | ) 95 | 96 | override fun postLoad() { 97 | val actionList = ArrayList>() 98 | rootInfo.controllers.forEach { 99 | it.actions.forEach { 100 | actionList.add( 101 | ActionInfo( 102 | it.actionClass, 103 | it.actionMethod, 104 | it.creator() 105 | ) 106 | ) 107 | } 108 | } 109 | 110 | // botService.rootRouter = BotRootRouter(rootInfo.router, actionList) 111 | } 112 | 113 | override fun makeAction( 114 | rootRouter: BotRootInfo, 115 | controllerFlow: ControllerProcessFlowInfo, 116 | controllerClass: Class<*>, 117 | actionMethod: Method, 118 | instanceGetter: ControllerInstanceGetter 119 | ): ActionProcessFlowInfo? { 120 | var path = "" 121 | var channels: Array = emptyArray() 122 | actionMethod.annotation { 123 | path = value 124 | channels = arrayOf(MessageChannel.Group) 125 | } 126 | actionMethod.annotation { 127 | path = value 128 | channels = arrayOf(MessageChannel.GroupTemporary, MessageChannel.Friend) 129 | } 130 | actionMethod.annotation { 131 | path = value 132 | channels = arrayOf(MessageChannel.Friend) 133 | } 134 | actionMethod.annotation { 135 | path = value 136 | channels = arrayOf(MessageChannel.GroupTemporary) 137 | } 138 | 139 | if (channels.isEmpty()) return null 140 | 141 | val actionName = actionMethod.name 142 | 143 | val actionFlow = ActionProcessFlowInfo(controllerClass, actionMethod) 144 | val matchers = readPath(path) 145 | fun checkPf(property: KProperty1, MutableList>>): Array> = 146 | ArrayList>() 147 | .apply { 148 | val checkPi = { it: ProcessInfo -> 149 | if (actionName !in it.except && it.only.isNotEmpty() && actionName in it.only) add(it) 150 | } 151 | property.get(rootRouter).forEach(checkPi) 152 | property.get(controllerFlow).forEach(checkPi) 153 | property.get(actionFlow).forEach(checkPi) 154 | sortBy { it.priority } 155 | } 156 | .map { it.invoker } 157 | .toTypedArray() 158 | 159 | // actionFlow.creator = ActionInvokerCreator { 160 | // BotActionInvoker( 161 | // channels, 162 | // matchers, 163 | // BotMethodInvoker(actionMethod, instanceGetter), 164 | // checkPf(ProcessFlowInfo::beforeProcesses), 165 | // checkPf(ProcessFlowInfo::afterProcesses), 166 | // checkPf(ProcessFlowInfo::catchProcesses) 167 | // ) 168 | // } 169 | return actionFlow 170 | } 171 | 172 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/controller/BotMethodInvoker.kt: -------------------------------------------------------------------------------- 1 | package yuq.controller 2 | 3 | import rain.controller.ControllerInstanceGetter 4 | import rain.controller.simple.SimpleKJReflectMethodInvoker 5 | import yuq.controller.BotActionContext 6 | import java.lang.reflect.Method 7 | 8 | open class BotMethodInvoker( 9 | method: Method, 10 | instance: ControllerInstanceGetter 11 | ) : SimpleKJReflectMethodInvoker Any?>(method, instance) { 12 | 13 | override fun getParam(param: MethodParam<(BotActionContext) -> Any?>, context: BotActionContext): Any? { 14 | 15 | TODO("Not yet implemented") 16 | } 17 | 18 | override fun initParam(method: Method, params: Array Any?>>) { 19 | TODO("Not yet implemented") 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/controller/MatcherItem.kt: -------------------------------------------------------------------------------- 1 | package yuq.controller 2 | 3 | import yuq.message.MessageItem 4 | import yuq.message.chain.MessageBody 5 | import yuq.message.items.Text 6 | 7 | class MatcherItem(private val items: MessageBody) { 8 | 9 | var end = false 10 | 11 | var currentItemIndex = 0 12 | var currentItemMatchIndex = 0 13 | 14 | var currentItem: MessageItem = items[0] 15 | var currentString: String? = (currentItem as? Text)?.text 16 | 17 | fun changeItem(index: Int) { 18 | if (index >= items.size) { 19 | end = true 20 | return 21 | } 22 | currentItem = items[index] 23 | currentString = (currentItem as? Text)?.text 24 | currentItemMatchIndex = 0 25 | } 26 | 27 | fun nextItem() { 28 | changeItem(currentItemIndex + 1) 29 | } 30 | 31 | fun mark() = currentItemIndex to currentItemMatchIndex 32 | 33 | fun reMark(mark: Pair) { 34 | changeItem(mark.first) 35 | 36 | currentItemMatchIndex = mark.second 37 | } 38 | 39 | fun isNext(item: String): Boolean { 40 | if (end) return false 41 | if (currentString == null) return false 42 | if (item.length > currentString!!.length - currentItemMatchIndex) return false 43 | if (currentString!!.substring(currentItemMatchIndex, currentItemMatchIndex + item.length) == item) { 44 | currentItemMatchIndex += item.length 45 | 46 | if (currentItemMatchIndex == currentString!!.length) nextItem() 47 | 48 | return true 49 | } 50 | 51 | return true 52 | } 53 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/controller/MessageChannel.kt: -------------------------------------------------------------------------------- 1 | package yuq.controller 2 | 3 | enum class MessageChannel(val channel: String) { 4 | // 群聊消息 5 | Group("Group"), 6 | // 好友消息 7 | Friend("Friend"), 8 | // 群临时会话 9 | GroupTemporary("GroupTemporary"), 10 | // 频道消息 11 | Guild("Guild"), 12 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/controller/router/BotRootRouter.kt: -------------------------------------------------------------------------------- 1 | package yuq.controller.router 2 | 3 | import yuq.controller.BotActionContext 4 | import rain.controller.ActionInfo 5 | import rain.controller.RootRouter 6 | 7 | class BotRootRouter( 8 | router: BotRouter, 9 | actions: List> 10 | ) : RootRouter(router, actions) -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/controller/router/BotRouter.kt: -------------------------------------------------------------------------------- 1 | package yuq.controller.router 2 | 3 | import yuq.controller.BotActionContext 4 | import yuq.controller.BotActionInvoker 5 | import yuq.controller.MatcherItem 6 | import rain.controller.Router 7 | 8 | class BotRouter(val matcher: RouterMatcher) : Router { 9 | 10 | val subRouters = ArrayList() 11 | val actions = ArrayList() 12 | 13 | suspend operator fun invoke(context: BotActionContext): Boolean { 14 | val mi = context.matcherItem 15 | val mark = mi.mark() 16 | 17 | if (subRouters.any(mi, mark) { it.matcher(context) && it(context) }) return true 18 | // return actions.any(mi, mark) { it(context) } 19 | return false 20 | } 21 | 22 | inline fun Iterable.any(mi: MatcherItem, mark: Pair, predicate: (T) -> Boolean): Boolean { 23 | if (this is Collection && isEmpty()) return false 24 | for (element in this) { 25 | mi.reMark(mark) 26 | if (predicate(element)) return true 27 | } 28 | return false 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/controller/router/RouterMatcher.kt: -------------------------------------------------------------------------------- 1 | package yuq.controller.router 2 | 3 | import yuq.controller.BotActionContext 4 | 5 | fun interface RouterMatcher { 6 | operator fun invoke(context: BotActionContext): Boolean 7 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/controller/router/matchers.kt: -------------------------------------------------------------------------------- 1 | package yuq.controller.router 2 | 3 | import yuq.controller.BotActionContext 4 | 5 | class StaticMatcher(val str: String) : RouterMatcher { 6 | override fun invoke(context: BotActionContext): Boolean { 7 | return context.matcherItem.isNext(str) 8 | } 9 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/controller/type.kt: -------------------------------------------------------------------------------- 1 | package yuq.controller 2 | 3 | import yuq.controller.router.BotRouter 4 | import rain.controller.RootRouterProcessFlowInfo 5 | import yuq.controller.BotActionContext 6 | 7 | typealias BotRootInfo = RootRouterProcessFlowInfo -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/error/err.kt: -------------------------------------------------------------------------------- 1 | package yuq.error 2 | 3 | open class YuQException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) 4 | open class YuQRuntimeException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause) 5 | 6 | open class ImageTypedException(message: String) : YuQRuntimeException(message) 7 | 8 | open class WaitNextMessageTimeoutException : YuQRuntimeException() 9 | 10 | open class SendMessageFailedByCancel : YuQRuntimeException("消息发送失败,发送消息被拦截。") 11 | open class SendMessageFailedByTimeout : YuQRuntimeException("消息发送完成,但是接收消息失败,可能被服务端拒绝广播。") 12 | 13 | open class PermissionDeniedException : YuQRuntimeException("权限不足。") -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/event/AtBotEvent.kt: -------------------------------------------------------------------------------- 1 | package yuq.event 2 | 3 | import com.icecreamqaq.yuq.contact.GuildMember 4 | import yuq.Bot 5 | import yuq.contact.Contact 6 | import yuq.contact.Friend 7 | import yuq.contact.Group 8 | import yuq.contact.GroupMember 9 | 10 | 11 | open class AtBotEvent(open val type: Int, open val sender: Contact, open val source: Contact) : BotEvent { 12 | open class ByGroup(type: Int, override val sender: GroupMember, override val source: Group) : 13 | AtBotEvent(type, sender, source) 14 | 15 | // open class ByGuild(type: Int, override val sender: GuildMember, override val source: Channel, val guild: Guild) : 16 | // AtBotEvent(type, sender, source) 17 | 18 | open class ByPrivate(type: Int, sender: Contact, source: Contact) : AtBotEvent(type, sender, source) { 19 | open class ByFriend(type: Int, override val sender: Friend, override val source: Friend) : 20 | ByPrivate(type, sender, source) 21 | 22 | open class ByTemp(type: Int, override val sender: GroupMember, override val source: GroupMember) : 23 | ByPrivate(type, sender, source) 24 | } 25 | 26 | override val bot: Bot 27 | get() = sender.bot 28 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/event/BotEvent.kt: -------------------------------------------------------------------------------- 1 | package yuq.event 2 | 3 | import rain.api.event.Event 4 | import yuq.Bot 5 | 6 | interface BotEvent : Event { 7 | val bot: Bot 8 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/event/BotStatusEvent.kt: -------------------------------------------------------------------------------- 1 | package yuq.event 2 | 3 | import yuq.Bot 4 | 5 | 6 | open class BotStatusEvent(override val bot: Bot) : BotEvent { 7 | // Bot 上线事件 8 | open class Online(bot: Bot) : BotStatusEvent(bot) 9 | 10 | // Bot 离线事件 11 | open class Offline(bot: Bot) : BotStatusEvent(bot) 12 | 13 | /*** Bot 重新上线事件 14 | * Bot 可能因为网络波动等出现掉线问题。 15 | * 重新上线可能不会触发离线,和上线事件。 16 | */ 17 | open class ReOnline(bot: Bot) : BotStatusEvent(bot) 18 | 19 | /*** Bot 被添加事件 20 | * 部分 Platform/Runtime 的 Bot 可能不会触发上线,离线等事件。 21 | */ 22 | open class Add(bot: Bot) : BotStatusEvent(bot) 23 | 24 | /*** Bot 被移除事件 25 | * 部分 Platform/Runtime 的 Bot 可能不会触发上线,离线等事件。 26 | */ 27 | open class Remove(bot: Bot) : BotStatusEvent(bot) 28 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/event/MessageEvent.kt: -------------------------------------------------------------------------------- 1 | package yuq.event 2 | 3 | import rain.event.events.AbstractCancelAbleEvent 4 | import yuq.Bot 5 | import yuq.contact.Contact 6 | import yuq.contact.Friend 7 | import yuq.contact.Group 8 | import yuq.contact.GroupMember 9 | import yuq.message.Message 10 | 11 | open class MessageEvent(open val sender: Contact, val message: Message) : AbstractCancelAbleEvent(), BotEvent { 12 | override val bot: Bot 13 | get() = sender.bot 14 | 15 | open class GroupMessage(override val sender: GroupMember, val group: Group, message: Message) : 16 | MessageEvent(sender, message) 17 | 18 | // open class GuildMessage( 19 | // override val sender: GuildMember, 20 | // val guild: Guild, 21 | // val channel: Channel, 22 | // message: Message 23 | // ) : MessageEvent(sender, message) 24 | 25 | open class PrivateMessage(sender: Contact, message: Message) : MessageEvent(sender, message) { 26 | open class FriendMessage(override val sender: Friend, message: Message) : PrivateMessage(sender, message) 27 | open class TempMessage(override val sender: GroupMember, message: Message) : PrivateMessage(sender, message) 28 | } 29 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/event/SendMessageEvent.kt: -------------------------------------------------------------------------------- 1 | package yuq.event 2 | 3 | import rain.event.events.CancelAbleEvent 4 | import yuq.Bot 5 | import yuq.contact.Contact 6 | import yuq.message.Message 7 | import yuq.message.MessageSource 8 | 9 | open class SendMessageEvent(val sendTo: Contact, val message: Message) : BotEvent { 10 | open class Per(sendTo: Contact, message: Message) : SendMessageEvent(sendTo, message), CancelAbleEvent { 11 | override var isCanceled: Boolean = false 12 | } 13 | 14 | open class Post(sendTo: Contact, message: Message, val messageSource: MessageSource) : 15 | SendMessageEvent(sendTo, message) 16 | 17 | override val bot: Bot 18 | get() = sendTo.bot 19 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/internal/BotService.kt: -------------------------------------------------------------------------------- 1 | package yuq.internal 2 | 3 | import kotlinx.coroutines.coroutineScope 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import rain.api.event.EventBus 7 | import rain.di.Config 8 | import rain.function.slf4j 9 | import yuq.Bot 10 | import yuq.annotation.Internal 11 | import yuq.contact.Contact 12 | import yuq.contact.Friend 13 | import yuq.contact.GroupMember 14 | import yuq.controller.BotActionContext 15 | import yuq.controller.MessageChannel 16 | import yuq.error.SendMessageFailedByCancel 17 | import yuq.error.SendMessageFailedByTimeout 18 | import yuq.event.AtBotEvent 19 | import yuq.event.MessageEvent 20 | import yuq.event.SendMessageEvent 21 | import yuq.message.Message 22 | import yuq.message.MessageSource 23 | import yuq.message.items.At 24 | import yuq.message.items.Text 25 | import yuq.message.source.MessageFailByCancel 26 | import yuq.message.source.MessageFailByReadTimeOut 27 | 28 | @Internal 29 | class BotService( 30 | val eventBus: EventBus, 31 | @Config("bot.name") val botName: String? = null, 32 | @Config("yuq.strict") val strict: Boolean, 33 | val frameworkInfo: FrameworkInfo, 34 | ) { 35 | 36 | companion object { 37 | val log = slf4j() 38 | } 39 | 40 | private fun Message.getOnlyAtFlag(bot: Bot): Int { 41 | if (body.size > 1) return 0 42 | val item = body[0] 43 | if (item is At && item.target == bot.id) return 1 44 | if (botName != null && item is Text && botName == item.text) return 2 45 | return 0 46 | } 47 | 48 | suspend fun receiveFriendMessage(bot: Bot, sender: Friend, message: Message) { 49 | log.info("${sender.logString} -> ${message.toLogString()}") 50 | frameworkInfo.receiveMessage(bot.guid) 51 | if (eventBus.post(MessageEvent.PrivateMessage.FriendMessage(sender, message))) return 52 | if (message.body.isEmpty()) return 53 | val flag = message.getOnlyAtFlag(bot) 54 | if (flag > 0) { 55 | eventBus.post(AtBotEvent.ByPrivate.ByFriend(flag, sender, sender)) 56 | return 57 | } 58 | sender.session.suspendCoroutineIt?.let { 59 | it.complete(message) 60 | return 61 | } 62 | doRouter(BotActionContext(bot, MessageChannel.Friend, sender, sender, message)) 63 | } 64 | 65 | suspend fun receiveTempMessage(bot: Bot, sender: GroupMember, message: Message) { 66 | log.info("${sender.logString} -> ${message.toLogString()}") 67 | frameworkInfo.receiveMessage(bot.guid) 68 | if (eventBus.post(MessageEvent.PrivateMessage.TempMessage(sender, message))) return 69 | if (message.body.isEmpty()) return 70 | val flag = message.getOnlyAtFlag(bot) 71 | if (flag > 0) { 72 | eventBus.post(AtBotEvent.ByPrivate.ByTemp(flag, sender, sender)) 73 | return 74 | } 75 | sender.session.suspendCoroutineIt?.let { 76 | it.complete(message) 77 | return 78 | } 79 | doRouter(BotActionContext(bot, MessageChannel.GroupTemporary, sender, sender, message)) 80 | } 81 | 82 | suspend fun receiveGroupMessage(bot: Bot, sender: GroupMember, message: Message) { 83 | log.info("[${sender.group.logString}]${sender.logStringSingle} -> ${message.toLogString()}") 84 | frameworkInfo.receiveMessage(bot.guid) 85 | if (eventBus.post(MessageEvent.GroupMessage(sender, sender.group, message))) return 86 | // val groupSession = botService.getContextSession(bot, "g${sender.group.id}") 87 | // if (groupSession.suspendCoroutineIt != null) { 88 | // groupSession.suspendCoroutineIt!!.complete(message) 89 | // return 90 | // } 91 | if (message.body.isEmpty()) return 92 | val flag = message.getOnlyAtFlag(bot) 93 | if (flag > 0) { 94 | eventBus.post(AtBotEvent.ByGroup(flag, sender, sender.group)) 95 | return 96 | } 97 | sender.group.session.suspendCoroutineIt?.let { 98 | it.complete(message) 99 | return 100 | } 101 | sender.session.suspendCoroutineIt?.let { 102 | it.complete(message) 103 | return 104 | } 105 | doRouter(BotActionContext(bot, MessageChannel.Group, sender, sender.group, message)) 106 | } 107 | 108 | suspend fun doRouter(context: BotActionContext){ 109 | 110 | } 111 | 112 | suspend fun hookSendMessage( 113 | message: Message, 114 | contact: Contact, 115 | sendFun: suspend () -> MessageSource? 116 | ): MessageSource { 117 | val mLog = message.toLogString() 118 | val cLog = contact.logString 119 | 120 | log.debug("准备发送消息: $cLog, $mLog") 121 | if (eventBus.post(SendMessageEvent.Per(contact, message))) return messageSendFailedByCancel(contact, message) 122 | val ms = sendFun() ?: messageSendFailedByReadTimeout(contact, message) 123 | log.info("$cLog <- $mLog") 124 | eventBus.post(SendMessageEvent.Post(contact, message, ms)) 125 | message.recallDelay?.let { 126 | coroutineScope { 127 | launch { 128 | delay(it) 129 | ms.recall() 130 | } 131 | } 132 | } 133 | return ms 134 | } 135 | 136 | fun messageSendFailedByCancel(contact: Contact, message: Message): MessageSource { 137 | if (strict) throw SendMessageFailedByCancel() 138 | return MessageFailByCancel() 139 | } 140 | 141 | fun messageSendFailedByReadTimeout(contact: Contact, message: Message): MessageSource { 142 | if (strict) throw SendMessageFailedByTimeout() 143 | return MessageFailByReadTimeOut() 144 | } 145 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/internal/FrameworkInfo.kt: -------------------------------------------------------------------------------- 1 | package yuq.internal 2 | 3 | import yuq.annotation.Internal 4 | 5 | @Internal 6 | class FrameworkInfo { 7 | 8 | fun receiveMessage(guid: String) { 9 | 10 | } 11 | 12 | fun sendMessage(guid: String) { 13 | 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/Message.kt: -------------------------------------------------------------------------------- 1 | package yuq.message 2 | 3 | import yuq.annotation.Internal 4 | import yuq.annotation.NoRecommendation 5 | import yuq.message.chain.MessageBody 6 | import yuq.message.items.Text 7 | 8 | class Message(val body: MessageBody) : MessageItemChain by body, MessagePlusAble { 9 | 10 | /*** 消息源信息。 11 | * 与消息本身无关,如果这条消息是收到的消息,则会附带本参数。 12 | * 消息源是定位消息在腾讯所在位置的记录,用于消息撤回,回复等操作。 13 | * 当你将消息发出时,并不会将发出消息的消息源写到本参数,而是 sendMessage 方法返回的消息源。 14 | */ 15 | var source: MessageSource? = null 16 | 17 | /*** 回复消息 18 | * 描述一条消息是否是对另一条消息的回复。 19 | * 如果收到的消息有回复目标,则会附带本参数。 20 | * 如果你需要发出一条回复消息,则需要将本参数设置为你要回复的消息的消息源。 21 | */ 22 | var reply: MessageSource? = null 23 | 24 | /*** At 参数 25 | * 使得消息发送时可以自动 At 目标。 26 | * 本参数是一个纯粹的发送参数,接收到的消息本参数永远为空。 27 | */ 28 | var at: MessageAt? = null 29 | 30 | /*** 消息撤回参数 31 | * 本条消息在发出后一段时间撤回,单位:毫秒。 32 | * 本参数是一个纯粹的发送参数,接收到的消息本参数永远为空。 33 | */ 34 | var recallDelay: Long? = null 35 | 36 | /*** 撤回消息 37 | * 该方法是 source 字段的 recall 方法快捷调用。 38 | * 当 source 为 null 时会直接报错,请注意。 39 | * @see MessageSource.recall 40 | */ 41 | suspend fun recall() { 42 | return source!!.recall() 43 | } 44 | 45 | 46 | fun bodyEquals(other: Any?): Boolean { 47 | if (other !is com.icecreamqaq.yuq.message.Message) return false 48 | if (body.size != other.body.size) return false 49 | for ((i, item) in body.withIndex()) { 50 | val oi = other.body[i] 51 | if (item != oi) return false 52 | } 53 | return true 54 | } 55 | 56 | override fun toString(): String { 57 | return toLogString() 58 | } 59 | 60 | @Internal 61 | @NoRecommendation 62 | fun toLogString(): String { 63 | val sb = StringBuilder("(") 64 | if (reply != null) sb.append("Reply To: ${reply!!.id}, ") 65 | if (at != null) sb.append("At them${if (at!!.newLine) " \\n" else ""}, ") 66 | if (body.size > 0) { 67 | sb.append("[ ${body[0].logString}") 68 | for (i in 1 until body.size) { 69 | sb.append(", ${body[i].logString}") 70 | } 71 | sb.append(" ]") 72 | } 73 | sb.append(")") 74 | return sb.toString() 75 | } 76 | 77 | 78 | constructor(item: MessageItem) : this(MessageBody().apply { append(item) }) 79 | 80 | override fun plus(item: MessageItem): Message = apply { body.append(item) } 81 | 82 | override fun plus(item: String): Message = apply { body.append(Text(item)) } 83 | 84 | override fun plus(item: Message): Message = apply { body.append(item.body) } 85 | 86 | override fun plus(item: MessageBody): Message = apply { body.append(item) } 87 | 88 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/MessageAt.kt: -------------------------------------------------------------------------------- 1 | package yuq.message 2 | 3 | // 用以描述在消息发送时自动 At 被接收方。 4 | data class MessageAt( 5 | // At 后是否插入一个换行 6 | val newLine: Boolean = false 7 | ) -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/MessageItem.kt: -------------------------------------------------------------------------------- 1 | package yuq.message 2 | 3 | import yuq.message.chain.MessageBody 4 | import yuq.message.items.Text 5 | 6 | interface MessageItem : MessagePlusAble, SendAble { 7 | val logString: String 8 | 9 | companion object { 10 | private fun MessageItem.messageOf(body: Message.() -> Unit) = toMessage().apply(body) 11 | } 12 | 13 | override fun toMessage(): Message = 14 | Message(this) 15 | 16 | override fun plus(item: Message): Message = 17 | messageOf { body.append(item.body) } 18 | 19 | override fun plus(item: MessageBody): Message = 20 | messageOf { body.append(item) } 21 | 22 | override fun plus(item: MessageItem): Message = 23 | messageOf { body.append(item) } 24 | 25 | override fun plus(item: String): Message = 26 | messageOf { body.append(Text(item)) } 27 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/MessageItemChain.kt: -------------------------------------------------------------------------------- 1 | package yuq.message 2 | 3 | 4 | interface MessageItemChain { 5 | 6 | companion object { 7 | inline fun MessageItemChain.firstBy() = first(T::class.java) 8 | inline fun MessageItemChain.lastBy() = last(T::class.java) 9 | inline fun MessageItemChain.findBy() = find(T::class.java) 10 | } 11 | 12 | operator fun get(index: Int): MessageItem 13 | fun unshift(item: MessageItem): MessageItemChain 14 | fun append(item: MessageItem): MessageItemChain 15 | 16 | fun first(type: Class): T? 17 | fun last(type: Class): T? 18 | fun find(type: Class): List 19 | 20 | fun first(): MessageItem? 21 | fun last(): MessageItem? 22 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/MessagePlusAble.kt: -------------------------------------------------------------------------------- 1 | package yuq.message 2 | 3 | import yuq.message.chain.MessageBody 4 | 5 | interface MessagePlusAble { 6 | operator fun plus(item: MessageItem): MessageItemChain 7 | operator fun plus(item: String): MessageItemChain 8 | operator fun plus(item: Message): MessageItemChain 9 | operator fun plus(item: MessageBody): MessageItemChain 10 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/MessageSource.kt: -------------------------------------------------------------------------------- 1 | package yuq.message 2 | 3 | interface MessageSource { 4 | val id: String 5 | 6 | /*** 撤回本消息 7 | * 请确保 Bot 拥有撤回权限。 8 | */ 9 | suspend fun recall() 10 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/SendAble.kt: -------------------------------------------------------------------------------- 1 | package yuq.message 2 | 3 | interface SendAble { 4 | fun toMessage(): Message 5 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/chain/ChainItem.kt: -------------------------------------------------------------------------------- 1 | package yuq.message.chain 2 | 3 | import yuq.message.MessageItem 4 | 5 | data class ChainItem(var item: MessageItem, var next: ChainItem? = null, var previous: ChainItem? = null) -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/chain/MessageBody.kt: -------------------------------------------------------------------------------- 1 | package yuq.message.chain 2 | 3 | import yuq.message.* 4 | import yuq.message.items.Text 5 | 6 | 7 | class MessageBody : MessageItemChain, List, MessagePlusAble, SendAble { 8 | 9 | private var _size = 0 10 | override val size get() = _size 11 | private var first: ChainItem? = null 12 | private var last: ChainItem? = null 13 | 14 | override fun first() = first?.item 15 | override fun last() = last?.item 16 | 17 | override fun first(type: Class): T? = firstOrNull { type.isInstance(it) } as? T 18 | override fun last(type: Class): T? = lastOrNull { type.isInstance(it) } as? T 19 | override fun find(type: Class): List = filter { type.isInstance(it) } as List 20 | 21 | override fun toMessage() = Message(this) 22 | 23 | override fun unshift(item: MessageItem): MessageBody { 24 | first = ChainItem(item, first) 25 | _size++ 26 | return this 27 | } 28 | 29 | override fun append(item: MessageItem): MessageBody { 30 | if (first == null) { 31 | val i = ChainItem(item) 32 | first = i 33 | last = i 34 | _size = 1 35 | } else { 36 | val i = ChainItem(item, null, last) 37 | last!!.next = i 38 | last = i 39 | _size++ 40 | } 41 | return this 42 | } 43 | 44 | override fun plus(item: MessageItem) = append(item) 45 | 46 | override fun plus(item: String) = append(Text(item)) 47 | 48 | override fun plus(item: Message) = append(item.body) 49 | 50 | override fun plus(item: MessageBody) = append(item) 51 | 52 | fun append(chain: MessageBody): MessageBody { 53 | if (chain.first != null) 54 | if (first == null) { 55 | first = chain.first 56 | last = chain.last 57 | _size = chain._size 58 | } else { 59 | last!!.next = chain.first 60 | last = chain.last 61 | _size += chain._size 62 | } 63 | return this 64 | } 65 | 66 | override fun contains(element: MessageItem): Boolean { 67 | var item = first 68 | while (item != null) if (item.item == element) return true else item = item.next 69 | return false 70 | } 71 | 72 | override fun containsAll(elements: Collection): Boolean { 73 | TODO("Not yet implemented") 74 | } 75 | 76 | override fun get(index: Int): MessageItem { 77 | if (index >= size) throw ArrayIndexOutOfBoundsException(index) 78 | if (index == 0) return first!!.item 79 | var item = first 80 | for (i in 1..index) { 81 | item = item!!.next 82 | } 83 | return item!!.item 84 | } 85 | 86 | 87 | override fun indexOf(element: MessageItem): Int { 88 | var item = first 89 | for (i in 0 until size) { 90 | if (item!!.item == element) return i else item = item.next 91 | } 92 | return -1 93 | } 94 | 95 | override fun isEmpty() = first == null 96 | 97 | class ChainIterator(private val chain: MessageBody, i: Int = 0) : MutableListIterator { 98 | var index = 0 99 | var next = chain.first 100 | var previous: ChainItem? = null 101 | 102 | init { 103 | for (j in 1..i) next() 104 | } 105 | 106 | override fun hasNext() = next != null 107 | 108 | override fun nextIndex() = index 109 | override fun next(): MessageItem { 110 | val next = next ?: throw NoSuchElementException() 111 | 112 | val i = next.item 113 | this.index++ 114 | this.previous = next 115 | this.next = next.next 116 | 117 | return i 118 | } 119 | 120 | override fun hasPrevious() = previous != null 121 | override fun previousIndex() = if (previous == null) -1 else index - 1 122 | override fun previous(): MessageItem { 123 | val previous = previous ?: throw NoSuchElementException() 124 | 125 | val i = previous.item 126 | this.index-- 127 | this.next = previous 128 | this.previous = previous.previous 129 | 130 | return i 131 | } 132 | 133 | override fun add(element: MessageItem) { 134 | val chainItem = ChainItem(element, next, previous) 135 | if (next == null && previous == null) { 136 | chain._size = 1 137 | chain.first = chainItem 138 | chain.last = chainItem 139 | } else chain._size++ 140 | next?.previous = chainItem 141 | previous?.next = chainItem 142 | next = chainItem 143 | } 144 | 145 | override fun remove() { 146 | if (next == null) return 147 | val next = next!!.next 148 | if (previous == null) chain.first = next 149 | if (next == null) chain.last = previous 150 | previous?.next = next 151 | next?.previous = previous 152 | chain._size -= 1 153 | } 154 | 155 | override fun set(element: MessageItem) { 156 | next!!.item = element 157 | } 158 | 159 | } 160 | 161 | override fun iterator(): MutableIterator = ChainIterator(this) 162 | 163 | override fun lastIndexOf(element: MessageItem): Int { 164 | var item = first 165 | var index = -1 166 | for (i in 0 until size) { 167 | if (item!!.item == element) index = i else item = item.next 168 | } 169 | return index 170 | } 171 | 172 | override fun listIterator(): MutableListIterator = ChainIterator(this) 173 | 174 | override fun listIterator(index: Int): MutableListIterator = ChainIterator(this, index) 175 | 176 | override fun subList(fromIndex: Int, toIndex: Int): MessageBody { 177 | if (fromIndex < 0 || fromIndex > size) throw ArrayIndexOutOfBoundsException(fromIndex) 178 | if (toIndex < fromIndex || fromIndex > size) throw ArrayIndexOutOfBoundsException(toIndex) 179 | val list = MessageBody() 180 | var item = first 181 | for (i in 0..toIndex) { 182 | if (i in fromIndex..toIndex) list.append(item!!.item) 183 | item = item!!.next 184 | } 185 | return list 186 | } 187 | 188 | 189 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/items/At.kt: -------------------------------------------------------------------------------- 1 | package yuq.message.items 2 | 3 | import yuq.message.MessageItem 4 | 5 | class At(var target: String) : MessageItem { 6 | override val logString: String 7 | get() = "At_$target" 8 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/items/Face.kt: -------------------------------------------------------------------------------- 1 | package yuq.message.items 2 | 3 | import yuq.message.MessageItem 4 | 5 | class Face(val id: Int): MessageItem { 6 | override val logString: String 7 | get() = "face_$id" 8 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/items/Image.kt: -------------------------------------------------------------------------------- 1 | package yuq.message.items 2 | 3 | import yuq.message.MessageItem 4 | 5 | class Image( 6 | var id: String, 7 | var platform: String, 8 | var url: String 9 | ) : MessageItem { 10 | override val logString: String 11 | get() = "img_$id" 12 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/items/Text.kt: -------------------------------------------------------------------------------- 1 | package yuq.message.items 2 | 3 | import yuq.message.MessageItem 4 | 5 | class Text(val text: String) : MessageItem { 6 | override val logString: String 7 | get() = text 8 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/source/MessageFailByCancel.kt: -------------------------------------------------------------------------------- 1 | package yuq.message.source 2 | 3 | import yuq.message.MessageSource 4 | 5 | class MessageFailByCancel : MessageSource { 6 | override val id: String 7 | get() = "MessageFailByCancel" 8 | 9 | override suspend fun recall() { 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/yuq/message/source/MessageFailByReadTimeOut.kt: -------------------------------------------------------------------------------- 1 | package yuq.message.source 2 | 3 | import yuq.message.MessageSource 4 | 5 | class MessageFailByReadTimeOut: MessageSource { 6 | override val id: String 7 | get() = "MessageFailByReadTimeOut" 8 | 9 | override suspend fun recall() { 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /core/src/main/resources/conf/module/com.IceCreamQAQ.YuQ.yml: -------------------------------------------------------------------------------- 1 | yu: 2 | scanPackages: com.icecreamqaq.yuq 3 | cache: 4 | ehcaches: 5 | ContextSession: 6 | tti: 1800 7 | 8 | yuq: 9 | controller: 10 | raincode: 11 | enable: true 12 | prefix: ^ 13 | chat: 14 | strict: true -------------------------------------------------------------------------------- /core/src/test/java/com/IceCreamQAQ/YuQ/Test.java: -------------------------------------------------------------------------------- 1 | package com.IceCreamQAQ.YuQ; 2 | 3 | import com.icecreamqaq.yuq.message.At; 4 | import com.icecreamqaq.yuq.message.Message; 5 | 6 | import java.util.Arrays; 7 | 8 | public class Test { 9 | 10 | static class Time { 11 | private boolean t3; 12 | private Integer t1; 13 | private int t2; 14 | private String format; 15 | 16 | private String t4; 17 | private String t5; 18 | 19 | public String getFormat() { 20 | return format; 21 | } 22 | 23 | public void setFormat(String format) { 24 | this.format = format; 25 | } 26 | } 27 | 28 | public static void main(String[] args) { 29 | Message message = new Message(); 30 | 31 | message.getBody().stream().filter( it -> it instanceof At).forEach(it -> ((At) it).getUser()); 32 | // RainCode.registerRainCodeDecoder( 33 | // "SF", 34 | // "time", 35 | // Time.class, (item) -> YuQ.getMif().text(new SimpleDateFormat(item.getParas().getFormat()).format(new Date())) 36 | // ); 37 | 38 | Arrays.stream(Time.class.getDeclaredFields()).forEach(field -> System.out.println(field.getName())); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /devtools/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | } 4 | 5 | dependencies{ 6 | api(project(":core")) 7 | } -------------------------------------------------------------------------------- /devtools/src/main/kotlin/com/icecreamqaq/yuq/devtools/DevBot.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.devtools 2 | 3 | import com.icecreamqaq.yuq.* 4 | import com.icecreamqaq.yuq.contact.Account 5 | import com.icecreamqaq.yuq.contact.UserListImpl 6 | import com.icecreamqaq.yuq.devtools.contact.DevFriendList 7 | import com.icecreamqaq.yuq.devtools.contact.DevGroupList 8 | 9 | class DevBot( 10 | override val botInfo: Account 11 | ): Bot { 12 | 13 | var online = false 14 | 15 | override val platform: String 16 | get() = "qq" 17 | 18 | override val friends: DevFriendList = DevFriendList() 19 | override val groups: DevGroupList = DevGroupList() 20 | override val guilds: GuildList = UserListImpl() 21 | 22 | override fun refreshFriends(): FriendList = friends 23 | 24 | override fun refreshGroups(): GroupList = groups 25 | 26 | override fun refreshGuilds(): GuildList = guilds 27 | 28 | override fun id2platformId(id: Long): String = id.toString() 29 | 30 | override fun platformId2id(platformId: String): Long = platformId.toLong() 31 | override fun login() { 32 | TODO("Not yet implemented") 33 | } 34 | 35 | 36 | override fun close() { 37 | if (online) { 38 | online = false 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /devtools/src/main/kotlin/com/icecreamqaq/yuq/devtools/DevYuQ.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.devtools 2 | 3 | import com.icecreamqaq.yuq.* 4 | import com.icecreamqaq.yuq.devtools.contact.* 5 | import com.icecreamqaq.yuq.event.YuQApplicationStatusChanged 6 | import com.icecreamqaq.yuq.message.MessageItemFactory 7 | import rain.api.loader.ApplicationService 8 | 9 | class DevYuQ( 10 | override val messageItemFactory: MessageItemFactory, 11 | val service: BotService, 12 | configBots: List 13 | ) : YuQ, ApplicationService { 14 | 15 | data class ConfigMember( 16 | var id: Long = 0, 17 | var name: String = "", 18 | var permission: Int = 0, 19 | var title: String = "", 20 | var namecard: String = "", 21 | var ban: Long = 0, 22 | var lastMessageTime: Long = 0 23 | ) 24 | 25 | data class ConfigGroup( 26 | var id: Long = 0, 27 | var name: String = "", 28 | var maxCount: Int = 500, 29 | var members: List = ArrayList() 30 | ) 31 | 32 | data class ConfigFriend( 33 | var id: Long = 0, 34 | var name: String = "" 35 | ) 36 | 37 | data class ConfigBot( 38 | var id: Long = 0, 39 | var name: String = "", 40 | var friends: List = ArrayList(), 41 | var groups: List = ArrayList() 42 | ) 43 | 44 | override var bots = ArrayList() 45 | 46 | init { 47 | configBots.forEach { 48 | DevBot(DevAccount(it.id, it.name)).apply { 49 | bots.add(this) 50 | it.friends.forEach { f -> 51 | DevFriend(this, f.id, f.name).apply { 52 | friends.add(this) 53 | } 54 | } 55 | it.groups.forEach { cg -> 56 | DevGroup(this, cg.id, cg.name, cg.maxCount).also { group -> 57 | groups.add(group) 58 | group.initMembers( 59 | cg.members.map { cm -> 60 | DevGroupMember( 61 | this, 62 | group, 63 | cm.id, 64 | cm.name, 65 | cm.namecard, 66 | cm.permission, 67 | cm.title, 68 | cm.ban, 69 | cm.lastMessageTime 70 | ) 71 | } 72 | ) 73 | } 74 | } 75 | login() 76 | } 77 | } 78 | println("") 79 | } 80 | 81 | 82 | override fun createBot(id: String, pwd: String, botName: String?, extData: String?): Bot { 83 | return DevBot(DevAccount(id.toLong(), "")).apply { bots.add(this) } 84 | } 85 | 86 | 87 | override fun start() { 88 | } 89 | 90 | override fun stop() { 91 | } 92 | 93 | 94 | } -------------------------------------------------------------------------------- /devtools/src/main/kotlin/com/icecreamqaq/yuq/devtools/contact/DevAccount.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.devtools.contact 2 | 3 | import com.icecreamqaq.yuq.contact.Account 4 | 5 | open class DevAccount( 6 | override val id: Long, 7 | override val nickname: String 8 | ) : Account { 9 | override val platformId: String 10 | get() = id.toString() 11 | override val avatar: String 12 | get() = "https://q1.qlogo.cn/g?b=qq&nk=$id&s=640" 13 | } -------------------------------------------------------------------------------- /devtools/src/main/kotlin/com/icecreamqaq/yuq/devtools/contact/DevContact.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.devtools.contact 2 | 3 | import com.icecreamqaq.yuq.contact.Contact 4 | import com.icecreamqaq.yuq.devtools.DevBot 5 | import com.icecreamqaq.yuq.message.Image 6 | import com.icecreamqaq.yuq.message.Message 7 | import com.icecreamqaq.yuq.message.MessageSource 8 | import java.io.File 9 | 10 | abstract class DevContact( 11 | override val bot: DevBot, 12 | final override val id: Long, 13 | override val nickname: String 14 | ) : Contact { 15 | 16 | override val platformId: String = id.toString() 17 | 18 | override val avatar: String 19 | get() = "https://q1.qlogo.cn/g?b=qq&nk=$id&s=640" 20 | 21 | override fun sendMessage(message: Message): MessageSource { 22 | TODO("Not yet implemented") 23 | } 24 | 25 | override fun uploadImage(imageFile: File): Image { 26 | TODO("Not yet implemented") 27 | } 28 | 29 | override fun sendFile(file: File) { 30 | TODO("Not yet implemented") 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /devtools/src/main/kotlin/com/icecreamqaq/yuq/devtools/contact/DevFriend.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.devtools.contact 2 | 3 | import com.icecreamqaq.yuq.contact.Friend 4 | import com.icecreamqaq.yuq.devtools.DevBot 5 | import com.icecreamqaq.yuq.event.FriendDeleteEvent 6 | import com.icecreamqaq.yuq.post 7 | 8 | class DevFriend(bot: DevBot, id: Long, nickname: String) :Friend, DevContact(bot, id, nickname) { 9 | 10 | override val guid: String = "${bot.botId}_f_$id" 11 | override val logString: String = "$nickname($id)" 12 | 13 | override fun delete() { 14 | bot.friends.remove(this) 15 | FriendDeleteEvent(this).post() 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /devtools/src/main/kotlin/com/icecreamqaq/yuq/devtools/contact/DevGroup.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.devtools.contact 2 | 3 | import com.icecreamqaq.yuq.contact.Group 4 | import com.icecreamqaq.yuq.contact.GroupMember 5 | import com.icecreamqaq.yuq.contact.GroupNoticeList 6 | import com.icecreamqaq.yuq.devtools.DevBot 7 | import com.icecreamqaq.yuq.error.PermissionDeniedException 8 | import com.icecreamqaq.yuq.event.BotLeaveGroupEvent 9 | import com.icecreamqaq.yuq.post 10 | 11 | class DevGroup( 12 | bot: DevBot, 13 | id: Long, 14 | nickname: String, 15 | override val maxCount: Int 16 | ) : DevContact(bot, id, nickname), Group { 17 | 18 | override lateinit var botMember: GroupMember 19 | 20 | override val members = DevGroupMemberList() 21 | override lateinit var owner: GroupMember 22 | override val admins: List = ArrayList() 23 | override val notices: GroupNoticeList 24 | get() = TODO("Not yet implemented") 25 | 26 | fun initMembers(members: List) { 27 | for (member in members) { 28 | if (member.id == bot.botId) botMember = member 29 | else this.members.add(member) 30 | } 31 | if (!::botMember.isInitialized) 32 | botMember = DevGroupMember(bot, this, bot.botId, bot.botInfo.nickname, "", 0, "", 0, 0) 33 | } 34 | 35 | override fun leave() { 36 | bot.groups.remove(this) 37 | BotLeaveGroupEvent.Leave(this).post() 38 | } 39 | 40 | override fun banAll() { 41 | if (!botMember.isAdmin()) throw PermissionDeniedException() 42 | } 43 | 44 | override fun unBanAll() { 45 | if (!botMember.isAdmin()) throw PermissionDeniedException() 46 | } 47 | 48 | override val guid: String = "${bot.botId}_g_$id" 49 | override val logString: String = "$nickname($id)" 50 | } -------------------------------------------------------------------------------- /devtools/src/main/kotlin/com/icecreamqaq/yuq/devtools/contact/DevGroupMember.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.devtools.contact 2 | 3 | import com.icecreamqaq.yuq.contact.GroupMember 4 | import com.icecreamqaq.yuq.devtools.DevBot 5 | import com.icecreamqaq.yuq.error.PermissionDeniedException 6 | import com.icecreamqaq.yuq.event.GroupMemberLeaveEvent 7 | import com.icecreamqaq.yuq.post 8 | 9 | class DevGroupMember( 10 | bot: DevBot, 11 | override val group: DevGroup, 12 | id: Long, 13 | nickname: String, 14 | override var namecard: String, 15 | override val permission: Int, 16 | override var title: String, 17 | override var ban: Long, 18 | override val lastMessageTime: Long 19 | ) : DevContact(bot, id, nickname), GroupMember { 20 | 21 | override fun ban(time: Int) { 22 | if (group.botMember.permission <= permission) throw PermissionDeniedException() 23 | ban = System.currentTimeMillis() + time * 1000 24 | } 25 | 26 | override fun unBan() { 27 | if (group.botMember.permission <= permission) throw PermissionDeniedException() 28 | ban = 0 29 | } 30 | 31 | override fun kick(message: String) { 32 | if (group.botMember.permission <= permission) throw PermissionDeniedException() 33 | group.members.remove(this) 34 | GroupMemberLeaveEvent.Kick(group, this, group.botMember).post() 35 | } 36 | 37 | override val guid: String = "${bot.botId}_g_${group.id}_m_$id" 38 | 39 | override val logStringSingle: String = "${nameCardOrName()}($id)" 40 | override val logString: String = "${nameCardOrName()}($id)[${group.nickname}(${group.id})]" 41 | } -------------------------------------------------------------------------------- /devtools/src/main/kotlin/com/icecreamqaq/yuq/devtools/contact/Type.kt: -------------------------------------------------------------------------------- 1 | package com.icecreamqaq.yuq.devtools.contact 2 | 3 | import com.icecreamqaq.yuq.contact.UserListImpl 4 | 5 | typealias DevFriendList = UserListImpl 6 | typealias DevGroupList = UserListImpl 7 | typealias DevGroupMemberList = UserListImpl -------------------------------------------------------------------------------- /devtools/src/main/resources/conf/module/com.IceCreamQAQ.YuQ.DevTools.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuQWorks/YuQ/36e2e6d991125c8fc0763922df8d5fd813ff6cb8/devtools/src/main/resources/conf/module/com.IceCreamQAQ.YuQ.DevTools.properties -------------------------------------------------------------------------------- /devtools/src/test/java/yuq/test/java/TestJavaController.java: -------------------------------------------------------------------------------- 1 | package yuq.test.java; 2 | 3 | import com.icecreamqaq.yuq.annotation.PrivateController; 4 | 5 | @PrivateController 6 | public class TestJavaController { 7 | 8 | public String javaHello(long qq){ 9 | return "Hello Java! QQ: " + qq + "."; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /devtools/src/test/kotlin/yuq/test/Index.kt: -------------------------------------------------------------------------------- 1 | package yuq.test 2 | 3 | import com.icecreamqaq.yuq.YuQStarter 4 | 5 | fun main(){ 6 | YuQStarter.start() 7 | } -------------------------------------------------------------------------------- /devtools/src/test/kotlin/yuq/test/TestController.kt: -------------------------------------------------------------------------------- 1 | package yuq.test 2 | 3 | import com.icecreamqaq.yuq.annotation.BotAction 4 | import com.icecreamqaq.yuq.annotation.BotController 5 | import com.icecreamqaq.yuq.contact.GroupMember 6 | import kotlinx.coroutines.delay 7 | 8 | @BotController 9 | class TestController { 10 | 11 | @BotAction("HelloSuspend") 12 | suspend fun helloSuspend(qq: Long, spx: Int = 3): String { 13 | delay(1000) 14 | return "Hello Suspend! $qq! $spx" 15 | } 16 | 17 | @BotAction("HelloKotlin") 18 | fun helloKotlin(qq: Long, spx: String?): String { 19 | return "Hello Kotlin! $qq! $spx" 20 | } 21 | 22 | // 可以匹配内容 你好 @甲 @乙 @丙 @丁 23 | @BotAction("你好 {sbs}") 24 | fun helloArray(sbs: Array) { 25 | 26 | } 27 | 28 | /*** 29 | * 可以将 "Map匹配体质40精神60攻击20" 30 | * 转化为 { 31 | * "体质": 40, 32 | * "精神": 60, 33 | * "攻击": 20 34 | * } 35 | */ 36 | @BotAction("Map匹配{param}") 37 | fun map(param: Map) { 38 | println(param) 39 | } 40 | 41 | enum class Sex { 42 | 男, 女 43 | } 44 | 45 | /*** 46 | * 可以将 "录入成员张三男李四女王五男" 47 | * 转化为 { 48 | * "张三": 男, 49 | * "李四": 女, 50 | * "王五": 男 51 | * } 52 | */ 53 | @BotAction("录入成员{param}") 54 | fun map(param: Map) { 55 | println(param) 56 | } 57 | 58 | @BotAction("测试 [a] {b}") 59 | fun test(a: String, b: Int) { 60 | println("a: $a, b: $b") 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /devtools/src/test/kotlin/yuq/test/Ts.kt: -------------------------------------------------------------------------------- 1 | package yuq.test 2 | 3 | import com.icecreamqaq.yuq.BotService 4 | import kotlinx.coroutines.runBlocking 5 | import javax.inject.Inject 6 | 7 | class Ts { 8 | 9 | @Inject 10 | private lateinit var rainBot: BotService 11 | 12 | 13 | } -------------------------------------------------------------------------------- /devtools/src/test/resources/conf/DevTools.yml: -------------------------------------------------------------------------------- 1 | yu: 2 | scanPackages: yuq.test 3 | yuq: 4 | devtools: 5 | bots: 6 | - id: 100001 7 | name: DevToolsBot01 8 | friends: 9 | - id: 1001001 10 | name: DevBot01Friend01 11 | - id: 1001002 12 | name: DevBot01Friend02 13 | groups: 14 | - id: 1002001 15 | name: DevBot01Group01 16 | members: 17 | - id: 1003001 18 | name: DevBot01Group01Member01 19 | permissions: 2 20 | - id: 1003002 21 | name: DevBot01Group01Member02 22 | permissions: 1 23 | - id: 1003003 24 | name: DevBot01Group01Member03 25 | permissions: 0 26 | - id: 100002 27 | name: DevToolsBot02 28 | friends: 29 | - id: 1002001 30 | name: DevBot02Friend01 31 | - id: 1002002 32 | name: DevBot02Friend02 33 | groups: 34 | - id: 1004001 35 | name: DevBot02Group01 36 | members: 37 | - id: 1005001 38 | name: DevBot02Group01Member01 39 | permissions: 2 40 | - id: 1005002 41 | name: DevBot02Group01Member02 42 | permissions: 1 43 | - id: 1005003 44 | name: DevBot02Group01Member03 45 | permissions: 0 -------------------------------------------------------------------------------- /devtools/src/test/resources/ehcache-YuQ-Mirai-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 24 | 25 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /devtools/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | true 11 | 12 | %date %highlight(%-5level) %cyan(%logger{5}@[%-4.30thread]) - %msg%n 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ${LOG_HOME}/YuQ-Mirai.log.%d{yyyy-MM-dd}.log 21 | 22 | 30 23 | 24 | 25 | 26 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 10MB 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuQWorks/YuQ/36e2e6d991125c8fc0763922df8d5fd813ff6cb8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /platforms/qq/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | } 4 | 5 | dependencies { 6 | api(project(":core")) 7 | } -------------------------------------------------------------------------------- /platforms/qq/src/main/kotlin/yuq/qq/contact/QAccount.kt: -------------------------------------------------------------------------------- 1 | package yuq.qq.contact 2 | 3 | import yuq.contact.Account 4 | import yuq.contact.Friend 5 | import yuq.contact.Group 6 | import yuq.contact.GroupMember 7 | 8 | interface QAccount : Account { 9 | // 实际的 QQ 号码 10 | val platformId: Long 11 | } 12 | 13 | interface QFriend : Friend, QAccount 14 | interface QGroup : Group, QAccount 15 | interface QGroupMember : GroupMember, QAccount { 16 | override val group: QGroup 17 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |

YuQ

5 |
6 | 7 | ---- 8 | YuQ 是一个机器人快速开发框架。[开发文档](https://yuqworks.github.io/YuQ-Doc/) 9 | 10 | 现在 YuQ 将不仅仅是 QQ 机器人。 11 | 借由高度封装,YuQ 可以在各个平台做出兼容实现的 Runtime。 12 | 帮助您在多平台快速展开机器人。 13 | 14 | - 路由映射 15 | - 依赖注入 16 | - 定时任务 17 | - ORM支持 18 | - 上下文消息 19 | 20 | YuQ 使用 Kotlin 开发,并且完美支持 Java 与 Kotlin。 21 | 22 | 在 YuQ 我们仅需很简单的代码,就可以完成很复杂的功能。 23 | 比如,我们要针对一个指令 "Hello",进行一个标准的 "Hello World!" 消息回复。 24 | ```Java 25 | @GroupController 26 | public class GroupMenu{ 27 | @Action("Hello") 28 | public String menu(){ 29 | return "Hello World!"; 30 | } 31 | } 32 | ``` 33 | YuQ 会在指令式机器人的开发中,提供非常好的帮助,让开发者能有更好的开发体验。 34 | 在 Controller 中,我们的 Action 方法,返回的内容,会直接构建成消息,并发送当当前消息源。 35 | 通过路由映射,我们可以很方便的编写指令,只需要将 Class 声明为一个 Controller,并且编写 Action 方法。 36 | 其余的,YuQ 会帮您完成。 37 | 38 | 比如我们想禁言一个人,禁言的指令为"ban @xxx或QQ号码 time" 39 | 我们只需要编写: 40 | ```Java 41 | @GroupController 42 | public class GroupMenu{ 43 | @Action("ban {ban} {time}") 44 | public String ban(Member ban, int time){ 45 | ban.ban(time); 46 | return "好的!"; 47 | } 48 | } 49 | ``` 50 | 这样,我们就可以很轻易的完成 ban 这个指令了。 51 | 52 | 对于需要连续对话的指令式机器人,基于 YuQ 也可以轻松满足。 53 | 54 | 通过 ContextSession 提供的 waitNextMessage 方法,或者 ContextController,我们可以轻松完成上下文对话的机器人。 55 | Context -------------------------------------------------------------------------------- /readme/img/73.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuQWorks/YuQ/36e2e6d991125c8fc0763922df8d5fd813ff6cb8/readme/img/73.png -------------------------------------------------------------------------------- /readme/img/Context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YuQWorks/YuQ/36e2e6d991125c8fc0763922df8d5fd813ff6cb8/readme/img/Context.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "YuQ" 2 | 3 | fun includeProject(name: String, dir: String? = null) { 4 | include(name) 5 | dir?.let { project(name).projectDir = file(it) } 6 | } 7 | includeProject(":core") 8 | includeProject(":devtools") 9 | 10 | fun platform(name:String){ 11 | includeProject(":yuq-$name","platforms/$name") 12 | } 13 | platform("qq") --------------------------------------------------------------------------------