├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── chatbot-spring-boot-autoconfigure ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── kingbbode │ │ └── chatbot │ │ └── autoconfigure │ │ ├── ChatbotAutoConfiguration.java │ │ └── messenger │ │ ├── line │ │ └── LineAutoConfiguration.java │ │ ├── slack │ │ ├── SlackAutoConfiguration.java │ │ └── SlackProperties.java │ │ └── telegram │ │ ├── TelegramAutoConfiguration.java │ │ └── TelegramProperties.java │ └── resources │ └── META-INF │ └── spring.factories ├── chatbot-spring-boot-core ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── github │ └── kingbbode │ └── chatbot │ └── core │ ├── ChatbotProperties.java │ ├── base │ ├── BaseBrain.java │ ├── knowledge │ │ ├── brain │ │ │ └── KnowledgeBrain.java │ │ └── component │ │ │ └── KnowledgeComponent.java │ └── stat │ │ └── StatComponent.java │ ├── brain │ ├── DefaultDispatcherBrain.java │ ├── DispatcherBrain.java │ ├── DistributedEnvironment.java │ ├── aop │ │ └── BrainCellAspect.java │ ├── cell │ │ ├── AbstractBrainCell.java │ │ ├── BrainCell.java │ │ ├── CommonBrainCell.java │ │ └── KnowledgeBrainCell.java │ └── factory │ │ ├── BrainFactory.java │ │ └── BrainFactoryCustomizer.java │ ├── common │ ├── annotations │ │ ├── Brain.java │ │ └── BrainCell.java │ ├── enums │ │ └── GrantType.java │ ├── exception │ │ ├── ArgumentInvalidException.java │ │ ├── BrainException.java │ │ └── InvalidReturnTypeException.java │ ├── interfaces │ │ ├── Dispatcher.java │ │ └── EventSensor.java │ ├── properties │ │ └── BotProperties.java │ ├── request │ │ └── BrainRequest.java │ ├── result │ │ ├── BrainResult.java │ │ ├── DefaultBrainResult.java │ │ └── SimpleMessageBrainResult.java │ └── util │ │ ├── BrainUtil.java │ │ └── RestTemplateFactory.java │ ├── conversation │ ├── Conversation.java │ └── ConversationService.java │ └── event │ ├── EmptyResultException.java │ ├── Event.java │ ├── EventQueue.java │ └── TaskRunner.java ├── chatbot-spring-boot-line-starter └── build.gradle ├── chatbot-spring-boot-slack-starter └── build.gradle ├── chatbot-spring-boot-telegram-starter └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── conv1.png ├── conv2.png ├── example.png ├── line │ ├── 1_create_app.png │ ├── 2_create_app.png │ ├── 2_secret.png │ ├── 3_use_webhook.png │ ├── 4_create_token.png │ ├── 5_ngrok.png │ └── 6_reg_callback.png ├── sample.jpg ├── slack │ ├── 1_create_app.png │ ├── 2_app_token.png │ ├── 3_bot_token.png │ ├── 4_scope.png │ ├── 5_on_socket_mode.png │ └── 6_event_subscribe.png └── telegram │ └── 1_create_bot.png ├── messenger ├── line │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── github │ │ └── kingbbode │ │ └── messenger │ │ └── line │ │ ├── LineDispatcher.java │ │ └── LineEventSensor.java ├── slack │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── github │ │ └── kingbbode │ │ └── messenger │ │ └── slack │ │ ├── SlackBotClient.java │ │ ├── SlackDispatcher.java │ │ ├── SlackEventSensor.java │ │ ├── SlackMessage.java │ │ ├── event │ │ ├── BlockActionDispatcherBrain.java │ │ └── SlackEvent.java │ │ └── result │ │ └── SlackMessageBrainResult.java └── telegram │ ├── build.gradle │ └── src │ └── main │ └── java │ └── com │ └── github │ └── kingbbode │ └── messenger │ └── telegram │ ├── TelegramBotsApiWrapper.java │ └── TelegramEventSensor.java ├── sample-bot ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── kingbbode │ │ └── chatbot │ │ └── sample │ │ ├── Application.java │ │ ├── brain │ │ └── HelloWorldBrain.java │ │ └── config │ │ └── SlackActionConfig.java │ └── resources │ └── application.yml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### IntelliJ IDEA ### 6 | .idea 7 | *.iws 8 | *.iml 9 | *.ipr 10 | out 11 | 12 | ### NetBeans ### 13 | nbproject/private/ 14 | build/ 15 | nbbuild/ 16 | dist/ 17 | nbdist/ 18 | .nb-gradle/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 YongGeun Kwon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kingbbode Spring Boot Chatbot 2 | 3 | [![Release](https://jitpack.io/v/kingbbode/spring-boot-chatbot.svg)](https://jitpack.io/#kingbbode/spring-boot-chatbot) 4 | 5 | 스프링 부트 기반 대화형 챗봇 서포트 프레임워크! 6 | 7 | - Spring Framework 기반 8 | - 1:1 대화형 제공 9 | - 개발하기 쉬운(?) 인터페이스 제공 10 | 11 | ## 제공 Starter Pack 12 | 13 | - chatbot-spring-boot-slack-starter 14 | - chatbot-spring-boot-line-starter 15 | - chatbot-spring-boot-telegram-starter 16 | 17 | ## Release Notes 18 | 19 | ### 0.6.0 20 | 21 | - Support Distribute Environment. 22 | 23 | ### 0.5.0 24 | 25 | - Slack Library Change (allbegray -> slack sdk) 26 | 27 | --- 28 | 29 | # 소개 30 | 31 | ## echo example 32 | 33 | 34 | ![example](./img/example.png) 35 | 36 | ```java 37 | @Brain 38 | public class FirstBrain { 39 | 40 | @BrainCell(key = "따라해봐", function = "echo-start") 41 | public String echo(BrainRequest brainRequest) { 42 | return "말해봐"; 43 | } 44 | 45 | @BrainCell(function = "echo-end", parent = "echo-start") 46 | public String echo2(BrainRequest brainRequest) { 47 | return brainRequest.getContent(); 48 | } 49 | } 50 | ``` 51 | 52 | ### @Brain 53 | 54 | `@Brain` 은 Spring Framework 의 `@Controller` 와 같은 역할을 합니다. 55 | 56 | 봇의 기능의 탐색을 위한 Bean 등록을 위한 어노테이션입니다. 57 | 58 | ### @BrainCell 59 | 60 | `@BrainCell` 은 Spring Framework 의 `@RequestMapping` 과 같은 역할을 합니다. 61 | 62 | 봇의 기능을 나타내는 어노테이션입니다. 63 | 64 | #### @BrainCell 속성 65 | 66 | - key : 명령어입니다. 67 | - function : 고유 key 입니다. 68 | - parent : 부모 기능을 나타냅니다. parent 가 있을 시 1단계 명령어로는 등록되지 않고, 부모 기능의 다음 명령어로 동작합니다. 69 | 70 | #### 대화형 기능 작성 71 | 72 | 단계적인 기능 접근 기능입니다. 예를 들어 최상위 기능으로 `A` 와 `B` 가 있고 `C`가 `A`의 자식일 경우, 첫 명령어로는 `A` 와 `B`만 노출되며, `C`는 `A` 명령어 이후로만 실행 됩니다. 73 | 74 | - key가 없는 자식은 모든 텍스트 메시지를 받습니다. 75 | 76 | ```java 77 | @BrainCell(function = "echo-end", parent = "echo-start") 78 | public String echo2(BrainRequest brainRequest) { 79 | return brainRequest.getContent(); 80 | } 81 | ``` 82 | ![conv2](./img/conv2.png) 83 | 84 | - 자식들에게 key가 있을 때는 자식의 key로만 동작합니다. 85 | 86 | ```java 87 | @BrainCell(function = "echo-end-1", key= "조회" parent = "echo-start") 88 | public String echo2(BrainRequest brainRequest) { 89 | return brainRequest.getContent(); 90 | } 91 | 92 | @BrainCell(function = "echo-end-2", key= "저장" parent = "echo-start") 93 | public String echo2(BrainRequest brainRequest) { 94 | return brainRequest.getContent(); 95 | } 96 | ``` 97 | 98 | ![conv1](./img/conv1.png) 99 | 100 | - 대화는 명령어 사용 유저와 1:1 로 작동합니다. 다른 유저와는 독립적으로 동작합니다 (다른 유저가 명령어 중간에 끼어들 수 없음) 101 | 102 | ## 1 어플리케이션 3 메신저 통합 가능 103 | 104 | ### Gradle 105 | 106 | ``` 107 | dependencies { 108 | compile 'com.github.kingbbode.spring-boot-chatbot:chatbot-spring-boot-slack-starter:{version}' 109 | compile 'com.github.kingbbode.spring-boot-chatbot:chatbot-spring-boot-line-starter:{version}' 110 | compile 'com.github.kingbbode.spring-boot-chatbot:chatbot-spring-boot-telegram-starter:{version}' 111 | } 112 | ``` 113 | 114 | 3가지 메신저에서 하나의 챗봇이 동작 할 수 있습니다. 115 | 116 | ![sample](./img/sample.jpg) 117 | 118 | --- 119 | 120 | # 연동 121 | 122 | ## Gradle 123 | 124 | ``` 125 | allprojects { 126 | repositories { 127 | ... 128 | maven { url 'https://jitpack.io' } 129 | } 130 | } 131 | ``` 132 | 133 | ``` 134 | dependencies { 135 | compile 'com.github.kingbbode:{select starter pack}:{version}' 136 | } 137 | ``` 138 | 139 | ## Properties 140 | 141 | ```java 142 | # CHAT-BOT 143 | chatbot.name = default # 내부적으로 사용되는 챗봇 고유 이름 144 | chatbot.base-package = # Brain Scan Package. 145 | chatbot.enabled = true # 챗봇 활성화 여부 146 | chatbot.enableBase = true # 기본 기능 사용 여부 (#기능 : 기능 목록 출력) 147 | chatbot.enableKnowledge = true # 학습 기능 사용 여부 (심심이 같은 기능) 148 | 149 | # Redis (default Embeded Redis) 150 | chatbot.hostName = localhost 151 | chatbot.port = 6879 152 | chatbot.timeout = 0 153 | chatbot.password = # password 154 | chatbot.usePool = true 155 | chatbot.useSsl = false 156 | chatbot.db-index = 0 157 | chatbot.client-name = # clientName 158 | chatbot.convert-pipeline-and-tx-results = true 159 | 160 | # Command 161 | chatbot.command-prefix = "#" # 커맨드 접두어 설정. 162 | ``` 163 | 164 | ## Slack Starter 165 | 166 | ### Gradle 167 | 168 | ``` 169 | dependencies { 170 | compile 'com.github.kingbbode.spring-boot-chatbot:chatbot-spring-boot-slack-starter:{version}' 171 | } 172 | ``` 173 | 174 | ### Slack Bot 생성 및 토큰 발급 175 | 176 | 슬랙 앱을 생성합니다. 177 | 178 | ![1_create_app](./img/slack/1_create_app.png) 179 | 180 | APP TOKEN 을 획득합니다. 181 | 182 | ![2_app_token](./img/slack/2_app_token.png) 183 | 184 | BOT TOKEN 을 획득합니다. 185 | 186 | ![3_bot_token](./img/slack/3_bot_token.png) 187 | 188 | BOT TOKEN 의 스코프를 추가합니다. 189 | 190 | ![4_scope](./img/slack/4_scope.png) 191 | 192 | 소켓 모드를 활성화합니다. 193 | 194 | ![5_on_socket_mode](./img/slack/5_on_socket_mode.png) 195 | 196 | 이벤트를 구독합니다. 197 | 198 | ![6_event_subscribe](./img/slack/6_event_subscribe.png) 199 | 200 | ### Properties 201 | 202 | 발급받은 token을 `application.properties` 혹은 `application.yml` 에 추가합니다. 203 | 204 | ``` 205 | slack: 206 | app-token: xxxxx 207 | bot-token: xxxxx 208 | ``` 209 | 210 | ## Line Starter 211 | 212 | ### Gradle 213 | 214 | ``` 215 | dependencies { 216 | compile 'com.github.kingbbode.spring-boot-chatbot:chatbot-spring-boot-line-starter:{version}' 217 | } 218 | ``` 219 | 220 | ### Line Bot 생성 및 토큰 발급 221 | 222 | Line Bot을 생성합니다. 223 | 224 | ![1_create_app](./img/line/1_create_app.png) 225 | 226 | ![2_create_app](./img/line/2_create_app.png) 227 | 228 | 생성된 `secret` 을 기억해둡니다. 229 | 230 | ![2_secret](./img/line/2_secret.png) 231 | 232 | WebHook을 활성화합니다. 233 | 234 | ![1_create_app](./img/line/3_use_webhook.png) 235 | 236 | `토큰` 을 생성합니다. 237 | 238 | ![1_create_app](./img/line/4_create_token.png) 239 | 240 | spring boot application `port` 와 연동되는 `callback url`을 등록합니다. 241 | 242 | 로컬 테스트시 `ngrok` 사용을 추천합니다. 243 | 244 | ``` 245 | ngrok http 8080 246 | ``` 247 | 248 | ![5_ngrok](./img/line/5_ngrok.png) 249 | 250 | `callback url` 을 등록합니다. 251 | 252 | ![6_reg_callback](./img/line/6_reg_callback.png) 253 | 254 | ### Properties 255 | 256 | 발급받은 token 과 secret 을 `application.properties` 혹은 `application.yml` 에 추가합니다. 257 | 258 | ``` 259 | line: 260 | bot: 261 | channel-token: xxx 262 | channel-secret: xxx 263 | ``` 264 | 265 | ## Telegram Starter 266 | 267 | ### Gradle 268 | 269 | ``` 270 | dependencies { 271 | compile 'com.github.kingbbode.spring-boot-chatbot:chatbot-spring-boot-telegram-starter:{version}' 272 | } 273 | ``` 274 | 275 | ### Telegram Bot 생성 및 토큰 발급 276 | 277 | BotFather 를 통해 봇을 생성하고 토큰을 발급받습니다. 278 | 279 | ![6_reg_callback](./img/telegram/1_create_bot.png) 280 | 281 | ### Properties 282 | 283 | 발급받은 token 을 `application.properties` 혹은 `application.yml` 에 추가합니다. 284 | 285 | ``` 286 | telegram: 287 | name: kingbbode-sample 288 | token: xxx 289 | ``` 290 | 291 | --- 292 | 293 | ## History 294 | 295 | 1. 첫 프레임워크화 296 | https://github.com/kingbbode/chatbot-framework 297 | 298 | 2. starter pack 구성 시작 299 | https://github.com/ultzum/spring-boot-starter-chatbot 300 | 301 | 3. 현재 302 | https://github.com/kingbbode/spring-boot-chatbot 303 | 304 | --- 305 | 306 | # TODO 307 | - 테스트 코드 작성 ( 폭망..) 308 | - 버전 상향 309 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | mavenCentral() 5 | maven { url "https://jitpack.io" } 6 | } 7 | } 8 | 9 | plugins { 10 | id 'org.springframework.boot' version '2.1.7.RELEASE' 11 | id 'io.spring.dependency-management' version '1.0.8.RELEASE' 12 | } 13 | 14 | ext { 15 | set('springCloudVersion', "Greenwich.SR2") 16 | } 17 | 18 | group 'com.github.kingbbode' 19 | version '0.7.3' 20 | 21 | configure(subprojects.findAll {it.name != 'messenger'}) { 22 | apply plugin: 'java' 23 | apply plugin: 'org.springframework.boot' 24 | apply plugin: 'io.spring.dependency-management' 25 | 26 | repositories { 27 | jcenter() 28 | mavenCentral() 29 | maven { url "https://jitpack.io" } 30 | } 31 | 32 | configurations { 33 | compileOnly { 34 | extendsFrom annotationProcessor 35 | } 36 | } 37 | 38 | 39 | dependencyManagement { 40 | imports { 41 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" 42 | } 43 | dependencies { 44 | dependency "javax.websocket:javax.websocket-api:1.1" 45 | dependency "org.glassfish.tyrus.bundles:tyrus-standalone-client:1.17" 46 | dependency "com.google.code.gson:gson:2.8.6" 47 | dependency "org.jetbrains.kotlin:kotlin-stdlib:1.3.72" 48 | dependency "com.slack.api:slack-api-client:1.16.0" 49 | dependency "com.slack.api:bolt-socket-mode:1.16.0" 50 | dependency "com.linecorp.bot:line-bot-spring-boot:1.14.0" 51 | dependency "org.telegram:telegrambots:3.6.1" 52 | } 53 | } 54 | 55 | dependencies { 56 | compileOnly 'org.projectlombok:lombok' 57 | annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' 58 | annotationProcessor 'org.projectlombok:lombok' 59 | testCompile("junit:junit:4.12") 60 | } 61 | } -------------------------------------------------------------------------------- /chatbot-spring-boot-autoconfigure/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | compile project(":chatbot-spring-boot-core") 6 | compileOnly project(":messenger:line") 7 | compileOnly project(":messenger:telegram") 8 | compileOnly project(":messenger:slack") 9 | } 10 | -------------------------------------------------------------------------------- /chatbot-spring-boot-autoconfigure/src/main/java/com/github/kingbbode/chatbot/autoconfigure/ChatbotAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.autoconfigure; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.github.kingbbode.chatbot.core.ChatbotProperties; 5 | import com.github.kingbbode.chatbot.core.base.BaseBrain; 6 | import com.github.kingbbode.chatbot.core.base.knowledge.brain.KnowledgeBrain; 7 | import com.github.kingbbode.chatbot.core.base.knowledge.component.KnowledgeComponent; 8 | import com.github.kingbbode.chatbot.core.base.stat.StatComponent; 9 | import com.github.kingbbode.chatbot.core.brain.DefaultDispatcherBrain; 10 | import com.github.kingbbode.chatbot.core.brain.DispatcherBrain; 11 | import com.github.kingbbode.chatbot.core.brain.DistributedEnvironment; 12 | import com.github.kingbbode.chatbot.core.brain.aop.BrainCellAspect; 13 | import com.github.kingbbode.chatbot.core.brain.factory.BrainFactory; 14 | import com.github.kingbbode.chatbot.core.brain.factory.BrainFactoryCustomizer; 15 | import com.github.kingbbode.chatbot.core.common.interfaces.EventSensor; 16 | import com.github.kingbbode.chatbot.core.common.properties.BotProperties; 17 | import com.github.kingbbode.chatbot.core.conversation.ConversationService; 18 | import com.github.kingbbode.chatbot.core.event.EventQueue; 19 | import com.github.kingbbode.chatbot.core.event.TaskRunner; 20 | import org.springframework.beans.factory.BeanFactory; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.beans.factory.annotation.Qualifier; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 25 | import org.springframework.boot.autoconfigure.data.redis.RedisProperties; 26 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 27 | import org.springframework.context.annotation.Bean; 28 | import org.springframework.context.annotation.Configuration; 29 | import org.springframework.context.annotation.Primary; 30 | import org.springframework.context.annotation.Profile; 31 | import org.springframework.core.env.Environment; 32 | import org.springframework.data.redis.connection.RedisConnectionFactory; 33 | import org.springframework.data.redis.core.RedisTemplate; 34 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 35 | import org.springframework.scheduling.annotation.EnableScheduling; 36 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 37 | import org.springframework.web.client.RestOperations; 38 | import redis.embedded.RedisServer; 39 | 40 | import java.io.IOException; 41 | import java.util.List; 42 | 43 | import static com.github.kingbbode.chatbot.core.common.util.RestTemplateFactory.getRestOperations; 44 | 45 | /** 46 | * Created by YG on 2017-07-10. 47 | */ 48 | @Configuration 49 | @ConditionalOnProperty(prefix = "chatbot", name = "enabled", havingValue = "true", matchIfMissing = true) 50 | @EnableConfigurationProperties({RedisProperties.class, ChatbotProperties.class}) 51 | @EnableScheduling 52 | public class ChatbotAutoConfiguration { 53 | 54 | public static final String EVENT_QUEUE_TREAD_POOL = "eventQueueTreadPool"; 55 | private ChatbotProperties chatbotProperties; 56 | 57 | public ChatbotAutoConfiguration(ChatbotProperties chatbotProperties) { 58 | this.chatbotProperties = chatbotProperties; 59 | } 60 | 61 | @Bean 62 | @ConditionalOnMissingBean 63 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { 64 | RedisTemplate template = new RedisTemplate<>(); 65 | template.setConnectionFactory(redisConnectionFactory); 66 | return template; 67 | } 68 | 69 | @Bean 70 | @ConditionalOnMissingBean 71 | public BrainFactory brainFactory( 72 | BotProperties botProperties, 73 | BeanFactory beanFactory, 74 | @Autowired(required = false) KnowledgeComponent knowledge, 75 | ChatbotProperties chatbotProperties, 76 | List brainFactoryCustomizers 77 | ){ 78 | return new BrainFactory(botProperties, beanFactory, knowledge, chatbotProperties, brainFactoryCustomizers); 79 | } 80 | 81 | @Bean 82 | @ConditionalOnMissingBean 83 | public EventQueue eventQueue(){ 84 | return new EventQueue(); 85 | } 86 | 87 | @Bean(name = "embeddedRedis", destroyMethod = "stop") 88 | @ConditionalOnProperty(name = "chatbot.enableEmbeddedRedis", havingValue = "true", matchIfMissing = true) 89 | public RedisServer redisServer(RedisProperties redisProperties) throws IOException { 90 | RedisServer redisServer = new RedisServer(redisProperties.getPort()); 91 | redisServer.start(); 92 | return redisServer; 93 | } 94 | 95 | @Bean 96 | @ConditionalOnMissingBean 97 | public ConversationService conversationService( 98 | Environment environment, 99 | RedisTemplate redisTemplate, 100 | ObjectMapper objectMapper, 101 | BotProperties botProperties 102 | ){ 103 | return new ConversationService(environment, redisTemplate, objectMapper, botProperties); 104 | } 105 | 106 | @Bean 107 | @Primary 108 | public RestOperations messageRestOperations() { 109 | HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); 110 | factory.setConnectTimeout(1000); 111 | factory.setReadTimeout(1000); 112 | return getRestOperations(factory); 113 | } 114 | 115 | @Bean(name = EVENT_QUEUE_TREAD_POOL) 116 | public ThreadPoolTaskExecutor eventQueueTreadPool() { 117 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 118 | executor.setCorePoolSize(5); 119 | executor.setMaxPoolSize(10); 120 | executor.setQueueCapacity(1000); 121 | executor.setWaitForTasksToCompleteOnShutdown(true); 122 | return executor; 123 | } 124 | 125 | @Bean 126 | @ConditionalOnMissingBean 127 | public DistributedEnvironment distributedEnvironment( 128 | BotProperties botProperties, 129 | RedisTemplate redisTemplate 130 | ) { 131 | return new DistributedEnvironment(botProperties, redisTemplate); 132 | } 133 | 134 | @Bean 135 | @ConditionalOnMissingBean 136 | public TaskRunner taskRunner( 137 | @Qualifier(EVENT_QUEUE_TREAD_POOL) ThreadPoolTaskExecutor executer, 138 | EventQueue eventQueue, 139 | List eventSensors 140 | ){ 141 | return new TaskRunner(executer, eventQueue, eventSensors); 142 | } 143 | 144 | @Bean 145 | @ConditionalOnMissingBean 146 | @ConditionalOnProperty(prefix = "chatbot", name = "enableBase", havingValue = "true", matchIfMissing = true) 147 | public BaseBrain baseBrain( 148 | BrainFactory brainFactory, 149 | @Autowired(required = false) KnowledgeComponent knowledgeComponent, 150 | StatComponent statComponent 151 | ){ 152 | return new BaseBrain(brainFactory, knowledgeComponent, statComponent); 153 | } 154 | 155 | @Bean 156 | @ConditionalOnMissingBean 157 | @ConditionalOnProperty(prefix = "chatbot", name = "enableKnowledge", havingValue = "true") 158 | public KnowledgeBrain knowledgeBrain(KnowledgeComponent knowledgeComponent){ 159 | return new KnowledgeBrain(knowledgeComponent); 160 | } 161 | 162 | @Bean 163 | @ConditionalOnMissingBean 164 | @ConditionalOnProperty(prefix = "chatbot", name = "enableKnowledge", havingValue = "true") 165 | public KnowledgeComponent knowledgeComponent( 166 | RedisTemplate redisTemplate, 167 | ObjectMapper objectMapper, 168 | BotProperties botProperties, 169 | StatComponent statComponent 170 | ){ 171 | return new KnowledgeComponent(redisTemplate, objectMapper, botProperties, statComponent); 172 | } 173 | 174 | @Bean 175 | @ConditionalOnMissingBean 176 | public StatComponent statComponent(){ 177 | return new StatComponent(); 178 | } 179 | 180 | @Bean 181 | @Profile({"bot"}) 182 | public BotProperties realBotConfig(){ 183 | return new BotProperties(chatbotProperties.getName(), chatbotProperties.getCommandPrefix(), false); 184 | } 185 | 186 | @Bean 187 | @Profile("!bot") 188 | public BotProperties devBotConfig(){ 189 | return new BotProperties(chatbotProperties.getName(),"#" + chatbotProperties.getCommandPrefix(), true); 190 | } 191 | 192 | @Bean 193 | @ConditionalOnMissingBean 194 | public DispatcherBrain dispatcherBrain( 195 | BrainFactory brainFactory, 196 | ConversationService conversationService, 197 | DistributedEnvironment distributedEnvironment 198 | ){ 199 | return new DefaultDispatcherBrain(brainFactory, conversationService, distributedEnvironment); 200 | } 201 | 202 | @Bean 203 | @ConditionalOnMissingBean 204 | public BrainCellAspect brainCellAspect( 205 | ConversationService conversationService, 206 | BrainFactory brainFactory, 207 | StatComponent statComponent 208 | ){ 209 | return new BrainCellAspect(conversationService, brainFactory, statComponent); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /chatbot-spring-boot-autoconfigure/src/main/java/com/github/kingbbode/chatbot/autoconfigure/messenger/line/LineAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.autoconfigure.messenger.line; 2 | 3 | import com.github.kingbbode.chatbot.core.brain.DispatcherBrain; 4 | import com.github.kingbbode.messenger.line.LineDispatcher; 5 | import com.github.kingbbode.messenger.line.LineEventSensor; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | @ConditionalOnClass(name = "com.github.kingbbode.messenger.line.LineDispatcher") 13 | public class LineAutoConfiguration { 14 | @Bean 15 | @ConditionalOnMissingBean 16 | public LineDispatcher lineDispatcher(DispatcherBrain dispatcherBrain) { 17 | return new LineDispatcher(dispatcherBrain); 18 | } 19 | 20 | @Bean 21 | @ConditionalOnMissingBean 22 | public LineEventSensor lineEventSensor(LineDispatcher lineDispatcher) { 23 | return new LineEventSensor(lineDispatcher); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chatbot-spring-boot-autoconfigure/src/main/java/com/github/kingbbode/chatbot/autoconfigure/messenger/slack/SlackAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.autoconfigure.messenger.slack; 2 | 3 | import com.github.kingbbode.chatbot.core.brain.DispatcherBrain; 4 | import com.github.kingbbode.chatbot.core.event.EventQueue; 5 | import com.github.kingbbode.messenger.slack.SlackBotClient; 6 | import com.github.kingbbode.messenger.slack.SlackDispatcher; 7 | import com.github.kingbbode.messenger.slack.SlackEventSensor; 8 | import com.github.kingbbode.messenger.slack.event.BlockActionDispatcherBrain; 9 | import com.slack.api.bolt.App; 10 | import com.slack.api.bolt.AppConfig; 11 | import com.slack.api.bolt.socket_mode.SocketModeApp; 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 15 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | 19 | import javax.websocket.DeploymentException; 20 | import java.io.IOException; 21 | import java.util.List; 22 | 23 | @Configuration 24 | @EnableConfigurationProperties(SlackProperties.class) 25 | @ConditionalOnClass(name = "com.github.kingbbode.messenger.slack.SlackDispatcher") 26 | @ConditionalOnProperty(prefix = "slack", value = {"app-token", "bot-token"}) 27 | public class SlackAutoConfiguration { 28 | 29 | @Bean 30 | @ConditionalOnMissingBean 31 | public App slackApp(SlackProperties slackProperties) { 32 | return new App(AppConfig.builder() 33 | .singleTeamBotToken(slackProperties.getBotToken()) 34 | .build() 35 | ); 36 | } 37 | 38 | @Bean 39 | @ConditionalOnMissingBean 40 | public SocketModeApp slackSocketModeApp(App app, SlackProperties slackProperties) throws IOException { 41 | return new SocketModeApp(slackProperties.getAppToken(), app); 42 | } 43 | 44 | @Bean 45 | @ConditionalOnMissingBean 46 | public SlackBotClient slackBotClient(App app, SlackProperties slackProperties) throws IOException, DeploymentException { 47 | return new SlackBotClient(app.slack().methods(slackProperties.getBotToken())); 48 | } 49 | 50 | @Bean 51 | @ConditionalOnMissingBean 52 | public SlackDispatcher slackDispatcher(SlackBotClient slackRTMClient) { 53 | return new SlackDispatcher(slackRTMClient); 54 | } 55 | 56 | @Bean 57 | @ConditionalOnMissingBean 58 | public SlackEventSensor slackEventSensor( 59 | App app, 60 | List blockActionConnectors, 61 | SlackDispatcher slackDispatcher, 62 | EventQueue eventQueue, 63 | DispatcherBrain dispatcherBrain 64 | ) { 65 | return new SlackEventSensor(app, blockActionConnectors, slackDispatcher, eventQueue, dispatcherBrain); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /chatbot-spring-boot-autoconfigure/src/main/java/com/github/kingbbode/chatbot/autoconfigure/messenger/slack/SlackProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.autoconfigure.messenger.slack; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | 7 | /** 8 | * Created by YG-MAC on 2018. 3. 4.. 9 | */ 10 | @Getter 11 | @Setter 12 | @ConfigurationProperties(prefix = "slack") 13 | public class SlackProperties { 14 | private String appToken; 15 | private String botToken; 16 | } 17 | -------------------------------------------------------------------------------- /chatbot-spring-boot-autoconfigure/src/main/java/com/github/kingbbode/chatbot/autoconfigure/messenger/telegram/TelegramAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.autoconfigure.messenger.telegram; 2 | 3 | import com.github.kingbbode.chatbot.core.brain.DispatcherBrain; 4 | import com.github.kingbbode.chatbot.core.event.EventQueue; 5 | import com.github.kingbbode.messenger.telegram.TelegramBotsApiWrapper; 6 | import com.github.kingbbode.messenger.telegram.TelegramEventSensor; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.telegram.telegrambots.ApiContextInitializer; 14 | import org.telegram.telegrambots.TelegramBotsApi; 15 | import org.telegram.telegrambots.exceptions.TelegramApiRequestException; 16 | 17 | /** 18 | * Created by YG-MAC on 2018. 3. 4.. 19 | */ 20 | @Configuration 21 | @EnableConfigurationProperties(TelegramProperties.class) 22 | @ConditionalOnClass(name = "com.github.kingbbode.messenger.telegram.TelegramEventSensor") 23 | @ConditionalOnProperty(prefix = "telegram", value = "token") 24 | public class TelegramAutoConfiguration { 25 | 26 | public TelegramAutoConfiguration() { 27 | ApiContextInitializer.init(); 28 | } 29 | 30 | @Bean 31 | @ConditionalOnMissingBean 32 | public TelegramEventSensor telegramEventSensor( 33 | EventQueue eventQueue, 34 | DispatcherBrain dispatcherBrain, 35 | TelegramProperties telegramProperties 36 | ) { 37 | return new TelegramEventSensor(eventQueue, dispatcherBrain, telegramProperties.getName(), telegramProperties.getToken()); 38 | } 39 | 40 | @Bean 41 | @ConditionalOnMissingBean(TelegramBotsApi.class) 42 | public TelegramBotsApi telegramBotsApi() throws TelegramApiRequestException { 43 | return new TelegramBotsApiWrapper(telegramEventSensor(null, null, null)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chatbot-spring-boot-autoconfigure/src/main/java/com/github/kingbbode/chatbot/autoconfigure/messenger/telegram/TelegramProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.autoconfigure.messenger.telegram; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | 7 | /** 8 | * Created by YG-MAC on 2018. 3. 4.. 9 | */ 10 | @Getter 11 | @Setter 12 | @ConfigurationProperties(prefix = "telegram") 13 | public class TelegramProperties { 14 | private String name = "임시봇"; 15 | private String token; 16 | } 17 | -------------------------------------------------------------------------------- /chatbot-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | # Auto Configure 2 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 3 | com.github.kingbbode.chatbot.autoconfigure.ChatbotAutoConfiguration,\ 4 | com.github.kingbbode.chatbot.autoconfigure.messenger.slack.SlackAutoConfiguration,\ 5 | com.github.kingbbode.chatbot.autoconfigure.messenger.telegram.TelegramAutoConfiguration,\ 6 | com.github.kingbbode.chatbot.autoconfigure.messenger.line.LineAutoConfiguration 7 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | compile("org.springframework.boot:spring-boot-starter-web") 6 | compile("org.springframework.boot:spring-boot-starter-aop") 7 | compile("org.springframework.boot:spring-boot-starter-data-redis") 8 | compile("org.apache.httpcomponents:httpclient") 9 | compile("it.ozimov:embedded-redis:0.7.2") 10 | compile("org.reflections:reflections:0.9.11") 11 | compile("org.apache.commons:commons-lang3:3.1") 12 | compile("joda-time:joda-time:2.9.3") 13 | compile("org.projectlombok:lombok") 14 | compile("net.javacrumbs.shedlock:shedlock-spring:4.19.1") 15 | compile("net.javacrumbs.shedlock:shedlock-provider-jdbc-template:4.19.1") 16 | testCompile("org.springframework.boot:spring-boot-starter-test") 17 | } 18 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/ChatbotProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | 7 | /** 8 | * Created by YG on 2017-07-10. 9 | */ 10 | @Getter 11 | @Setter 12 | @ConfigurationProperties(prefix = "chatbot") 13 | public class ChatbotProperties { 14 | private String name = "default"; 15 | private String basePackage = ""; 16 | private boolean enableBase = true; 17 | private boolean enableKnowledge = false; 18 | private String commandPrefix = "#"; 19 | } 20 | 21 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/base/BaseBrain.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.base; 2 | 3 | import com.github.kingbbode.chatbot.core.base.knowledge.component.KnowledgeComponent; 4 | import com.github.kingbbode.chatbot.core.base.stat.StatComponent; 5 | import com.github.kingbbode.chatbot.core.brain.cell.CommonBrainCell; 6 | import com.github.kingbbode.chatbot.core.brain.factory.BrainFactory; 7 | import com.github.kingbbode.chatbot.core.common.annotations.Brain; 8 | import com.github.kingbbode.chatbot.core.common.annotations.BrainCell; 9 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 10 | import com.github.kingbbode.chatbot.core.common.util.BrainUtil; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.util.ObjectUtils; 13 | 14 | /** 15 | * Created by YG-MAC on 2017. 1. 27.. 16 | */ 17 | @Brain 18 | @RequiredArgsConstructor 19 | public class BaseBrain { 20 | 21 | private final BrainFactory brainFactory; 22 | private final KnowledgeComponent knowledgeComponent; 23 | private final StatComponent statComponent; 24 | 25 | @BrainCell(key = "기능", explain = "기능 목록 출력", function = "1") 26 | public String explain(BrainRequest brainRequest) { 27 | String explain = "**** 기능 목록 **** \n" + 28 | BrainUtil.explainDetail(brainFactory.findBrainCellByType(CommonBrainCell.class)); 29 | 30 | if(!ObjectUtils.isEmpty(this.knowledgeComponent)) { 31 | explain += "\n**** 학습 목록 **** \n" + BrainUtil.explainForKnowledge(knowledgeComponent.getCommands()); 32 | } 33 | return explain; 34 | } 35 | 36 | @BrainCell(key = "고유정보", explain = "고유 정보 추출",function = "2") 37 | public String info(BrainRequest brainRequest){ 38 | return brainRequest.toString(); 39 | } 40 | 41 | @BrainCell(key = "기능통계", explain = "뭘 많이 쓰는지..", function = "3") 42 | public String stat(BrainRequest brainRequest) { 43 | return statComponent.toString(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/base/knowledge/brain/KnowledgeBrain.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.base.knowledge.brain; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.github.kingbbode.chatbot.core.base.knowledge.component.KnowledgeComponent; 5 | import com.github.kingbbode.chatbot.core.common.annotations.Brain; 6 | import com.github.kingbbode.chatbot.core.common.annotations.BrainCell; 7 | import com.github.kingbbode.chatbot.core.common.exception.ArgumentInvalidException; 8 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 9 | import lombok.RequiredArgsConstructor; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | /** 13 | * Created by YG-MAC on 2017. 1. 26.. 14 | */ 15 | @Slf4j 16 | @Brain 17 | @RequiredArgsConstructor 18 | public class KnowledgeBrain { 19 | 20 | private final KnowledgeComponent knowledgeComponent; 21 | 22 | @BrainCell(key = "학습", explain = "명령어 학습시키기", function = "addKnowledge") 23 | public String addKnowledge(BrainRequest brainRequest) throws JsonProcessingException { 24 | return "학습시킬 명령어가 무엇인가요?"; 25 | } 26 | 27 | @BrainCell(parent = "addKnowledge", function = "addKnowledge2") 28 | public String addKnowledge2(BrainRequest brainRequest) { 29 | brainRequest.getConversation().put("key", brainRequest.getContent()); 30 | return "이 명렁어에 기억할 내용은 무엇인가요?"; 31 | } 32 | 33 | @BrainCell(parent = "addKnowledge2", function = "addKnowledge3", example = "입력하신 내용에 오류가 있습니다. 다시 입력해주세요.") 34 | public String addKnowledge3(BrainRequest brainRequest) { 35 | log.info("addKnowledge user :{}, key : {}, content : {}", brainRequest.getUser(), brainRequest.getConversation().getParam().get("key"), brainRequest.getContent()); 36 | try { 37 | return knowledgeComponent.addKnowledge(brainRequest.getConversation().getParam().get("key"), brainRequest.getContent()); 38 | } catch (JsonProcessingException e) { 39 | throw new ArgumentInvalidException(e); 40 | } 41 | } 42 | 43 | @BrainCell(key = "까묵", explain = "학습 시킨 명령어 지우기", example = "까묵 명령어", function = "forgetKnowledge") 44 | public String forgetKnowledge(BrainRequest brainRequest) { 45 | return "무엇을 까먹을까요?"; 46 | } 47 | 48 | @BrainCell(parent = "forgetKnowledge", function = "forgetKnowledge2") 49 | public String forgetKnowledge2(BrainRequest brainRequest) { 50 | return knowledgeComponent.forgetKnowledge(brainRequest.getContent()); 51 | } 52 | } -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/base/knowledge/component/KnowledgeComponent.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.base.knowledge.component; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.core.type.TypeReference; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.github.kingbbode.chatbot.core.base.stat.StatComponent; 7 | import com.github.kingbbode.chatbot.core.common.properties.BotProperties; 8 | import lombok.RequiredArgsConstructor; 9 | import org.springframework.data.redis.core.HashOperations; 10 | import org.springframework.data.redis.core.RedisTemplate; 11 | 12 | import javax.annotation.PostConstruct; 13 | import java.io.IOException; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Set; 18 | import java.util.concurrent.ConcurrentHashMap; 19 | 20 | /** 21 | * Created by YG on 2016-04-12. 22 | */ 23 | @RequiredArgsConstructor 24 | public class KnowledgeComponent { 25 | 26 | private final RedisTemplate redisTemplate; 27 | private final ObjectMapper objectMapper; 28 | private final BotProperties botProperties; 29 | private final StatComponent statComponent; 30 | 31 | private static final String REDIS_KEY_KNOWLEDGE = ":knowledge"; 32 | 33 | private String key; 34 | private Map> knowledge; 35 | 36 | @PostConstruct 37 | public void init() throws IOException { 38 | this.key = botProperties.getName() + REDIS_KEY_KNOWLEDGE; 39 | Map> map = new ConcurrentHashMap<>(); 40 | HashOperations hashOperations = redisTemplate.opsForHash(); 41 | Map entries = hashOperations.entries(this.key); 42 | for(Map.Entry entry : entries.entrySet()){ 43 | map.put(entry.getKey(), objectMapper.readValue(entry.getValue(), new TypeReference>() { 44 | })); 45 | } 46 | knowledge = map; 47 | } 48 | 49 | public void clear() { 50 | knowledge.clear(); 51 | } 52 | 53 | public boolean contains(String command){ 54 | return knowledge.containsKey(command); 55 | } 56 | 57 | public List get(String command) { 58 | List candi = knowledge.getOrDefault(command, null); 59 | if(candi != null){ 60 | statComponent.plus("학습:" + command); 61 | } 62 | return candi; 63 | } 64 | 65 | public String addKnowledge(String command, String content) throws JsonProcessingException { 66 | if (content.startsWith(botProperties.getCommandPrefix())) { 67 | return botProperties.getCommandPrefix() + "로 시작하는 내용은 암기할 수 없습니다"; 68 | } 69 | 70 | if (knowledge.containsKey(command)) { 71 | List list = knowledge.get(command); 72 | if (list.size() > 9) { 73 | return command + " 명령어에 습득할 수 있는 머리가 꽉 차서 못하겠습니다"; 74 | } 75 | list.add(content); 76 | redisTemplate.opsForHash().put(this.key, command, objectMapper.writeValueAsString(list)); 77 | return command + " 명령어에 지식을 하나 더 습득했습니다"; 78 | } else { 79 | if (knowledge.size() > 10000) { 80 | return "제 머리로는 더 이상 지식을 습득할 수 없습니다"; 81 | } 82 | List list = new ArrayList<>(); 83 | list.add(content); 84 | knowledge.put(command, list); 85 | redisTemplate.opsForHash().put(this.key, command, objectMapper.writeValueAsString(list)); 86 | return "새로운 지식을 습득했습니다. \n 사용법 : " + command; 87 | } 88 | } 89 | 90 | public String forgetKnowledge(String command) { 91 | if (knowledge.containsKey(command)) { 92 | knowledge.remove(command); 93 | redisTemplate.opsForHash().delete(this.key, command); 94 | return command + "를 까먹었습니다"; 95 | } else { 96 | return "그런건 원래 모릅니다"; 97 | } 98 | } 99 | 100 | public Set>> getCommands(){ 101 | return knowledge.entrySet(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/base/stat/StatComponent.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.base.stat; 2 | 3 | import javax.annotation.PostConstruct; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * Created by YG on 2017-06-02. 9 | */ 10 | public class StatComponent { 11 | private Map statMap; 12 | 13 | @PostConstruct 14 | public void init(){ 15 | statMap = new HashMap<>(); 16 | } 17 | 18 | public void plus(String key) { 19 | int current = 0; 20 | if(statMap.containsKey(key)){ 21 | current = statMap.get(key); 22 | } 23 | statMap.put(key, current + 1); 24 | } 25 | 26 | public String toString(){ 27 | StringBuilder stringBuilder = new StringBuilder(); 28 | statMap.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach(entry-> stringBuilder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n")); 29 | return stringBuilder.toString(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/brain/DefaultDispatcherBrain.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.brain; 2 | 3 | import com.github.kingbbode.chatbot.core.brain.cell.AbstractBrainCell; 4 | import com.github.kingbbode.chatbot.core.brain.factory.BrainFactory; 5 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 6 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 7 | import com.github.kingbbode.chatbot.core.common.result.DefaultBrainResult; 8 | import com.github.kingbbode.chatbot.core.conversation.Conversation; 9 | import com.github.kingbbode.chatbot.core.conversation.ConversationService; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.io.IOException; 14 | import java.lang.reflect.InvocationTargetException; 15 | 16 | /** 17 | * Created by YG on 2017-01-23. 18 | */ 19 | @Slf4j 20 | @RequiredArgsConstructor 21 | public class DefaultDispatcherBrain implements DispatcherBrain { 22 | private final BrainFactory brainFactory; 23 | private final ConversationService conversationService; 24 | private final DistributedEnvironment distributedEnvironment; 25 | 26 | @Override 27 | public BrainResult execute(BrainRequest brainRequest) { 28 | try { 29 | if(distributedEnvironment.sync(brainRequest)) { 30 | return selectedBrainCell(brainRequest).execute(brainRequest); 31 | } 32 | } catch (Exception e) { 33 | log.warn("execute error -{}", e.getMessage(), e); 34 | } 35 | return DefaultBrainResult.NONE; 36 | } 37 | 38 | private T selectedBrainCell(BrainRequest brainRequest) throws IOException { 39 | Conversation conversation = conversationService.getLatest(brainRequest.getUser()); 40 | if(conversation != null && brainFactory.containsConversationInfo(conversation.getFunction())){ 41 | return conversation(brainRequest, conversation); 42 | } 43 | return brainFactory.get(brainRequest.getContent()); 44 | } 45 | 46 | @SuppressWarnings("unchecked") 47 | private T conversation(BrainRequest brainRequest, Conversation conversation) { 48 | BrainFactory.ConversationInfo info = brainFactory.getConversationInfo(conversation.getFunction()); 49 | String functionKey = info.findFunctionKey(brainRequest.getContent()); 50 | if(functionKey != null && brainFactory.containsFunctionKey(functionKey)){ 51 | brainRequest.setConversation(conversation); 52 | return brainFactory.getByFunctionKey(functionKey); 53 | } 54 | if(brainRequest.getContent().equals("취소")){ 55 | conversationService.delete(brainRequest.getUser()); 56 | return (T) new AbstractBrainCell(){ 57 | @Override 58 | public DefaultBrainResult execute(BrainRequest brainRequest) throws InvocationTargetException, IllegalAccessException { 59 | return DefaultBrainResult.builder() 60 | .message("취소되었습니다.") 61 | .room(brainRequest.getRoom()) 62 | .thread(brainRequest.getThread()) 63 | .build(); 64 | } 65 | }; 66 | } 67 | return (T) new AbstractBrainCell(){ 68 | @Override 69 | public DefaultBrainResult execute(BrainRequest brainRequest) throws InvocationTargetException, IllegalAccessException { 70 | return DefaultBrainResult.builder() 71 | .message(info.example()) 72 | .room(brainRequest.getRoom()) 73 | .thread(brainRequest.getThread()) 74 | .build(); 75 | } 76 | }; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/brain/DispatcherBrain.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.brain; 2 | 3 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 4 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 5 | 6 | public interface DispatcherBrain { 7 | BrainResult execute(BrainRequest brainRequest); 8 | } 9 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/brain/DistributedEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.brain; 2 | 3 | import com.github.kingbbode.chatbot.core.common.properties.BotProperties; 4 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | 8 | import java.time.Duration; 9 | import java.util.Arrays; 10 | 11 | @RequiredArgsConstructor 12 | public class DistributedEnvironment { 13 | public static final String VALUE = "1"; 14 | public static final Duration EXPIRE_TIME = Duration.ofMinutes(1); 15 | private final BotProperties botProperties; 16 | private final RedisTemplate redisTemplate; 17 | 18 | public boolean sync(BrainRequest brainRequest) { 19 | String key = String.join(":", Arrays.asList( 20 | brainRequest.getMessenger(), 21 | brainRequest.getMessageNo() + botProperties.getCommandPrefix() 22 | )); 23 | Boolean result = redisTemplate.opsForValue().setIfAbsent(key, VALUE, EXPIRE_TIME); 24 | return result == null || result; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/brain/aop/BrainCellAspect.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.brain.aop; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.github.kingbbode.chatbot.core.base.stat.StatComponent; 5 | import com.github.kingbbode.chatbot.core.brain.factory.BrainFactory; 6 | import com.github.kingbbode.chatbot.core.common.annotations.BrainCell; 7 | import com.github.kingbbode.chatbot.core.common.exception.InvalidReturnTypeException; 8 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 9 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 10 | import com.github.kingbbode.chatbot.core.common.result.SimpleMessageBrainResult; 11 | import com.github.kingbbode.chatbot.core.conversation.Conversation; 12 | import com.github.kingbbode.chatbot.core.conversation.ConversationService; 13 | import lombok.RequiredArgsConstructor; 14 | import org.aspectj.lang.ProceedingJoinPoint; 15 | import org.aspectj.lang.annotation.Around; 16 | import org.aspectj.lang.annotation.Aspect; 17 | import org.aspectj.lang.reflect.MethodSignature; 18 | import org.springframework.util.ObjectUtils; 19 | 20 | /** 21 | * Created by YG-MAC on 2017. 1. 26.. 22 | */ 23 | @Aspect 24 | @RequiredArgsConstructor 25 | public class BrainCellAspect { 26 | private final ConversationService conversationService; 27 | private final BrainFactory brainFactory; 28 | private final StatComponent statComponent; 29 | 30 | @Around("@annotation(com.github.kingbbode.chatbot.core.common.annotations.BrainCell)") 31 | public Object checkArgLength(ProceedingJoinPoint joinPoint) throws Throwable { 32 | BrainCell brainCell = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(BrainCell.class); 33 | BrainRequest request = (BrainRequest) joinPoint.getArgs()[0]; 34 | if(!ObjectUtils.isEmpty(request.getConversation()) && brainCell.cancelable() && request.getContent().equals("취소")){ 35 | conversationService.delete(request.getUser()); 36 | return "초기화되었습니다."; 37 | } 38 | if(request.getContent().length() == 0){ 39 | return "입력해주세요."; 40 | } 41 | Object object = joinPoint.proceed(); 42 | 43 | if(!(object instanceof String) && !(object instanceof BrainResult) ){ 44 | throw new InvalidReturnTypeException(new Throwable()); 45 | } 46 | 47 | conversation(brainCell, request); 48 | 49 | if("".equals(brainCell.parent())){ 50 | statComponent.plus(brainCell.key()); 51 | } 52 | 53 | return conversationComment(object, brainCell); 54 | } 55 | 56 | private Object conversationComment(Object object, BrainCell brainCell) { 57 | if(!brainFactory.containsConversationInfo(brainCell.function())) { 58 | return object; 59 | } 60 | if(object instanceof String){ 61 | object += "\n\n상태를 취소하려면 '취소'를 입력해주세요.\n30초 동안 아무 작업이 없을 시 자동 초기화됩니다."; 62 | }else if(object instanceof SimpleMessageBrainResult){ 63 | ((SimpleMessageBrainResult) object).comment("\n상태를 취소하려면 '취소'를 입력해주세요.\n30초 동안 아무 작업이 없을 시 자동 초기화됩니다."); 64 | } 65 | return object; 66 | } 67 | 68 | private void conversation(BrainCell brainCell, BrainRequest request) throws JsonProcessingException { 69 | if(!brainFactory.containsConversationInfo(brainCell.function())) { 70 | if(!"".equals(brainCell.parent())){ 71 | conversationService.delete(request.getUser()); 72 | } 73 | return; 74 | } 75 | 76 | Conversation conversation = new Conversation(brainCell.function()); 77 | conversation.put(request.getConversation()); 78 | conversation.put("content", request.getContent()); 79 | conversationService.push(request.getUser(), conversation); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/brain/cell/AbstractBrainCell.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.brain.cell; 2 | 3 | 4 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 5 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 6 | import com.github.kingbbode.chatbot.core.common.result.DefaultBrainResult; 7 | 8 | /** 9 | * Created by YG on 2017-01-26. 10 | */ 11 | 12 | 13 | public abstract class AbstractBrainCell implements BrainCell { 14 | @Override 15 | public String explain() { 16 | return ""; 17 | } 18 | 19 | public static AbstractBrainCell NOT_FOUND_BRAIN_CELL = new AbstractBrainCell() { 20 | @Override 21 | public BrainResult execute(BrainRequest brainRequest) { 22 | return DefaultBrainResult.NONE; 23 | } 24 | 25 | @Override 26 | public String explain() { 27 | return "존재하지 않는 기능입니다."; 28 | } 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/brain/cell/BrainCell.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.brain.cell; 2 | 3 | 4 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 5 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | 9 | /** 10 | * Created by YG on 2017-01-26. 11 | */ 12 | public interface BrainCell { 13 | String explain(); 14 | BrainResult execute(BrainRequest brainRequest) throws InvocationTargetException, IllegalAccessException; 15 | } 16 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/brain/cell/CommonBrainCell.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.brain.cell; 2 | 3 | import com.github.kingbbode.chatbot.core.common.annotations.BrainCell; 4 | import com.github.kingbbode.chatbot.core.common.exception.ArgumentInvalidException; 5 | import com.github.kingbbode.chatbot.core.common.exception.BrainException; 6 | import com.github.kingbbode.chatbot.core.common.exception.InvalidReturnTypeException; 7 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 8 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 9 | import com.github.kingbbode.chatbot.core.common.result.DefaultBrainResult; 10 | import org.apache.commons.lang3.text.WordUtils; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.beans.factory.BeanFactory; 14 | 15 | import java.lang.reflect.Method; 16 | 17 | /** 18 | * Created by YG on 2017-01-23. 19 | */ 20 | public class CommonBrainCell extends AbstractBrainCell { 21 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 22 | 23 | private String name; 24 | private Method active; 25 | private Object object; 26 | private BeanFactory beanFactory; 27 | private BrainCell brain; 28 | 29 | public CommonBrainCell(BrainCell brain, Class clazz, Method active, BeanFactory beanFactory) { 30 | this.name = WordUtils.uncapitalize(clazz.getSimpleName()); 31 | this.brain = brain; 32 | this.active = active; 33 | this.beanFactory = beanFactory; 34 | } 35 | 36 | @Override 37 | public String explain() { 38 | return brain.explain(); 39 | } 40 | 41 | @Override 42 | public BrainResult execute(BrainRequest brainRequest) { 43 | if (!inject()) { 44 | return DefaultBrainResult.Builder.FAILED 45 | .room(brainRequest.getRoom()) 46 | .thread(brainRequest.getThread()) 47 | .build(); 48 | } 49 | Object result; 50 | try { 51 | result = active.invoke(object, brainRequest); 52 | } catch (Throwable e) { 53 | if (e.getCause() instanceof BrainException) { 54 | return DefaultBrainResult.builder() 55 | .message(e.getCause().getMessage()) 56 | .room(brainRequest.getRoom()) 57 | .thread(brainRequest.getThread()) 58 | .build(); 59 | } else if (e.getCause() instanceof ArgumentInvalidException) { 60 | return DefaultBrainResult.builder() 61 | .message(active.getAnnotation(BrainCell.class).example()) 62 | .room(brainRequest.getRoom()) 63 | .thread(brainRequest.getThread()) 64 | .build(); 65 | } else if (e.getCause() instanceof InvalidReturnTypeException) { 66 | return DefaultBrainResult.builder() 67 | .message("Method Return Type Exception!") 68 | .room(brainRequest.getRoom()) 69 | .thread(brainRequest.getThread()) 70 | .build(); 71 | } 72 | return DefaultBrainResult.builder() 73 | .message("Server Error : " + e.getMessage()) 74 | .room(brainRequest.getRoom()) 75 | .thread(brainRequest.getThread()) 76 | .build(); 77 | } 78 | if (result instanceof String) { 79 | return DefaultBrainResult.builder() 80 | .message((String) result) 81 | .room(brainRequest.getRoom()) 82 | .thread(brainRequest.getThread()) 83 | .build(); 84 | } else if (result instanceof BrainResult) { 85 | return (BrainResult) result; 86 | } 87 | 88 | return DefaultBrainResult.builder() 89 | .room(brainRequest.getRoom()) 90 | .thread(brainRequest.getThread()) 91 | .message("Not Support Result Type.") 92 | .build(); 93 | } 94 | 95 | private boolean inject() { 96 | if (object != null) { 97 | return true; 98 | } 99 | if (beanFactory.containsBean(name)) { 100 | object = beanFactory.getBean(name); 101 | } 102 | 103 | return object != null; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/brain/cell/KnowledgeBrainCell.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.brain.cell; 2 | 3 | import com.github.kingbbode.chatbot.core.base.knowledge.component.KnowledgeComponent; 4 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 5 | import com.github.kingbbode.chatbot.core.common.result.DefaultBrainResult; 6 | 7 | import java.util.List; 8 | import java.util.Random; 9 | 10 | /** 11 | * Created by YG on 2017-01-26. 12 | */ 13 | public class KnowledgeBrainCell extends AbstractBrainCell { 14 | private KnowledgeComponent knowledgeComponent; 15 | 16 | public KnowledgeBrainCell(KnowledgeComponent knowledgeComponent) { 17 | this.knowledgeComponent = knowledgeComponent; 18 | } 19 | 20 | @Override 21 | public String explain() { 22 | return "사용자 학습 기능 입니다."; 23 | } 24 | 25 | @Override 26 | public DefaultBrainResult execute(BrainRequest brainRequest) { 27 | List results = knowledgeComponent.get(brainRequest.getContent()); 28 | if(results == null){ 29 | return DefaultBrainResult.NONE; 30 | } 31 | 32 | return DefaultBrainResult.builder() 33 | .message(results.get(new Random().nextInt(results.size()))) 34 | .room(brainRequest.getRoom()) 35 | .thread(brainRequest.getThread()) 36 | .build(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/brain/factory/BrainFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.brain.factory; 2 | 3 | import com.github.kingbbode.chatbot.core.ChatbotProperties; 4 | import com.github.kingbbode.chatbot.core.base.knowledge.component.KnowledgeComponent; 5 | import com.github.kingbbode.chatbot.core.brain.cell.AbstractBrainCell; 6 | import com.github.kingbbode.chatbot.core.brain.cell.CommonBrainCell; 7 | import com.github.kingbbode.chatbot.core.brain.cell.KnowledgeBrainCell; 8 | import com.github.kingbbode.chatbot.core.common.annotations.Brain; 9 | import com.github.kingbbode.chatbot.core.common.annotations.BrainCell; 10 | import com.github.kingbbode.chatbot.core.common.properties.BotProperties; 11 | import com.google.common.collect.Lists; 12 | import lombok.RequiredArgsConstructor; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.reflections.Reflections; 15 | import org.reflections.scanners.SubTypesScanner; 16 | import org.reflections.scanners.TypeAnnotationsScanner; 17 | import org.reflections.util.ClasspathHelper; 18 | import org.reflections.util.ConfigurationBuilder; 19 | import org.springframework.beans.factory.BeanFactory; 20 | import org.springframework.util.ObjectUtils; 21 | 22 | import javax.annotation.PostConstruct; 23 | import java.io.IOException; 24 | import java.lang.reflect.InvocationTargetException; 25 | import java.lang.reflect.Method; 26 | import java.util.*; 27 | import java.util.stream.Collectors; 28 | 29 | import static com.github.kingbbode.chatbot.core.brain.cell.AbstractBrainCell.NOT_FOUND_BRAIN_CELL; 30 | 31 | /** 32 | * Created by YG on 2017-01-23. 33 | */ 34 | @Slf4j 35 | @RequiredArgsConstructor 36 | public class BrainFactory { 37 | 38 | private final BotProperties botProperties; 39 | private final BeanFactory beanFactory; 40 | private final KnowledgeComponent knowledge; 41 | private final ChatbotProperties chatbotProperties; 42 | private final List brainFactoryCustomizers; 43 | 44 | private KnowledgeBrainCell knowledgeBrainCell; 45 | 46 | //#command, functionKey 47 | private Map commandMap; 48 | //functionKey, BrainCell 49 | private Map keyMap; 50 | //functionKey, ConversationInfo 51 | private Map conversationInfoMap; 52 | 53 | private List scanPackages = Lists.newArrayList("com.github.kingbbode.chatbot.core.base"); 54 | 55 | @PostConstruct 56 | public void init() throws InvocationTargetException, IllegalAccessException, IOException { 57 | if(!ObjectUtils.isEmpty(brainFactoryCustomizers)) { 58 | brainFactoryCustomizers.forEach(brainFactoryCustomizer -> scanPackages.addAll(brainFactoryCustomizer.packages())); 59 | } 60 | if(!ObjectUtils.isEmpty(knowledge)) { 61 | knowledgeBrainCell = new KnowledgeBrainCell(knowledge); 62 | } 63 | this.load(); 64 | } 65 | 66 | @SuppressWarnings("unchecked") 67 | private void load() throws InvocationTargetException, IllegalAccessException, IOException { 68 | Set keyChecker = new HashSet<>(); 69 | Map command = new HashMap<>(); 70 | Map> childCommand = new HashMap<>(); 71 | Map key = new HashMap<>(); 72 | Map conversationInfo = new HashMap<>(); 73 | registerDefault(keyChecker, command, childCommand, key); 74 | Reflections reflections = findPackage(chatbotProperties.getBasePackage()); 75 | reflections.getTypesAnnotatedWith(Brain.class).stream() 76 | .filter(clazz -> !"BaseBrain".equals(clazz.getSimpleName())) 77 | .filter(clazz -> !"KnowledgeBrain".equals(clazz.getSimpleName())) 78 | .forEach(clazz -> registerCommonBrainCellByClass(keyChecker, command, childCommand, key, clazz)); 79 | 80 | childCommand.forEach((key2, value2) -> { 81 | ConversationInfo info = new ConversationInfo(); 82 | value2.forEach((key1, value1) -> { 83 | if (key1.equals(key2 + "query")) { 84 | info.setQuery(value1); 85 | } else { 86 | info.add(key1, value1); 87 | } 88 | }); 89 | conversationInfo.put(key2, info); 90 | }); 91 | log.info("Load Brain command : {}, key : {}, conversation : {}", command.size(), key.size(), conversationInfo.size()); 92 | this.commandMap = command; 93 | this.keyMap = key; 94 | this.conversationInfoMap = conversationInfo; 95 | } 96 | 97 | private void registerDefault(Set keyChecker, Map command, Map> childCommand, Map key) { 98 | scanPackages.forEach(packageName -> findPackage(packageName).getTypesAnnotatedWith(Brain.class) 99 | .stream() 100 | .filter(clazz -> !("KnowledgeBrain".equals(clazz.getSimpleName()) && !chatbotProperties.isEnableKnowledge())) 101 | .filter(clazz -> !("BaseBrain".equals(clazz.getSimpleName()) && !chatbotProperties.isEnableBase())) 102 | .forEach(clazz -> registerCommonBrainCellByClass(keyChecker, command, childCommand, key, clazz))); 103 | } 104 | 105 | private Reflections findPackage(String packageName) { 106 | return new Reflections(new ConfigurationBuilder() 107 | .setUrls(ClasspathHelper.forPackage(packageName)).setScanners( 108 | new TypeAnnotationsScanner(), new SubTypesScanner())); 109 | } 110 | 111 | private void registerCommonBrainCellByClass(Set keyChecker, Map command, Map> childCommand, Map key, Class clazz) { 112 | for (Method method : clazz.getDeclaredMethods()) { 113 | if (method.isAnnotationPresent(BrainCell.class)) { 114 | BrainCell brainCell = method.getAnnotation(BrainCell.class); 115 | if("".equals(brainCell.parent())) { 116 | command.put(botProperties.getCommandPrefix() + brainCell.key(), brainCell.function()); 117 | }else{ 118 | if(!childCommand.containsKey(brainCell.parent())){ 119 | childCommand.put(brainCell.parent(), new HashMap<>()); 120 | } 121 | childCommand.get(brainCell.parent()).put(brainCell.key().equals("query")?brainCell.parent() + brainCell.key():brainCell.key(), brainCell.function()); 122 | } 123 | key.put(brainCell.function(), new CommonBrainCell(brainCell, clazz, method, beanFactory)); 124 | String chkKey = brainCell.parent() + brainCell.key(); 125 | if(keyChecker.contains(chkKey)){ 126 | throw new RuntimeException("중복된 Key 값이 존재합니다." + chkKey); 127 | } 128 | keyChecker.add(chkKey); 129 | } 130 | } 131 | } 132 | 133 | @SuppressWarnings("unchecked") 134 | public T get(String command){ 135 | if(commandMap.containsKey(command)){ 136 | return (T) keyMap.get(commandMap.get(command)); 137 | } 138 | return (T)( 139 | chatbotProperties.isEnableKnowledge()? 140 | knowledgeBrainCell: 141 | NOT_FOUND_BRAIN_CELL); 142 | } 143 | 144 | public boolean containsFunctionKey(String function){ 145 | return keyMap.containsKey(function); 146 | } 147 | 148 | @SuppressWarnings("unchecked") 149 | public T getByFunctionKey(String function){ 150 | return (T) keyMap.get(function); 151 | } 152 | 153 | public boolean containsConversationInfo(String function){ 154 | return conversationInfoMap.containsKey(function); 155 | } 156 | 157 | public ConversationInfo getConversationInfo(String function){ 158 | return conversationInfoMap.get(function); 159 | } 160 | 161 | @SuppressWarnings("unchecked") 162 | public Set> findBrainCellByType(Class type){ 163 | return commandMap.entrySet().stream() 164 | .filter(entry -> keyMap.containsKey(entry.getValue())) 165 | .filter(entry -> keyMap.get(entry.getValue()).getClass().equals(type)) 166 | .map(entry -> new BrainCellInfo<>(entry.getKey(), (T) keyMap.get(entry.getValue()))) 167 | .collect(Collectors.toSet()); 168 | } 169 | 170 | public static class BrainCellInfo { 171 | private String command; 172 | private T brainCell; 173 | 174 | BrainCellInfo(String command, T brainCell) { 175 | this.command = command; 176 | this.brainCell = brainCell; 177 | } 178 | 179 | @Override 180 | public String toString() { 181 | return command + 182 | " : " + 183 | brainCell.explain(); 184 | } 185 | } 186 | 187 | public static class ConversationInfo { 188 | private Map afters = new HashMap<>(); 189 | private String query; 190 | 191 | void setQuery(String query) { 192 | this.query = query; 193 | } 194 | 195 | void add(String key, String value){ 196 | afters.put(key, value); 197 | } 198 | 199 | 200 | public String findFunctionKey(String command){ 201 | if(afters.containsKey(command)){ 202 | return afters.get(command); 203 | } 204 | return query; 205 | } 206 | 207 | public String example() { 208 | StringBuilder builder = new StringBuilder(); 209 | this.afters.keySet().forEach(key -> { 210 | builder.append("'"); 211 | builder.append(key); 212 | builder.append("'"); 213 | builder.append(" "); 214 | }); 215 | 216 | return builder.toString() + "중에 입력해주세요."; 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/brain/factory/BrainFactoryCustomizer.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.brain.factory; 2 | 3 | import java.util.List; 4 | 5 | public interface BrainFactoryCustomizer { 6 | List packages(); 7 | } 8 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/annotations/Brain.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.annotations; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.TYPE) 12 | @Component 13 | public @interface Brain { 14 | } 15 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/annotations/BrainCell.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.annotations; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Bot 두뇌의 지식을 지정하는 Annotation 11 | * @author YG 12 | * @key 명령어 Key 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(ElementType.METHOD) 16 | public @interface BrainCell { 17 | String parent() default ""; 18 | String key() default "query"; 19 | String explain() default ""; 20 | String example() default ""; 21 | String function(); 22 | boolean cancelable() default true; 23 | } 24 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/enums/GrantType.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.enums; 2 | 3 | /** 4 | * Created by YG on 2016-10-14. 5 | */ 6 | public enum GrantType { 7 | PASSWORD("password"), 8 | REFRESH("refresh_token"); 9 | 10 | String key; 11 | 12 | GrantType(String key) { 13 | this.key = key; 14 | } 15 | 16 | public String getKey() { 17 | return key; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/exception/ArgumentInvalidException.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.exception; 2 | 3 | import java.lang.reflect.UndeclaredThrowableException; 4 | 5 | /** 6 | * Created by YG on 2017-02-08. 7 | */ 8 | public class ArgumentInvalidException extends UndeclaredThrowableException { 9 | 10 | 11 | 12 | public ArgumentInvalidException(Throwable undeclaredThrowable) { 13 | super(undeclaredThrowable); 14 | } 15 | 16 | public ArgumentInvalidException(Throwable undeclaredThrowable, String s) { 17 | super(undeclaredThrowable, s); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/exception/BrainException.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.exception; 2 | 3 | import java.lang.reflect.UndeclaredThrowableException; 4 | 5 | /** 6 | * Created by YG on 2017-02-08. 7 | */ 8 | public class BrainException extends UndeclaredThrowableException { 9 | 10 | 11 | 12 | public BrainException(Throwable undeclaredThrowable) { 13 | super(undeclaredThrowable); 14 | } 15 | 16 | public BrainException(Throwable undeclaredThrowable, String s) { 17 | super(undeclaredThrowable, s); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/exception/InvalidReturnTypeException.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.exception; 2 | 3 | import java.lang.reflect.UndeclaredThrowableException; 4 | 5 | /** 6 | * Created by YG on 2017-02-08. 7 | */ 8 | public class InvalidReturnTypeException extends UndeclaredThrowableException { 9 | 10 | public InvalidReturnTypeException(Throwable undeclaredThrowable) { 11 | super(undeclaredThrowable); 12 | } 13 | 14 | public InvalidReturnTypeException(Throwable undeclaredThrowable, String s) { 15 | super(undeclaredThrowable, s); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/interfaces/Dispatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.interfaces; 2 | 3 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 4 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 5 | import com.github.kingbbode.chatbot.core.event.EmptyResultException; 6 | 7 | /** 8 | * Created by YG on 2017-07-10. 9 | */ 10 | public interface Dispatcher { 11 | BrainRequest dispatch(T event); 12 | void onMessage(BrainRequest request, BrainResult result); 13 | default BrainRequest skip() { 14 | throw new EmptyResultException(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/interfaces/EventSensor.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.interfaces; 2 | 3 | import com.github.kingbbode.chatbot.core.event.Event; 4 | 5 | import java.util.List; 6 | 7 | public interface EventSensor { 8 | List sensingEvent(); 9 | } 10 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/properties/BotProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.properties; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | /** 7 | * Created by YG on 2016-10-13. 8 | */ 9 | @Getter 10 | @Setter 11 | public class BotProperties { 12 | private String name; 13 | private String commandPrefix; 14 | private boolean testMode; 15 | 16 | public BotProperties(String name, String commandPrefix, boolean testMode) { 17 | this.name = name; 18 | this.commandPrefix = commandPrefix; 19 | this.testMode = testMode; 20 | } 21 | 22 | public boolean isTestMode() { 23 | return testMode; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/request/BrainRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.request; 2 | 3 | 4 | import com.github.kingbbode.chatbot.core.conversation.Conversation; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | /** 10 | * Created by YG on 2017-01-23. 11 | */ 12 | @Getter 13 | @Setter 14 | public class BrainRequest { 15 | private String messenger; 16 | private String messageNo; 17 | private String user; 18 | private String room; 19 | private String thread; 20 | private String content; 21 | private Conversation conversation; 22 | 23 | @Builder 24 | public BrainRequest(String messenger, String messageNo, String user, String room, String thread, String content) { 25 | this.messenger = messenger; 26 | this.messageNo = messageNo; 27 | this.user = user; 28 | this.room = room; 29 | this.thread = thread; 30 | this.content = content; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "messenger=" + messenger + "\n" + 36 | "messageNo=" + messageNo + "\n" + 37 | "user=" + user + "\n" + 38 | "room=" + room + "\n" + 39 | "thread=" + thread + "\n" + 40 | "content=" + content + "\n"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/result/BrainResult.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.result; 2 | 3 | public interface BrainResult { 4 | String getRoom(); 5 | String getThread(); 6 | } 7 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/result/DefaultBrainResult.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.result; 2 | 3 | /** 4 | * Created by YG on 2017-01-26. 5 | */ 6 | public class DefaultBrainResult extends SimpleMessageBrainResult { 7 | public static DefaultBrainResult NONE = builder().build(); 8 | 9 | private final String room; 10 | private final String thread; 11 | 12 | public DefaultBrainResult(Builder builder) { 13 | this.message = builder.message; 14 | this.room = builder.room; 15 | this.thread = builder.thread; 16 | } 17 | 18 | @Override 19 | public String getRoom() { 20 | return room; 21 | } 22 | 23 | @Override 24 | public String getThread() { 25 | return thread; 26 | } 27 | 28 | public static Builder builder() { 29 | return new Builder(); 30 | } 31 | 32 | public static class Builder { 33 | private String message; 34 | private String room; 35 | private String thread; 36 | 37 | public Builder message(String message){ 38 | this.message = message; 39 | return this; 40 | } 41 | 42 | public Builder room(String room){ 43 | this.room = room; 44 | return this; 45 | } 46 | 47 | public Builder thread(String thread){ 48 | this.thread = thread; 49 | return this; 50 | } 51 | 52 | public DefaultBrainResult build() { 53 | if(this.message == null){ 54 | return NONE; 55 | } 56 | return new DefaultBrainResult(this); 57 | } 58 | public static Builder FAILED = new Builder() 59 | .message("해당 기능은 장애 상태 입니다"); 60 | public static Builder GREETING = new Builder() 61 | .message("안녕하세요.\n '기능'을 참고하세요"); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/result/SimpleMessageBrainResult.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.result; 2 | 3 | public abstract class SimpleMessageBrainResult implements BrainResult { 4 | protected String message; 5 | 6 | public String getMessage() { 7 | return message; 8 | } 9 | 10 | public void comment(String comment) { 11 | message += comment; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/util/BrainUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.util; 2 | 3 | import com.github.kingbbode.chatbot.core.brain.cell.AbstractBrainCell; 4 | import com.github.kingbbode.chatbot.core.brain.factory.BrainFactory; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | /** 11 | * Created by YG on 2016-04-12. 12 | */ 13 | public class BrainUtil { 14 | 15 | public static String explainDetail(Set> entrySet) { 16 | StringBuilder stringBuilder = new StringBuilder(); 17 | entrySet 18 | .forEach(info -> { 19 | stringBuilder.append(info.toString()); 20 | stringBuilder.append("\n"); 21 | } 22 | ); 23 | return stringBuilder.toString(); 24 | } 25 | 26 | public static String explainForKnowledge(Set>> entrySet) { 27 | StringBuilder stringBuilder = new StringBuilder(); 28 | entrySet 29 | .forEach(entry -> { 30 | stringBuilder.append(entry.getKey()); 31 | stringBuilder.append(" - 지식깊이 : "); 32 | stringBuilder.append(entry.getValue().size()); 33 | stringBuilder.append("\n"); 34 | }); 35 | 36 | return stringBuilder.toString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/common/util/RestTemplateFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.common.util; 2 | 3 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 4 | import org.springframework.http.converter.ByteArrayHttpMessageConverter; 5 | import org.springframework.http.converter.FormHttpMessageConverter; 6 | import org.springframework.http.converter.HttpMessageConverter; 7 | import org.springframework.http.converter.StringHttpMessageConverter; 8 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 9 | import org.springframework.web.client.RestOperations; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | import java.nio.charset.Charset; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * Created by YG on 2017-01-20. 18 | */ 19 | public class RestTemplateFactory { 20 | public static RestOperations getRestOperations(HttpComponentsClientHttpRequestFactory factory) { 21 | RestTemplate restTemplate = new RestTemplate(factory); 22 | 23 | StringHttpMessageConverter stringMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8")); 24 | MappingJackson2HttpMessageConverter jackson2Converter = new MappingJackson2HttpMessageConverter(); 25 | ByteArrayHttpMessageConverter byteArrayHttpMessageConverter = new ByteArrayHttpMessageConverter(); 26 | FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter(); 27 | formHttpMessageConverter.setCharset(Charset.forName("UTF-8")); 28 | 29 | List> converters = new ArrayList<>(); 30 | converters.add(jackson2Converter); 31 | converters.add(stringMessageConverter); 32 | converters.add(byteArrayHttpMessageConverter); 33 | converters.add(formHttpMessageConverter); 34 | 35 | restTemplate.setMessageConverters(converters); 36 | return restTemplate; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/conversation/Conversation.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.conversation; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by YG on 2017-04-03. 8 | */ 9 | public class Conversation { 10 | private String function; 11 | private Map param; 12 | 13 | public Conversation() { 14 | } 15 | 16 | public Conversation(String function) { 17 | this.function = function; 18 | this.param = new HashMap<>(); 19 | } 20 | 21 | public String getFunction() { 22 | return function; 23 | } 24 | 25 | public Map getParam() { 26 | return param; 27 | } 28 | 29 | public void put(String key, String value) { 30 | param.put(key, value); 31 | } 32 | 33 | public void setParam(Map param) { 34 | this.param = param; 35 | } 36 | 37 | public void setFunction(String function) { 38 | this.function = function; 39 | } 40 | 41 | public void put(Conversation conversation) { 42 | if(conversation != null) { 43 | this.param.putAll(conversation.param); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/conversation/ConversationService.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.conversation; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.github.kingbbode.chatbot.core.common.properties.BotProperties; 6 | import lombok.RequiredArgsConstructor; 7 | import org.joda.time.DateTime; 8 | import org.springframework.core.env.Environment; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | 11 | import javax.annotation.PostConstruct; 12 | import java.io.IOException; 13 | 14 | /** 15 | * Created by YG on 2017-04-03. 16 | */ 17 | @RequiredArgsConstructor 18 | public class ConversationService { 19 | private final Environment environment; 20 | private final RedisTemplate redisTemplate; 21 | private final ObjectMapper objectMapper; 22 | private final BotProperties botProperties; 23 | 24 | private static final String REDIS_KEY = ":conversation:"; 25 | 26 | private String key; 27 | private int expireTime; 28 | 29 | @PostConstruct 30 | private void init() { 31 | this.key = botProperties.isTestMode() ? "test:" + botProperties.getName() + REDIS_KEY : botProperties.getName() + REDIS_KEY; 32 | this.expireTime = environment.acceptsProfiles("dev") ? 300 : 30; 33 | } 34 | 35 | 36 | public Conversation pop(String userId) throws IOException { 37 | String result = redisTemplate.opsForList().rightPop(this.key + userId); 38 | if (result != null) { 39 | redisTemplate.expireAt(this.key + userId, new DateTime().plusSeconds(expireTime).toDate()); 40 | } 41 | return result != null ? objectMapper.readValue(redisTemplate.opsForList().rightPop(this.key + userId), Conversation.class) : null; 42 | } 43 | 44 | public void push(String userId, Conversation value) throws JsonProcessingException { 45 | redisTemplate.opsForList().rightPush(this.key + userId, objectMapper.writeValueAsString(value)); 46 | this.touch(userId); 47 | } 48 | 49 | public void touch(String userId) { 50 | redisTemplate.expireAt(this.key + userId, new DateTime().plusSeconds(expireTime).toDate()); 51 | } 52 | 53 | public Conversation getLatest(String userId) throws IOException { 54 | Long size = redisTemplate.opsForList().size(this.key + userId); 55 | String result = redisTemplate.opsForList().index(this.key + userId, (size == null ? 0 : size) - 1); 56 | if (result != null) { 57 | redisTemplate.expireAt(this.key + userId, new DateTime().plusSeconds(expireTime).toDate()); 58 | } 59 | return result != null ? objectMapper.readValue(result, Conversation.class) : null; 60 | } 61 | 62 | public void delete(String userId) { 63 | redisTemplate.opsForValue().getOperations().delete(this.key + userId); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/event/EmptyResultException.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.event; 2 | 3 | public class EmptyResultException extends RuntimeException{ 4 | } 5 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/event/Event.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.event; 2 | 3 | import com.github.kingbbode.chatbot.core.brain.DispatcherBrain; 4 | import com.github.kingbbode.chatbot.core.common.interfaces.Dispatcher; 5 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 6 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.Setter; 10 | import org.springframework.util.ObjectUtils; 11 | 12 | /** 13 | * Created by YG on 2017-07-10. 14 | */ 15 | @Getter 16 | @Setter 17 | @NoArgsConstructor 18 | public class Event { 19 | private Dispatcher dispatcher; 20 | private T item; 21 | private DispatcherBrain brain; 22 | 23 | public Event(Dispatcher dispatcher, T item, DispatcherBrain brain) { 24 | this.dispatcher = dispatcher; 25 | this.item = item; 26 | this.brain = brain; 27 | } 28 | 29 | protected void setBrain(DispatcherBrain brain) { 30 | this.brain = brain; 31 | } 32 | 33 | public void execute(){ 34 | try { 35 | BrainRequest request = dispatcher.dispatch(item); 36 | if(ObjectUtils.isEmpty(request)) { 37 | return; 38 | } 39 | BrainResult result = brain.execute(request); 40 | if(ObjectUtils.isEmpty(result)) { 41 | return; 42 | } 43 | dispatcher.onMessage(request, result); 44 | } catch (EmptyResultException e) { 45 | //noop 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/event/EventQueue.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.event; 2 | 3 | import java.util.Queue; 4 | import java.util.concurrent.ConcurrentLinkedQueue; 5 | 6 | /** 7 | * Created by YG on 2016-11-03. 8 | */ 9 | public class EventQueue { 10 | private Queue queue = new ConcurrentLinkedQueue<>(); 11 | 12 | public boolean hasNext(){ 13 | return queue.size()>0; 14 | } 15 | 16 | public void offer(Event e) { 17 | if (e == null) { 18 | return; 19 | } 20 | queue.offer(e); 21 | } 22 | 23 | Event poll() { 24 | return queue.poll(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chatbot-spring-boot-core/src/main/java/com/github/kingbbode/chatbot/core/event/TaskRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.core.event; 2 | 3 | import com.github.kingbbode.chatbot.core.common.interfaces.EventSensor; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.scheduling.annotation.Scheduled; 7 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 8 | 9 | import java.util.Collection; 10 | import java.util.List; 11 | 12 | /** 13 | * Created by YG on 2016-08-17. 14 | */ 15 | @Slf4j 16 | @RequiredArgsConstructor 17 | public class TaskRunner { 18 | 19 | private final ThreadPoolTaskExecutor executor; 20 | private final EventQueue eventQueue; 21 | private final List eventSensors; 22 | 23 | @Scheduled(fixedDelay = 10) 24 | private void execute(){ 25 | while(eventQueue.hasNext()){ 26 | Event event = eventQueue.poll(); 27 | executor.execute(new FetcherTask(event)); 28 | } 29 | } 30 | 31 | public static class FetcherTask implements Runnable { 32 | Event event; 33 | FetcherTask(Event event) { 34 | this.event = event; 35 | } 36 | 37 | @Override 38 | public void run() { 39 | try { 40 | this.event.execute(); 41 | } catch (Exception e) { 42 | log.warn("execute error message={}", e.getMessage(), e); 43 | } 44 | } 45 | } 46 | 47 | @Scheduled(fixedDelay = 10) 48 | public void sensingEvent(){ 49 | eventSensors.stream() 50 | .map(EventSensor::sensingEvent) 51 | .flatMap(Collection::stream) 52 | .forEach(this.eventQueue::offer); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /chatbot-spring-boot-line-starter/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | compile project(":chatbot-spring-boot-autoconfigure") 6 | compile project(":messenger:line") 7 | } 8 | -------------------------------------------------------------------------------- /chatbot-spring-boot-slack-starter/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | compile project(":chatbot-spring-boot-autoconfigure") 6 | compile project(":messenger:slack") 7 | } 8 | -------------------------------------------------------------------------------- /chatbot-spring-boot-telegram-starter/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | compile project(":chatbot-spring-boot-autoconfigure") 6 | compile project(":messenger:telegram") 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Mar 04 14:01:33 KST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /img/conv1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/conv1.png -------------------------------------------------------------------------------- /img/conv2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/conv2.png -------------------------------------------------------------------------------- /img/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/example.png -------------------------------------------------------------------------------- /img/line/1_create_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/line/1_create_app.png -------------------------------------------------------------------------------- /img/line/2_create_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/line/2_create_app.png -------------------------------------------------------------------------------- /img/line/2_secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/line/2_secret.png -------------------------------------------------------------------------------- /img/line/3_use_webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/line/3_use_webhook.png -------------------------------------------------------------------------------- /img/line/4_create_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/line/4_create_token.png -------------------------------------------------------------------------------- /img/line/5_ngrok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/line/5_ngrok.png -------------------------------------------------------------------------------- /img/line/6_reg_callback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/line/6_reg_callback.png -------------------------------------------------------------------------------- /img/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/sample.jpg -------------------------------------------------------------------------------- /img/slack/1_create_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/slack/1_create_app.png -------------------------------------------------------------------------------- /img/slack/2_app_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/slack/2_app_token.png -------------------------------------------------------------------------------- /img/slack/3_bot_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/slack/3_bot_token.png -------------------------------------------------------------------------------- /img/slack/4_scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/slack/4_scope.png -------------------------------------------------------------------------------- /img/slack/5_on_socket_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/slack/5_on_socket_mode.png -------------------------------------------------------------------------------- /img/slack/6_event_subscribe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/slack/6_event_subscribe.png -------------------------------------------------------------------------------- /img/telegram/1_create_bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingbbode/spring-boot-chatbot/1e9b08ce0d9323a015e868d4a345e96c351e1671/img/telegram/1_create_bot.png -------------------------------------------------------------------------------- /messenger/line/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'org.springframework.boot' 3 | 4 | sourceCompatibility = 1.8 5 | 6 | bootJar { enabled = false } 7 | jar { enabled = true } 8 | 9 | 10 | dependencies { 11 | compile project(":chatbot-spring-boot-core") 12 | compile('com.linecorp.bot:line-bot-spring-boot') 13 | compileOnly("org.projectlombok:lombok") 14 | } 15 | 16 | -------------------------------------------------------------------------------- /messenger/line/src/main/java/com/github/kingbbode/messenger/line/LineDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.messenger.line; 2 | 3 | import com.github.kingbbode.chatbot.core.brain.DispatcherBrain; 4 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 5 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 6 | import com.github.kingbbode.chatbot.core.common.result.DefaultBrainResult; 7 | import com.github.kingbbode.chatbot.core.common.result.SimpleMessageBrainResult; 8 | import com.linecorp.bot.model.event.MessageEvent; 9 | import com.linecorp.bot.model.event.message.TextMessageContent; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.beans.factory.InitializingBean; 12 | 13 | import java.util.Optional; 14 | 15 | @Slf4j 16 | public class LineDispatcher implements InitializingBean { 17 | 18 | private static final String MESSENGER = "LINE"; 19 | 20 | private DispatcherBrain dispatcherBrain; 21 | 22 | public LineDispatcher(DispatcherBrain dispatcherBrain) { 23 | this.dispatcherBrain = dispatcherBrain; 24 | } 25 | 26 | public String dispatch(MessageEvent event) { 27 | BrainRequest brainRequest = BrainRequest.builder() 28 | .user(event.getSource().getUserId()) 29 | .room(event.getReplyToken().trim()) 30 | .content(event.getMessage().getText()) 31 | .messageNo(event.getMessage().getId()) 32 | .messenger(MESSENGER) 33 | .build(); 34 | 35 | BrainResult brainResult = dispatcherBrain.execute(brainRequest); 36 | 37 | if(!(brainResult instanceof SimpleMessageBrainResult)) { 38 | log.warn("not support result type. {}", brainResult.getClass().getSimpleName()); 39 | return ""; 40 | } 41 | 42 | return Optional.of((DefaultBrainResult) brainResult) 43 | .map(DefaultBrainResult::getMessage) 44 | .orElse(""); 45 | } 46 | 47 | @Override 48 | public void afterPropertiesSet() throws Exception { 49 | log.info("[BOT] Registered LineDispatcher."); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /messenger/line/src/main/java/com/github/kingbbode/messenger/line/LineEventSensor.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.messenger.line; 2 | 3 | import com.linecorp.bot.model.event.JoinEvent; 4 | import com.linecorp.bot.model.event.MessageEvent; 5 | import com.linecorp.bot.model.event.message.TextMessageContent; 6 | import com.linecorp.bot.model.message.TextMessage; 7 | import com.linecorp.bot.spring.boot.annotation.EventMapping; 8 | import com.linecorp.bot.spring.boot.annotation.LineMessageHandler; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.InitializingBean; 11 | 12 | @Slf4j 13 | @LineMessageHandler 14 | public class LineEventSensor implements InitializingBean { 15 | 16 | private LineDispatcher lineDispatcher; 17 | 18 | public LineEventSensor(LineDispatcher lineDispatcher) { 19 | this.lineDispatcher = lineDispatcher; 20 | } 21 | 22 | @EventMapping 23 | public TextMessage handleTextMessageEvent(MessageEvent event) { 24 | return new TextMessage(lineDispatcher.dispatch(event)); 25 | } 26 | 27 | @EventMapping 28 | public void joinEvent(JoinEvent event) { 29 | 30 | } 31 | 32 | @Override 33 | public void afterPropertiesSet() throws Exception { 34 | log.info("[BOT] Registered LineEventSensor."); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /messenger/slack/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | compile("com.slack.api:bolt-socket-mode") 6 | compile("javax.websocket:javax.websocket-api") 7 | compile("org.glassfish.tyrus.bundles:tyrus-standalone-client") 8 | compile("com.google.code.gson:gson") 9 | compile project(":chatbot-spring-boot-core") 10 | compileOnly("org.projectlombok:lombok") 11 | } -------------------------------------------------------------------------------- /messenger/slack/src/main/java/com/github/kingbbode/messenger/slack/SlackBotClient.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.messenger.slack; 2 | 3 | import com.slack.api.methods.MethodsClient; 4 | import com.slack.api.methods.SlackApiException; 5 | import com.slack.api.methods.request.chat.ChatPostMessageRequest; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.util.StringUtils; 8 | 9 | import java.io.IOException; 10 | 11 | @Slf4j 12 | public class SlackBotClient { 13 | private final MethodsClient methodsClient; 14 | 15 | public SlackBotClient(MethodsClient methodsClient) throws IOException { 16 | this.methodsClient = methodsClient; 17 | } 18 | 19 | public void sendMessage(SlackMessage message) { 20 | if (StringUtils.isEmpty(message.getChannel())) { 21 | return; 22 | } 23 | 24 | try { 25 | methodsClient.chatPostMessage(ChatPostMessageRequest.builder() 26 | .channel(message.getChannel()) 27 | .threadTs(message.getThreadTs()) 28 | .text(message.getText()) 29 | .blocks(message.getBlocks()) 30 | .attachments(message.getAttachments()) 31 | .build() 32 | ); 33 | } catch (IOException | SlackApiException e) { 34 | log.warn("slack chat post failed. {}", e.getMessage()); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /messenger/slack/src/main/java/com/github/kingbbode/messenger/slack/SlackDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.messenger.slack; 2 | 3 | import com.github.kingbbode.chatbot.core.common.interfaces.Dispatcher; 4 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 5 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 6 | import com.github.kingbbode.chatbot.core.common.result.SimpleMessageBrainResult; 7 | import com.github.kingbbode.messenger.slack.event.SlackEvent; 8 | import com.github.kingbbode.messenger.slack.result.SlackMessageBrainResult; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.springframework.beans.factory.InitializingBean; 12 | 13 | @Slf4j 14 | public class SlackDispatcher implements Dispatcher, InitializingBean { 15 | private static final String MESSENGER = "SLACK"; 16 | private final SlackBotClient slackBotClient; 17 | 18 | public SlackDispatcher(SlackBotClient slackRTMClient) { 19 | this.slackBotClient = slackRTMClient; 20 | } 21 | 22 | @Override 23 | public BrainRequest dispatch(SlackEvent message) { 24 | return BrainRequest.builder() 25 | .messenger(MESSENGER) 26 | .messageNo(message.getClientMsgId()) 27 | .user(message.getUser()) 28 | .room(message.getChannel()) 29 | .thread(message.getThead()) 30 | .content(message.getValue()) 31 | .build(); 32 | } 33 | 34 | @Override 35 | public void onMessage(BrainRequest brainRequest, BrainResult result) { 36 | if (result instanceof SimpleMessageBrainResult) { 37 | slackBotClient.sendMessage(SlackMessage.builder() 38 | .channel(extractChannel(result, brainRequest)) 39 | .threadTs(result.getThread()) 40 | .text("<@" + brainRequest.getUser() + ">\n" + ((SimpleMessageBrainResult) result).getMessage()) 41 | .build() 42 | ); 43 | } 44 | else if (result instanceof SlackMessageBrainResult) { 45 | SlackMessageBrainResult slackMessageResult = (SlackMessageBrainResult) result; 46 | slackBotClient.sendMessage( 47 | SlackMessage.builder() 48 | .channel(extractChannel(result, brainRequest)) 49 | .threadTs(result.getThread()) 50 | .text(slackMessageResult.getText()) 51 | .blocks(slackMessageResult.getBlocks()) 52 | .attachments(slackMessageResult.getAttachments()) 53 | .build() 54 | ); 55 | } 56 | else { 57 | log.warn("not support result type. {}", result.getClass().getSimpleName()); 58 | } 59 | } 60 | 61 | private String extractChannel(BrainResult result, BrainRequest brainRequest) { 62 | return StringUtils.isEmpty(result.getRoom()) ? brainRequest.getRoom() : result.getRoom(); 63 | } 64 | 65 | @Override 66 | public void afterPropertiesSet() { 67 | log.info("[BOT] Registered SlackDispatcher."); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /messenger/slack/src/main/java/com/github/kingbbode/messenger/slack/SlackEventSensor.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.messenger.slack; 2 | 3 | import com.github.kingbbode.chatbot.core.brain.DispatcherBrain; 4 | import com.github.kingbbode.chatbot.core.event.Event; 5 | import com.github.kingbbode.chatbot.core.event.EventQueue; 6 | import com.github.kingbbode.messenger.slack.event.BlockActionDispatcherBrain; 7 | import com.github.kingbbode.messenger.slack.event.SlackEvent; 8 | import com.slack.api.app_backend.events.payload.EventsApiPayload; 9 | import com.slack.api.bolt.App; 10 | import com.slack.api.bolt.context.builtin.ActionContext; 11 | import com.slack.api.bolt.context.builtin.EventContext; 12 | import com.slack.api.bolt.handler.BoltEventHandler; 13 | import com.slack.api.bolt.request.builtin.BlockActionRequest; 14 | import com.slack.api.bolt.response.Response; 15 | import com.slack.api.methods.SlackApiException; 16 | import com.slack.api.model.event.MessageEvent; 17 | import lombok.extern.slf4j.Slf4j; 18 | import org.springframework.beans.factory.InitializingBean; 19 | import org.springframework.util.StringUtils; 20 | 21 | import java.io.IOException; 22 | import java.util.List; 23 | import java.util.Objects; 24 | 25 | @Slf4j 26 | public class SlackEventSensor implements InitializingBean, BoltEventHandler { 27 | 28 | private final App app; 29 | private final List blockActionConnectors; 30 | private final SlackDispatcher slackDispatcher; 31 | private final EventQueue eventQueue; 32 | private final DispatcherBrain dispatcherBrain; 33 | 34 | public SlackEventSensor( 35 | App app, 36 | List blockActionConnectors, 37 | SlackDispatcher slackDispatcher, 38 | EventQueue eventQueue, 39 | DispatcherBrain dispatcherBrain) { 40 | this.app = app; 41 | this.blockActionConnectors = blockActionConnectors; 42 | this.slackDispatcher = slackDispatcher; 43 | this.eventQueue = eventQueue; 44 | this.dispatcherBrain = dispatcherBrain; 45 | } 46 | 47 | @Override 48 | public Response apply(EventsApiPayload eventsApiPayload, EventContext context) throws IOException, SlackApiException { 49 | MessageEvent event = eventsApiPayload.getEvent(); 50 | if(!StringUtils.isEmpty(event.getBotId()) || !Objects.isNull(event.getEdited())) { 51 | return context.ack(); 52 | } 53 | 54 | SlackEvent slackEvent = SlackEvent.builder() 55 | .clientMsgId(event.getClientMsgId()) 56 | .user(event.getUser()) 57 | .channel(event.getChannel()) 58 | .thead(StringUtils.hasText(event.getThreadTs()) ? event.getThreadTs() : event.getTs()) 59 | .value(event.getText()) 60 | .build(); 61 | 62 | this.eventQueue.offer(new Event<>(slackDispatcher, slackEvent, dispatcherBrain)); 63 | return context.ack(); 64 | } 65 | 66 | @Override 67 | public void afterPropertiesSet() throws Exception { 68 | app.event(MessageEvent.class, this); 69 | log.info("[BOT] Connect Slack BOT."); 70 | blockActionConnectors.forEach(connector -> 71 | app.blockAction(connector.getActionId(), (blockActionRequest, context) -> connect(connector, blockActionRequest, context)) 72 | ); 73 | log.info("[BOT] Connect Block Action. count={}", blockActionConnectors.size()); 74 | } 75 | 76 | private Response connect(BlockActionDispatcherBrain connector, BlockActionRequest blockActionRequest, ActionContext context) { 77 | blockActionRequest.getPayload().getActions().stream().findFirst() 78 | .ifPresent(action -> { 79 | SlackEvent slackEvent = SlackEvent.builder() 80 | .clientMsgId(blockActionRequest.getPayload().getTriggerId()) 81 | .user(blockActionRequest.getPayload().getUser().getId()) 82 | .channel(blockActionRequest.getPayload().getChannel().getId()) 83 | .thead(StringUtils.hasText(blockActionRequest.getPayload().getMessage().getThreadTs()) ? blockActionRequest.getPayload().getMessage().getThreadTs() : blockActionRequest.getPayload().getMessage().getTs()) 84 | .value(action.getValue()) 85 | .build(); 86 | this.eventQueue.offer(new Event<>(slackDispatcher, slackEvent, connector.dispatcher())); 87 | }); 88 | return context.ack(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /messenger/slack/src/main/java/com/github/kingbbode/messenger/slack/SlackMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.messenger.slack; 2 | 3 | import com.slack.api.model.Attachment; 4 | import com.slack.api.model.block.LayoutBlock; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | import java.util.List; 9 | 10 | @Getter 11 | public class SlackMessage { 12 | private String channel; 13 | private String threadTs; 14 | private String text; 15 | private List blocks; 16 | private List attachments; 17 | 18 | @Builder 19 | public SlackMessage(String channel, String threadTs, String text, List blocks, List attachments) { 20 | this.channel = channel; 21 | this.threadTs = threadTs; 22 | this.text = text; 23 | this.blocks = blocks; 24 | this.attachments = attachments; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /messenger/slack/src/main/java/com/github/kingbbode/messenger/slack/event/BlockActionDispatcherBrain.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.messenger.slack.event; 2 | 3 | import com.github.kingbbode.chatbot.core.brain.DispatcherBrain; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | public interface BlockActionDispatcherBrain { 8 | Pattern getActionId(); 9 | DispatcherBrain dispatcher(); 10 | } 11 | -------------------------------------------------------------------------------- /messenger/slack/src/main/java/com/github/kingbbode/messenger/slack/event/SlackEvent.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.messenger.slack.event; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | public class SlackEvent { 8 | private final String clientMsgId; 9 | private final String channel; 10 | private final String thead; 11 | private final String user; 12 | private final String value; 13 | 14 | @Builder 15 | public SlackEvent(String clientMsgId, String channel, String thead, String user, String value) { 16 | this.clientMsgId = clientMsgId; 17 | this.channel = channel; 18 | this.thead = thead; 19 | this.user = user; 20 | this.value = value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /messenger/slack/src/main/java/com/github/kingbbode/messenger/slack/result/SlackMessageBrainResult.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.messenger.slack.result; 2 | 3 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 4 | import com.slack.api.model.Attachment; 5 | import com.slack.api.model.block.LayoutBlock; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | 9 | import java.util.List; 10 | 11 | @Getter 12 | public class SlackMessageBrainResult implements BrainResult { 13 | 14 | private final String room; 15 | private final String thread; 16 | private final String text; 17 | private final List blocks; 18 | private final List attachments; 19 | 20 | @Builder 21 | public SlackMessageBrainResult(String room, String thread, String text, List blocks, List attachments) { 22 | this.room = room; 23 | this.thread = thread; 24 | this.text = text; 25 | this.blocks = blocks; 26 | this.attachments = attachments; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /messenger/telegram/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | compile project(":chatbot-spring-boot-core") 6 | compile ("org.telegram:telegrambots") 7 | compileOnly("org.projectlombok:lombok") 8 | } 9 | 10 | -------------------------------------------------------------------------------- /messenger/telegram/src/main/java/com/github/kingbbode/messenger/telegram/TelegramBotsApiWrapper.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.messenger.telegram; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.InitializingBean; 6 | import org.telegram.telegrambots.TelegramBotsApi; 7 | 8 | @Slf4j 9 | @RequiredArgsConstructor 10 | public class TelegramBotsApiWrapper extends TelegramBotsApi implements InitializingBean { 11 | 12 | private final TelegramEventSensor telegramEventSensor; 13 | 14 | @Override 15 | public void afterPropertiesSet() throws Exception { 16 | this.registerBot(telegramEventSensor); 17 | log.info("[BOT] register telegram sensor."); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /messenger/telegram/src/main/java/com/github/kingbbode/messenger/telegram/TelegramEventSensor.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.messenger.telegram; 2 | 3 | import com.github.kingbbode.chatbot.core.brain.DispatcherBrain; 4 | import com.github.kingbbode.chatbot.core.common.interfaces.Dispatcher; 5 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 6 | import com.github.kingbbode.chatbot.core.common.result.BrainResult; 7 | import com.github.kingbbode.chatbot.core.common.result.SimpleMessageBrainResult; 8 | import com.github.kingbbode.chatbot.core.event.Event; 9 | import com.github.kingbbode.chatbot.core.event.EventQueue; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.springframework.beans.factory.InitializingBean; 13 | import org.telegram.telegrambots.api.methods.send.SendMessage; 14 | import org.telegram.telegrambots.api.objects.Update; 15 | import org.telegram.telegrambots.bots.TelegramLongPollingBot; 16 | import org.telegram.telegrambots.exceptions.TelegramApiException; 17 | 18 | /** 19 | * Created by YG-MAC on 2018. 3. 4.. 20 | */ 21 | @Slf4j 22 | public class TelegramEventSensor extends TelegramLongPollingBot implements Dispatcher, InitializingBean { 23 | 24 | private static final String MESSENGER = "TELEGRAM"; 25 | private final String butUserName; 26 | private final String botToken; 27 | private final EventQueue eventQueue; 28 | private final DispatcherBrain dispatcherBrain; 29 | 30 | public TelegramEventSensor(EventQueue eventQueue, DispatcherBrain dispatcherBrain, String butUserName, String botToken) { 31 | super(); 32 | this.butUserName = butUserName; 33 | this.botToken = botToken; 34 | this.eventQueue = eventQueue; 35 | this.dispatcherBrain = dispatcherBrain; 36 | } 37 | 38 | @Override 39 | public void onUpdateReceived(Update update) { 40 | if(!update.hasMessage()) { 41 | return; 42 | } 43 | this.eventQueue.offer(new Event<>(this, update, dispatcherBrain)); 44 | } 45 | 46 | @Override 47 | public String getBotUsername() { 48 | return butUserName; 49 | } 50 | 51 | @Override 52 | public String getBotToken() { 53 | return botToken; 54 | } 55 | 56 | @Override 57 | public void afterPropertiesSet() throws Exception { 58 | log.info("[BOT] Registered TelegramEventSensor."); 59 | } 60 | 61 | @Override 62 | public BrainRequest dispatch(Update update) { 63 | return BrainRequest.builder() 64 | .user(String.valueOf(update.getMessage().getFrom().getId())) 65 | .room(String.valueOf(update.getMessage().getChatId())) 66 | .content(update.getMessage().getText()) 67 | .messageNo(String.valueOf(update.getMessage().getChatId())) 68 | .messenger(MESSENGER) 69 | .build(); 70 | } 71 | 72 | @Override 73 | public void onMessage(BrainRequest brainRequest, BrainResult brainResult) { 74 | if(!(brainResult instanceof SimpleMessageBrainResult)) { 75 | log.warn("not support result type. {}", brainResult.getClass().getSimpleName()); 76 | return; 77 | } 78 | try { 79 | sendApiMethod(new SendMessage( 80 | StringUtils.isEmpty(brainResult.getRoom()) ? brainRequest.getRoom() : brainResult.getRoom(), 81 | ((SimpleMessageBrainResult) brainResult).getMessage()) 82 | ); 83 | } catch (TelegramApiException e) { 84 | log.error("telegram message send failed. message={}", e.getMessage(), e); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /sample-bot/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'org.springframework.boot' 3 | apply plugin: "io.spring.dependency-management" 4 | 5 | sourceCompatibility = 1.8 6 | 7 | dependencies { 8 | /*compile project(":chatbot-spring-boot-line-starter") 9 | compile project(":chatbot-spring-boot-telegram-starter")*/ 10 | compile project(":chatbot-spring-boot-slack-starter") 11 | testCompile group: 'junit', name: 'junit', version: '4.12' 12 | } 13 | -------------------------------------------------------------------------------- /sample-bot/src/main/java/com/github/kingbbode/chatbot/sample/Application.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.sample; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | public static void main(String[] args) { 9 | SpringApplication.run(Application.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample-bot/src/main/java/com/github/kingbbode/chatbot/sample/brain/HelloWorldBrain.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.sample.brain; 2 | 3 | import com.github.kingbbode.chatbot.core.common.annotations.Brain; 4 | import com.github.kingbbode.chatbot.core.common.annotations.BrainCell; 5 | import com.github.kingbbode.chatbot.core.common.request.BrainRequest; 6 | import com.github.kingbbode.messenger.slack.result.SlackMessageBrainResult; 7 | import com.slack.api.model.block.ActionsBlock; 8 | import com.slack.api.model.block.SectionBlock; 9 | import com.slack.api.model.block.composition.MarkdownTextObject; 10 | import com.slack.api.model.block.composition.PlainTextObject; 11 | import com.slack.api.model.block.element.ButtonElement; 12 | 13 | import java.util.Arrays; 14 | import java.util.Collections; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | @Brain 19 | public class HelloWorldBrain { 20 | 21 | @BrainCell(key="안녕", function = "hello") 22 | public String hello(BrainRequest brainRequest) { 23 | return "안녕~~"; 24 | } 25 | 26 | 27 | @BrainCell(key = "따라해봐", function = "echo-start") 28 | public String echo(BrainRequest brainRequest) { 29 | return "말해봐"; 30 | } 31 | 32 | @BrainCell(function = "echo-end", parent = "echo-start") 33 | public String echo2(BrainRequest brainRequest) { 34 | return brainRequest.getContent(); 35 | } 36 | 37 | 38 | private Map map = new HashMap<>(); 39 | 40 | @BrainCell(key = "기록", function = "record-start") 41 | public String record(BrainRequest brainRequest) { 42 | return "조회? 저장?"; 43 | } 44 | 45 | @BrainCell(function = "record-read", key="조회", parent = "record-start") 46 | public String record2(BrainRequest brainRequest) { 47 | return "무엇을?"; 48 | } 49 | 50 | @BrainCell(function = "record-read-2", parent = "record-read") 51 | public String record21(BrainRequest brainRequest) { 52 | return map.getOrDefault(brainRequest.getContent(), "저장된 내용이 없다"); 53 | } 54 | 55 | @BrainCell(function = "record-save", key="저장", parent = "record-start") 56 | public String record3(BrainRequest brainRequest) { 57 | return "무엇을?"; 58 | } 59 | 60 | @BrainCell(function = "record-save-2", parent = "record-save") 61 | public String record4(BrainRequest brainRequest) { 62 | brainRequest.getConversation().put("key", brainRequest.getContent()); 63 | return "내용은?"; 64 | } 65 | 66 | @BrainCell(function = "record-save-3", parent = "record-save-2") 67 | public String record5(BrainRequest brainRequest) { 68 | map.put(brainRequest.getConversation().getParam().get("key"), brainRequest.getContent()); 69 | return "저장했다"; 70 | } 71 | 72 | 73 | 74 | @BrainCell(key="슬랙테스트", function = "slack") 75 | public SlackMessageBrainResult slack(BrainRequest brainRequest) { 76 | return SlackMessageBrainResult.builder() 77 | .room(brainRequest.getRoom()) 78 | .blocks( 79 | Arrays.asList( 80 | ActionsBlock.builder() 81 | .elements( 82 | Collections.singletonList( 83 | ButtonElement.builder() 84 | .text(PlainTextObject.builder() 85 | .text("Farmhouse") 86 | .emoji(true) 87 | .build() 88 | ) 89 | .value("king") 90 | .actionId("ACTION1") 91 | .build() 92 | ) 93 | ) 94 | .build(), 95 | SectionBlock.builder() 96 | .text( 97 | MarkdownTextObject.builder() 98 | .text("This is a section block with a button.") 99 | .build() 100 | ) 101 | .accessory( 102 | ButtonElement.builder() 103 | .text(PlainTextObject.builder() 104 | .text("Click Me") 105 | .emoji(true) 106 | .build() 107 | ) 108 | .value("bbode") 109 | .actionId("ACTION2") 110 | .build() 111 | ) 112 | .build() 113 | ) 114 | ) 115 | .build(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /sample-bot/src/main/java/com/github/kingbbode/chatbot/sample/config/SlackActionConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.kingbbode.chatbot.sample.config; 2 | 3 | import com.github.kingbbode.chatbot.core.brain.DispatcherBrain; 4 | import com.github.kingbbode.chatbot.core.common.result.DefaultBrainResult; 5 | import com.github.kingbbode.messenger.slack.event.BlockActionDispatcherBrain; 6 | import com.github.kingbbode.messenger.slack.result.SlackMessageBrainResult; 7 | import com.slack.api.model.block.SectionBlock; 8 | import com.slack.api.model.block.composition.MarkdownTextObject; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | import java.util.Collections; 13 | import java.util.regex.Pattern; 14 | 15 | @Configuration 16 | public class SlackActionConfig { 17 | 18 | @Bean 19 | public BlockActionDispatcherBrain slackTest1() { 20 | return new BlockActionDispatcherBrain() { 21 | 22 | @Override 23 | public Pattern getActionId() { 24 | return Pattern.compile("ACTION1*"); 25 | } 26 | 27 | @Override 28 | public DispatcherBrain dispatcher() { 29 | return brainRequest -> DefaultBrainResult.builder() 30 | .message(brainRequest.getContent()) 31 | .build(); 32 | } 33 | }; 34 | } 35 | 36 | @Bean 37 | public BlockActionDispatcherBrain slackTest2() { 38 | return new BlockActionDispatcherBrain() { 39 | 40 | @Override 41 | public Pattern getActionId() { 42 | return Pattern.compile("ACTION2*"); 43 | } 44 | 45 | @Override 46 | public DispatcherBrain dispatcher() { 47 | return brainRequest -> SlackMessageBrainResult.builder() 48 | .room(brainRequest.getRoom()) 49 | .thread(brainRequest.getThread()) 50 | .blocks( 51 | Collections.singletonList( 52 | SectionBlock.builder() 53 | .text( 54 | MarkdownTextObject.builder() 55 | .text("Action2!") 56 | .build() 57 | ) 58 | .build() 59 | ) 60 | ) 61 | .build(); 62 | } 63 | }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /sample-bot/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | telegram: 2 | name: kingbbode-sample 3 | token: xxx 4 | slack: 5 | token: xxx 6 | 7 | line: 8 | bot: 9 | channel-token: xxx 10 | channel-secret: xxx 11 | 12 | debug: true 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'spring-boot-chatbot' 2 | include 'chatbot-spring-boot-core' 3 | include 'messenger:line' 4 | findProject(':messenger:line')?.name = 'line' 5 | include 'messenger:telegram' 6 | findProject(':messenger:telegram')?.name = 'telegram' 7 | include 'messenger:slack' 8 | findProject(':messenger:slack')?.name = 'slack' 9 | 10 | include 'chatbot-spring-boot-autoconfigure' 11 | include 'chatbot-spring-boot-line-starter' 12 | include 'chatbot-spring-boot-telegram-starter' 13 | include 'chatbot-spring-boot-slack-starter' 14 | 15 | if (!System.env.JITPACK) 16 | include 'sample-bot' 17 | 18 | --------------------------------------------------------------------------------