├── settings.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── src
└── main
│ ├── java
│ └── wiki
│ │ └── IceCream
│ │ └── yuq
│ │ └── demo
│ │ ├── controller
│ │ ├── TestPrivateController.java
│ │ ├── JsonController.java
│ │ ├── BpController.java
│ │ ├── TestContextController.java
│ │ ├── WeatherController.java
│ │ └── TestGroupController.java
│ │ ├── Start.java
│ │ ├── event
│ │ ├── myEvent
│ │ │ ├── MessageStartWithTag2.java
│ │ │ ├── MessageStartWithTag1.java
│ │ │ └── MyNewEvent.java
│ │ ├── FriendListEvent.java
│ │ ├── GroupManager.java
│ │ └── OnMessageEvent.java
│ │ └── job
│ │ └── JobMain.java
│ └── resources
│ ├── conf
│ └── YuQ.properties
│ ├── ehcache-YuQ-Mirai-demo.xml
│ └── logback.xml
├── readme.md
├── pom.xml
├── gradlew.bat
└── gradlew
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "YuQ-Mirai-Demo"
2 |
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YuQWorks/YuQ-Mirai-Demo/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | device.json
2 |
3 | .gradle/
4 | build/
5 | target/
6 | .idea/
7 |
8 | tmp/
9 | log/
10 | src/main/resources/conf/test**
11 |
12 | *.iml
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/src/main/java/wiki/IceCream/yuq/demo/controller/TestPrivateController.java:
--------------------------------------------------------------------------------
1 | package wiki.IceCream.yuq.demo.controller;
2 |
3 | import com.IceCreamQAQ.Yu.annotation.Action;
4 | import com.icecreamqaq.yuq.annotation.PrivateController;
5 |
6 |
7 | /**
8 | * 该触发器响应私聊消息,其他与群聊路由一致
9 | */
10 | @PrivateController
11 | public class TestPrivateController {
12 |
13 |
14 | @Action("hello")
15 | public String helloYuQ(){
16 | return "hello,welcome to yuq.";
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/wiki/IceCream/yuq/demo/Start.java:
--------------------------------------------------------------------------------
1 | package wiki.IceCream.yuq.demo;
2 |
3 | import com.icecreamqaq.yuq.mirai.YuQMiraiStart;
4 |
5 | public class Start {
6 |
7 | /***
8 | * 请不要往本类里面添加任何项目代码,也不要在本类里面引用任何类,以防止增强失败。
9 | * 这个问题在之后会解决。
10 | * 消息处理顺序 接受---->事件处理(参看event包)---->路由(参看controller包)
11 | * 特殊功能 job -->定时器 具体查看Job包
12 | * @param args 启动参数
13 | */
14 | public static void main(String[] args) {
15 | YuQMiraiStart.start(args);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/resources/conf/YuQ.properties:
--------------------------------------------------------------------------------
1 |
2 | # 运行模式,该模式会影响配置文件读取,或某些模块的行为。默认为 dev
3 | # yu.config.runMode = dev
4 |
5 | # 扫描包路径
6 | yu.scanPackages = wiki.IceCream.yuq.demo
7 | # 如果需要添加多个扫包路径,我们可以在当前配置节点前添加一个 [ 声明配置为一个 Array。
8 | # 多个扫包路径,我们只需要重复叠 [ 就可以了。
9 | # yu.[scanPackages = xxx.xxx
10 | # yu.[[scanPackages = yyy.yyy
11 |
12 | # 机器人名,可不配置。
13 | # YuQ.bot.name = Yu
14 |
15 | # 自定义 Ehcache 配置文件
16 | # yu.cache.ehcache.config = ehcache-YuQ-Mirai.xml
17 |
18 | # 登录的 QQ 号
19 | YuQ.Mirai.user.qq = 451717812
20 | # 登录的 QQ 号的密码
21 | YuQ.Mirai.user.pwd = qqqwww111
--------------------------------------------------------------------------------
/src/main/java/wiki/IceCream/yuq/demo/event/myEvent/MessageStartWithTag2.java:
--------------------------------------------------------------------------------
1 | package wiki.IceCream.yuq.demo.event.myEvent;
2 |
3 | import com.IceCreamQAQ.Yu.event.events.Event;
4 | import com.icecreamqaq.yuq.event.MessageEvent;
5 |
6 | /**
7 | * 参考一即可。
8 | */
9 | public class MessageStartWithTag2 extends Event {
10 |
11 | MessageEvent messageEvent;
12 | String startTag;
13 |
14 | public MessageStartWithTag2(MessageEvent messageEvent, String startTag) {
15 | this.messageEvent = messageEvent;
16 | this.startTag = startTag;
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/src/main/resources/ehcache-YuQ-Mirai-demo.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 | * 注意:消息在被传递到这里之前,会先经过 MessageEvent,则代表当取消事件时,消息并不会被传递到本处。 18 | *
19 | * Message 的相关快捷方法,被封装在 Message 的伴生对象内,既为 Message.Companion。 20 | * Message.Companion.firstString 方法,获取 MessageBody 的第一个文字元素,并且转换为 String。 21 | */ 22 | @Action("绑手机") 23 | public String bp(ContextSession session) { 24 | reply("请输入手机号码"); 25 | String phone = Message.Companion.firstString(session.waitNextMessage()); 26 | reply("请输入手机验证码"); 27 | String key = Message.Companion.firstString(session.waitNextMessage()); 28 | return String.format("您输入的手机号码为:%s,手机验证码为:%s。", phone, key); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/wiki/IceCream/yuq/demo/controller/TestContextController.java: -------------------------------------------------------------------------------- 1 | package wiki.IceCream.yuq.demo.controller; 2 | 3 | import com.IceCreamQAQ.Yu.annotation.Action; 4 | import com.icecreamqaq.yuq.annotation.*; 5 | import com.icecreamqaq.yuq.controller.BotActionContext; 6 | import com.icecreamqaq.yuq.controller.NextActionContext; 7 | import com.icecreamqaq.yuq.message.Message; 8 | 9 | /** 10 | * 此方法接近弃用,因为单路径有更加简洁的情况。 11 | * @see BpController 12 | */ 13 | @ContextController 14 | public class TestContextController { 15 | 16 | /** 17 | * 在 ContextController 里面的所有 Action,会被自动映射成上下文路由。 18 | *
19 | * 当上下文路由存在时,将优先进行上下文路由匹配。 20 | * 当上下文匹配失败时,并不会再进行正常的指令匹配。 21 | *
22 | * 上下文 Action 的功能与普通 Action 一致,只是触发条件不同。 23 | *
24 | * Save 注解表示,当 Action 正常完成时,将本参数内容,保存到 Session,可以供后续注入。 25 | * Save 保存名为 Named 注解声明内容,如参数未声明 Named 注解,则为参数名。 26 | */ 27 | @Action("bindPhone") 28 | @NextContext("phoneVerKey") 29 | @ContextTip("请输入手机号码。") 30 | @ContextTip(value = "手机号码输入错误,请重新输入。", status = 1) 31 | public void bindPhone(@Save @PathVar(0) String phone) { 32 | if (phone.length() != 11) throw new NextActionContext("bindPhone", 1); 33 | } 34 | 35 | /** 36 | * 此处的 phone 则是上一步中保存下来的 phone 37 | *
38 | * 当不再提供一个 NextContext 时,则完成一个上下文,回归普通。
39 | */
40 | @Action("phoneVerKey")
41 | @ContextTip("请输入手机验证码。")
42 | @ContextTip(value = "手机验证码输入错误,请重新输入。", status = 1)
43 | public String phoneVerKey(@PathVar(0) String key, String phone) {
44 | if (key.length() != 4) throw new NextActionContext("phoneVerKey", 1);
45 | return String.format("您要绑定的手机号为:%s,手机验证码为:%s,绑定成功!", phone, key);
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/wiki/IceCream/yuq/demo/job/JobMain.java:
--------------------------------------------------------------------------------
1 | package wiki.IceCream.yuq.demo.job;
2 |
3 | import com.IceCreamQAQ.Yu.annotation.Cron;
4 | import com.IceCreamQAQ.Yu.annotation.JobCenter;
5 | import com.IceCreamQAQ.Yu.util.DateUtil;
6 | import com.icecreamqaq.yuq.YuQ;
7 | import com.icecreamqaq.yuq.message.Message;
8 |
9 | import javax.inject.Inject;
10 |
11 | @JobCenter
12 | public class JobMain {
13 |
14 | /***
15 | * 时钟任务。
16 | * value 参数可接受 1d 1h 1m 1s 1S(天,小时,分钟,秒,毫秒)的参数。
17 | * 同时也支持类似于 5m5s 的组合参数。
18 | *
19 | * 当然如果你愿意,1d2S111m56h7998s333m 这样的参数也能顺利解析并正确使用。
20 | * 但是代码是写给自己看的,为什么要跟自己过不去呢?
21 | *
22 | * 时钟任务方法不接受任何参数,也不接受任何返回值。
23 | */
24 | @Cron("10s")
25 | public void ten() {
26 | System.out.println("到十秒钟啦!");
27 | }
28 |
29 | @Inject
30 | private DateUtil dateUtil;
31 |
32 | @Inject
33 | private YuQ yuq;
34 |
35 | @Cron("At::h::00")
36 | public void printTime() {
37 | // Message message = new Message().plus("内容");
38 |
39 | // yuq.getGroups().get(123456789L).sendMessage(message);
40 | }
41 |
42 | /***
43 | * 定时任务。
44 | * value 必须按规范写成组合
45 | * 以 At:: 开头。
46 | * 如果匹配每天的第几个小时的第几分钟,则接下来写 d。
47 | * 如果匹配某小时的第几分钟则写 h。
48 | * 接下来写分隔符 ::
49 | * 如果上一步写的是 d,则写 小时:分钟(这里只有一个 : )(二十四小时制)。例如: 12:00
50 | * 如果上一步写的是 h,则直接写第几分钟。例如: 00
51 | * 所有的冒号均是英文半角。
52 | *
53 | * 本实例在每个小时刚开始触发。
54 | *
55 | * 定时任务方法不接受任何参数,也不接受任何返回值。
56 | *
57 | * At::h::8:15 (每天的八点十五
58 | *
59 | * At::d::30 (每个小时的第30分钟
60 | */
61 | @Cron("At::h::00")
62 | public void at00() {
63 | System.out.println("现在是每个小时开始的第一分钟!" + dateUtil.formatDate());
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # YuQ-Mirai
2 |
3 | YuQ-Mirai 是一个依赖于 [Mirai](https://github.com/mamoe/mirai) 的 YuQ Framework 的机器人实现。
4 |
5 | YuQ Framework 的目的就是让机器人开发变得更简单和更有效率。
6 |
7 | 这是一个 Demo 项目。
8 | 框架依旧处于开发阶段,我将尽可能的保证 API 尽量少的变动。
9 |
10 | ##
11 |
12 | Demo 同时提供了 Maven 和 Gradle 的项目配置。
13 | 请选择一个喜欢的使用。
14 |
15 | ## 基础开发介绍:
16 |
17 | 在 YuQ 我们仅需很简单的代码,就可以完成很复杂的功能。
18 | 比如,我们要针对一个指令"菜单",进行一个标准的菜单消息回复。
19 | ```Java
20 | @GroupController
21 | public class GroupMenu{
22 | @Action("菜单")
23 | public String menu(){
24 | return "这是具体的菜单内容。";
25 | }
26 | }
27 | ```
28 | YuQ 会在指令式机器人的开发中,提供非常好的帮助,让开发者能有更好的开发体验。
29 | 在 Controller 中,我们的 Action 方法,返回的内容,会直接构建成消息,并发送当当前消息源。
30 | 通过路由映射,我们可以很方便的编写指令,只需要将 Class 声明为一个 Controller,并且编写 Action 方法。
31 | 其余的,YuQ 会帮您完成。
32 |
33 | 比如我们想禁言一个人,禁言的指令为"ban @xxx或QQ号码 time"
34 | 我们只需要编写:
35 | ```Java
36 | @GroupController
37 | public class GroupMenu{
38 | @Action("ban {ban} {time}")
39 | public String ban(Member ban, int time){
40 | ban.ban(time);
41 | return "好的!";
42 | }
43 | }
44 | ```
45 | 这样,我们就可以很轻易的完成 ban 这个指令了。
46 |
47 | 对于需要连续对话的指令式机器人,基于 YuQ 也可以轻松满足。
48 |
49 | 更详细的使用文档:[文档](https://yuqworks.github.io/YuQ-Doc/)
50 |
51 | ## 特性
52 |
53 | ### 路由映射
54 | 参考 wiki.IceCream.yuq.demo.controller.TestGroupController
55 | ### 依赖注入
56 | 参考 wiki.IceCream.yuq.demo.controller.TestGroupController
57 | ### 事件系统
58 | 参考 wiki.IceCream.yuq.demo.event.OnMessageEvent
59 | ### 后台队列
60 | 参考 wiki.IceCream.yuq.demo.job.JobMain
61 | ### 数据库支持
62 | 使用方法参考 [SuperDemo](https://github.com/YuQWorks/YuQ-SuperDemo)
63 | ### Web支持
64 | 使用方法参考 [SuperDemo](https://github.com/YuQWorks/YuQ-SuperDemo)
65 | ## 使用方法:
66 | clone
67 | 编辑 /src/main/resource/conf/YuQ.properties 填写合适的内容。
68 | run wiki.IceCream.yuq.demo.Start
69 |
70 | YuQ-Mirai 可直接启动,无需任何外部手段/依赖。
71 |
72 | 打包:
73 | ```
74 | ./gradlew build
75 | ```
76 | 对于 Eclipse 用户,可以考虑使用 `./gradlew eclipse` 命令生成 Eclipse 项目。
--------------------------------------------------------------------------------
/src/main/java/wiki/IceCream/yuq/demo/event/OnMessageEvent.java:
--------------------------------------------------------------------------------
1 | package wiki.IceCream.yuq.demo.event;
2 |
3 | import com.IceCreamQAQ.Yu.annotation.Event;
4 | import com.IceCreamQAQ.Yu.annotation.EventListener;
5 | import com.icecreamqaq.yuq.event.GroupMessageEvent;
6 | import com.icecreamqaq.yuq.event.MessageEvent;
7 | import com.icecreamqaq.yuq.event.PrivateMessageEvent;
8 | import com.icecreamqaq.yuq.message.Message;
9 |
10 | @EventListener
11 | public class OnMessageEvent {
12 |
13 |
14 | /***
15 | * 整体
16 | *
17 | * 当收到群聊消息时,本方法会被调用。
18 | * 事件会优先于控制器收到响应。
19 | * 事件可以被取消,当事件被取消之后,控制器将不会再响应。
20 | * @param event 事件
21 | */
22 | @Event(weight = Event.Weight.normal)
23 | public void onGroupMessage(GroupMessageEvent event) {
24 | System.out.printf("消息来自群:%s(%d)%n", event.getGroup().getName(), event.getGroup().getId());
25 | System.out.printf("消息来自群成员:%s(%d)%n" , event.getSender().getNameCard(),event.getSender().getId());
26 | }
27 |
28 | /***
29 | * 一个事件可以被重复注册多次,并可以通过指定优先级来让他们保持一定的先后顺序。
30 | * 本事件则简单地介绍了一个取消事件的方式。
31 | *
32 | * 当消息事件被取消后,后续将不会再进行控制器部分的响应了。
33 | * 也就是说,群号为 111 的群,永远不会响应到 Controller
34 | */
35 | @Event(weight = Event.Weight.low)
36 | public void onGroupMessageLow(GroupMessageEvent event) {
37 | if (111 == event.getGroup().getId()) {
38 | event.setCancel(true);
39 | }
40 | }
41 |
42 | /***
43 | * 当收到私聊消息时,本方法会被调用。
44 | * 事件会优先于控制器收到响应。
45 | * 事件可以被取消,当事件被取消之后,控制器将不会再响应。
46 | * @param event 事件
47 | */
48 | @Event
49 | public void onPrivateMessage(PrivateMessageEvent event) {
50 | System.out.printf("消息来自群成员:%s(%d)%n" , event.getSender().getName(),event.getSender().getId());
51 | }
52 |
53 | /***
54 | * 当你注册了某个事件的父事件的时候,则这个父事件的所有子事件都会被响应。
55 | * 如 MessageEvent 是 PrivateMessageEvent 和 GroupMessageEvent 的父事件,
56 | * 则 PrivateMessageEvent 和 GroupMessageEvent 事件触发的时候,监听了 MessageEvent 事件的监听器都会受到响应。
57 | */
58 | @Event
59 | public void onMessage(MessageEvent event) {
60 |
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
40 | * 指令格式应该是 "weather 南京" 41 | *
42 | * 使用 @Synonym 使命令拥有别名 43 | *
44 | * 所以”天气 南京“ 和 ”天气预报 南京“ 同样会触发命令 45 | */ 46 | @Action("weather {city}") 47 | @Synonym({"天气 {city}","天气预报 {city}"}) 48 | @QMsg 49 | public Message weather(String city){ 50 | if(city.equals("")){ 51 | //使用mif将String变成Message 52 | return mif.text("要查询的城市名称不能为空").toMessage(); 53 | } 54 | //使用伴生方法将String变成Message 55 | return Message.Companion.toMessage(city+"的天气是。。。。"); 56 | } 57 | 58 | 59 | /** 60 | * After与 Before 极其类似,除了出现的时间点不同,使用和逻辑上基本一致 61 | * @param reMessage 这是由Action处理完发送的信息,目前还没有交给服务器 62 | * @param qq 同Before 63 | * @return 并非直接返回信息来源,可以在后续@After中进行使用,所以这个方法无返回值,直接修改reMessage也可实现。 64 | */ 65 | @After 66 | public Message addNotice(Message reMessage,long qq){ 67 | //使用mif添加At效果,同理可使用mif添加图片等。 68 | return reMessage.plus(mif.at(qq)); 69 | } 70 | 71 | 72 | /** 73 | * 直接使用注入来获取YuQ对象,对象内存储着Bot的各种信息,包括 群列表,好友列表等。 74 | */ 75 | @Inject 76 | private YuQ yuq; 77 | 78 | /** 79 | * Catch 与 Before 、 After 类似,但是必须要求参数来捕获指定异常 80 | * @param exception 这里捕获空指针异常 81 | * @return 也并非返回信息来源,而是留给后续使用 82 | */ 83 | @Catch(error = NullPointerException.class) 84 | public void reportNullPoint(NullPointerException exception){ 85 | //从YuQ(此时的YuQ是你的Bot)中获取指定对象并且发送信息。 86 | yuq.getFriends().get(12345L).sendMessage(mif.text(exception.toString()).toMessage()); 87 | } 88 | 89 | 90 | 91 | } 92 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/main/java/wiki/IceCream/yuq/demo/controller/TestGroupController.java: -------------------------------------------------------------------------------- 1 | package wiki.IceCream.yuq.demo.controller; 2 | 3 | import com.IceCreamQAQ.Yu.annotation.Action; 4 | import com.IceCreamQAQ.Yu.annotation.Before; 5 | import com.IceCreamQAQ.Yu.annotation.Synonym; 6 | import com.IceCreamQAQ.Yu.job.JobManager; 7 | import com.icecreamqaq.yuq.YuQ; 8 | import com.icecreamqaq.yuq.annotation.GroupController; 9 | import com.icecreamqaq.yuq.annotation.NextContext; 10 | import com.icecreamqaq.yuq.entity.Group; 11 | import com.icecreamqaq.yuq.entity.Member; 12 | import com.icecreamqaq.yuq.message.Message; 13 | import com.icecreamqaq.yuq.message.MessageItem; 14 | import com.icecreamqaq.yuq.message.MessageItemFactory; 15 | 16 | import javax.inject.Inject; 17 | 18 | /*** 19 | * GroupController 代表了,这个 Controller 将响应群消息。 20 | */ 21 | @GroupController 22 | public class TestGroupController { 23 | 24 | /*** 25 | * YuQ 接口是 YuQ Framework 向用户提供的一个便于使用的 API。 26 | * 通过注入 YuQ 来获得 YuQ 的实例,来调用 YuQ Framework 的绝大部分功能。 27 | * 如发送消息,撤回消息等等。 28 | */ 29 | @Inject 30 | private YuQ yuq; 31 | 32 | /*** 33 | * MessageItemFactory 用来创建 Message 的具体内容。 34 | */ 35 | @Inject 36 | private MessageItemFactory mif; 37 | 38 | /*** 39 | * Before 则为具体的控制器的动作前置验证,也可以称作拦截器,负责在 Action 处理消息之前进行验证。 40 | * 41 | * Before 方法接收 0 - 多个参数,通常,您所写下的参数,将会以名称匹配来进行依赖注入。 42 | * 支持注入的名称 - 注入的内容 43 | * qq - 发送消息的 QQ 账号 44 | * group - 发送消息的 QQ 群号 45 | * message - 具体的 Message 对象 46 | * messageId - 消息 ID 47 | * sourceMessage - 未经处理的源消息内容,则为具体的 Runtime 的消息内容 48 | * actionContext - 当前消息的 ActionContext 对象 49 | * 以及,您 Before 传递回来的需要保存的对象。 50 | * 51 | * Before 方法可以接受任何类型的返回值,当您返回了一个值的时候,框架会帮您保存起来,名称则为将类名的第一个字母转化小写后的名字。 52 | * 53 | * Before 方法可以抛出异常,来作为验证失败的中断处理方法。 54 | * 当您抛出了一个 Message 类型的异常后,如果您没有设置任何接收的 QQ,或 QQ 群,那么我们将会将消息发送至当前消息来源者,如果您设置了接收对象,那么发送至您的接收对象。 55 | * 当您想中断处理链路,并且不进行任何返回的时候,您可以抛出 DoNone 类型的异常。 56 | * 57 | * 一个 Controller 类内,可以接受多个 Before,他们按照一定的顺序依次执行,当所有 Before 执行完成之后,将继续执行 Action。 58 | */ 59 | @Before 60 | public void before(long qq) { 61 | if (qq % 2 == 0) throw mif.text("你没有使用该命令的权限!").toMessage().toThrowable(); 62 | } 63 | 64 | private boolean menuFlag = true; 65 | 66 | /*** 67 | * Action 则为具体的控制器的动作,负责处理收到的消息。 68 | * 69 | * Action 方法接收 0 - 多个参数,通常,您所写下的参数,将会以名称匹配来进行依赖注入。 70 | * 支持注入的名称 - 注入的内容 71 | * qq - 发送消息的 QQ 账号 72 | * group - 发送消息的 QQ 群号 73 | * message - 具体的 Message 对象 74 | * messageId - 消息 ID 75 | * sourceMessage - 未经处理的源消息内容,则为具体的 Runtime 的消息内容 76 | * actionContext - 当前消息的 ActionContext 对象 77 | * 以及,您 Before 传递回来的需要保存的对象。 78 | * 79 | * Action 可接收方法可以接受任何类型的返回值,当您返回了一个值的时候, 80 | * 如果您返回的是 Message 类型的时候,我们会帮您发送这个消息,如果您没有设置任何接收的 QQ,或 QQ 群,那么我们将会将消息发送至当前消息来源者,如果您设置了接收对象,那么发送至您的接收对象。 81 | * 如果您返回了一个 String 类型的时候,我们会帮您构建一个 Message,并发送到当前消息的来源。 82 | * 如果您返回了一个 MessageItem 类型的时候,我们会帮您构建一个 Message,并发送到当前消息的来源。 83 | * 如果您返回的是其他类型,我们会帮您调用 toString 方法,并构建一个 Message,然后发送到当前消息的来源。 84 | * 85 | * Action 方法可以抛出异常,来返回一些信息。 86 | * 当您抛出了一个 Message 类型的异常后,如果您没有设置任何接收的 QQ,或 QQ 群,那么我们将会将消息发送至当前消息来源者,如果您设置了接收对象,那么发送至您的接收对象。 87 | * 当您想中断处理链路,并且不进行任何返回的时候,您可以抛出 DoNone 类型的异常。 88 | * @return 89 | */ 90 | @Action("菜单") 91 | public Object menu(long qq) { 92 | if (menuFlag) 93 | return mif.at(qq).plus(",您好。\n" + 94 | "这里是基础菜单。" + 95 | "但是由于这是一个演示 Demo,他没有什么功能。" + 96 | "所以也并没有菜单。" + 97 | "那就这样吧。"); 98 | return "菜单被禁用!"; 99 | } 100 | 101 | /*** 102 | * Action 内,不仅可以写单级指令,还可以写多级指令。 103 | * 最后的 {flag} 则代表了一个可变内容,他可以根据方法参数类型,自动映射为指定类型。 104 | */ 105 | @Action("设置 菜单开关 {flag}") 106 | public String menu2(boolean flag) { 107 | menuFlag = flag; 108 | return "菜单开关:" + flag; 109 | } 110 | 111 | /*** 112 | * 可以在路由内书写 { 名称 : 正则表达式 } 来动态匹配指令上的内容。 113 | * 如果你想匹配任意内容,则 : 及后续可以省略。示例:{color} 114 | * 本例子则代表只匹配单个文本。 115 | */ 116 | @Action("发个{color:.}包") 117 | public String sendPackage(String color) { 118 | return String.format("QQ%s包!", color); 119 | } 120 | 121 | /*** 122 | * YuQ 可以将您发送的数字QQ号,或者 At 某人,智能转化为您所需要的内容。 123 | * 本处就将对象转化为 Member 的实例。 124 | * 通过调用 Member 的 ban 方法,可以将目标禁言一段时间。单位:秒。 125 | * 通过书写 Member 类型的 qq 参数,即可获取当前消息发送者的 Member 实例。 126 | * 通过调用 Member 的 isAdmin 方法,可以获取当前目标是否具有管理员权限。(管理员与群主都具有管理员权限) 127 | * 128 | * 本 Action 的作用,如果发送者是管理员,就将目标禁言一段时间,如果发送人不是管理员,就将自己禁言一段时间。 129 | */ 130 | @Action("禁言 {sb} {time}") 131 | @Synonym({"ban {sb} {time}"}) 132 | public String ban(Member sb, Member qq, int time) { 133 | if (time < 60) time = 60; 134 | if (qq.isAdmin()) { 135 | sb.ban(time); 136 | return "好的"; 137 | } 138 | qq.ban(time); 139 | return "您没有使用该命令的权限!为了防止恶意操作,你已被禁言相同时间。"; 140 | } 141 | 142 | @Inject 143 | private JobManager jobManager; 144 | 145 | /*** 146 | * 我们可以通过注入 JobManager 快速,动态的创建定时与时钟任务。 147 | * 写在路由中的 {time} 与整级路由 {nr} 最大的不同,就是写在路由中的,只能用 String 类型接受,并不能智能匹配成其他类型。 148 | */ 149 | @Action("{time}秒后说 {nr}") 150 | public Object timeSend(String time, MessageItem nr, Group group) { 151 | Message nm = new Message().plus(nr); 152 | jobManager.registerTimer(() -> group.sendMessage(nm), Integer.parseInt(time) * 1000); 153 | return "好的"; 154 | } 155 | 156 | 157 | /*** 158 | * NextContext 注解用来声明完成之后进入某个上下文。 159 | * 这是一个用来进入上下文的 Action,当然,他与普通的 Action 没有什么区别。 160 | * 他可以完成普通 Action 的所有功能。 161 | * 只是当他在正常完成之后,会帮你把上下文自动切换到 NextContext 中声明的内容。 162 | * 163 | * 正常完成则为,当 Action 方法没有产生任何异常时,Action 方为正常完成。 164 | */ 165 | @Action("绑定手机号") 166 | @NextContext("bindPhone") 167 | public void bindPhone(long qq) { 168 | if (qq % 2 == 0) throw mif.text("您无需绑定手机号码。").toMessage().toThrowable(); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------