From 18465ada303fbc110baba83d2b5f87011c637702 Mon Sep 17 00:00:00 2001 From: Ruslan Penkrat Date: Tue, 10 Aug 2021 23:22:25 +0300 Subject: [PATCH] initial commit --- .gitignore | 19 +++ README.MD | 17 ++ pom.xml | 57 +++++++ stbf-api/pom.xml | 30 ++++ .../main/java/ru/penkrat/stbf/api/Action.java | 57 +++++++ .../ru/penkrat/stbf/api/BotCommandChain.java | 39 +++++ .../java/ru/penkrat/stbf/api/BotRequest.java | 17 ++ .../java/ru/penkrat/stbf/api/BotResponse.java | 15 ++ .../java/ru/penkrat/stbf/api/Command.java | 7 + .../ru/penkrat/stbf/api/CommandChain.java | 7 + .../java/ru/penkrat/stbf/api/Keyboard.java | 5 + .../ru/penkrat/stbf/api/KeyboardBuilder.java | 33 ++++ .../ru/penkrat/stbf/api/RequestMatcher.java | 8 + .../main/java/ru/penkrat/stbf/api/Screen.java | 14 ++ .../ru/penkrat/stbf/api/ScreenProperties.java | 23 +++ .../ru.penkrat.stbf.api.KeyboardBuilder | 1 + stbf-common/pom.xml | 41 +++++ .../common/command/AbstractActionCommand.java | 29 ++++ .../stbf/common/command/SimpleCommand.java | 36 +++++ .../stbf/common/screen/TextScreen.java | 34 ++++ .../penkrat/stbf/tools/RequestMatchers.java | 20 +++ .../test/java/ru/penkrat/stbf/AppTest.java | 20 +++ stbf-pengrad/pom.xml | 43 +++++ .../stbf/impl/pengrad/BotRequestImpl.java | 61 +++++++ .../stbf/impl/pengrad/BotResponseImpl.java | 151 ++++++++++++++++++ .../stbf/impl/pengrad/KeyboardImpl.java | 16 ++ .../impl/pengrad/PengradKeyboardBuilder.java | 149 +++++++++++++++++ .../stbf/impl/pengrad/PengradTelegramBot.java | 35 ++++ .../ru.penkrat.stbf.api.KeyboardBuilder | 1 + stbf-rubenlagus/pom.xml | 41 +++++ .../stbf/impl/rubenlagus/BotRequestImpl.java | 58 +++++++ .../stbf/impl/rubenlagus/BotResponseImpl.java | 113 +++++++++++++ .../stbf/impl/rubenlagus/KeyboardImpl.java | 18 +++ .../rubenlagus/RubenlagusKeyboardBuilder.java | 150 +++++++++++++++++ .../rubenlagus/RubenlagusTelegramBot.java | 54 +++++++ .../ru.penkrat.stbf.api.KeyboardBuilder | 1 + stbf-test/pom.xml | 53 ++++++ .../ru/penkrat/stbf/test/BotRequestImpl.java | 42 +++++ .../ru/penkrat/stbf/test/BotResponseImpl.java | 43 +++++ .../java/ru/penkrat/stbf/test/Result.java | 23 +++ .../java/ru/penkrat/stbf/test/TestBot.java | 43 +++++ .../test/java/ru/penkrat/stbf/BotTest.java | 23 +++ 42 files changed, 1647 insertions(+) create mode 100644 .gitignore create mode 100644 README.MD create mode 100644 pom.xml create mode 100644 stbf-api/pom.xml create mode 100644 stbf-api/src/main/java/ru/penkrat/stbf/api/Action.java create mode 100644 stbf-api/src/main/java/ru/penkrat/stbf/api/BotCommandChain.java create mode 100644 stbf-api/src/main/java/ru/penkrat/stbf/api/BotRequest.java create mode 100644 stbf-api/src/main/java/ru/penkrat/stbf/api/BotResponse.java create mode 100644 stbf-api/src/main/java/ru/penkrat/stbf/api/Command.java create mode 100644 stbf-api/src/main/java/ru/penkrat/stbf/api/CommandChain.java create mode 100644 stbf-api/src/main/java/ru/penkrat/stbf/api/Keyboard.java create mode 100644 stbf-api/src/main/java/ru/penkrat/stbf/api/KeyboardBuilder.java create mode 100644 stbf-api/src/main/java/ru/penkrat/stbf/api/RequestMatcher.java create mode 100644 stbf-api/src/main/java/ru/penkrat/stbf/api/Screen.java create mode 100644 stbf-api/src/main/java/ru/penkrat/stbf/api/ScreenProperties.java create mode 100644 stbf-api/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder create mode 100644 stbf-common/pom.xml create mode 100644 stbf-common/src/main/java/ru/penkrat/stbf/common/command/AbstractActionCommand.java create mode 100644 stbf-common/src/main/java/ru/penkrat/stbf/common/command/SimpleCommand.java create mode 100644 stbf-common/src/main/java/ru/penkrat/stbf/common/screen/TextScreen.java create mode 100644 stbf-common/src/main/java/ru/penkrat/stbf/tools/RequestMatchers.java create mode 100644 stbf-common/src/test/java/ru/penkrat/stbf/AppTest.java create mode 100644 stbf-pengrad/pom.xml create mode 100644 stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/BotRequestImpl.java create mode 100644 stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/BotResponseImpl.java create mode 100644 stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/KeyboardImpl.java create mode 100644 stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/PengradKeyboardBuilder.java create mode 100644 stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/PengradTelegramBot.java create mode 100644 stbf-pengrad/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder create mode 100644 stbf-rubenlagus/pom.xml create mode 100644 stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/BotRequestImpl.java create mode 100644 stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/BotResponseImpl.java create mode 100644 stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/KeyboardImpl.java create mode 100644 stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/RubenlagusKeyboardBuilder.java create mode 100644 stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/RubenlagusTelegramBot.java create mode 100644 stbf-rubenlagus/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder create mode 100644 stbf-test/pom.xml create mode 100644 stbf-test/src/main/java/ru/penkrat/stbf/test/BotRequestImpl.java create mode 100644 stbf-test/src/main/java/ru/penkrat/stbf/test/BotResponseImpl.java create mode 100644 stbf-test/src/main/java/ru/penkrat/stbf/test/Result.java create mode 100644 stbf-test/src/main/java/ru/penkrat/stbf/test/TestBot.java create mode 100644 stbf-test/src/test/java/ru/penkrat/stbf/BotTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b42d33 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..07e7cf2 --- /dev/null +++ b/README.MD @@ -0,0 +1,17 @@ +## Простой фреймворк\фасад для Telegram-бота + +### Цели + +* Предоставить удобные абстракции +* Предоставить инструменты для тестирования +* Отделить логику бота от конкретной реализации + +### Основная идея + +Цепочка из `Command` принимает `BotRequest` и `BotResponse`, и после обработки вызывает следующий `Command`. + +`BotRequest` содержит информации о действии пользователя. + +`BotResponse` позволяет отправить ответ в виде `Screen`. + +`Screen` содержит отображаемый текст и действия -- кнопки для клавиатуры или inline-кнопки с данными. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8acb628 --- /dev/null +++ b/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + pom + + ru.penkrat.stbf + stbf-parent + 0.0.1-SNAPSHOT + stbf-parent + + Simple Telegram Bot Facade + + 1.8 + 1.18.6 + 1.7.25 + + + + stbf-api + stbf-pengrad + stbf-test + stbf-common + stbf-rubenlagus + + + + + org.slf4j + slf4j-api + ${slf4j.version} + compile + + + org.projectlombok + lombok + ${lombok.version} + true + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + diff --git a/stbf-api/pom.xml b/stbf-api/pom.xml new file mode 100644 index 0000000..8179595 --- /dev/null +++ b/stbf-api/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + + ru.penkrat.stbf + stbf-parent + 0.0.1-SNAPSHOT + + + stbf-api + stbf-api + Simple Telegram Bot Facade API Module + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + diff --git a/stbf-api/src/main/java/ru/penkrat/stbf/api/Action.java b/stbf-api/src/main/java/ru/penkrat/stbf/api/Action.java new file mode 100644 index 0000000..f7a4be8 --- /dev/null +++ b/stbf-api/src/main/java/ru/penkrat/stbf/api/Action.java @@ -0,0 +1,57 @@ +package ru.penkrat.stbf.api; + +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import lombok.ToString; + +@Getter +@Builder +@ToString(of = "text") +public class Action { + + public static Action simple(String text) { + return builder().text(text).build(); + } + + public static Action simple(String text, String cmd) { + validateCmd(cmd); + return builder().text(text).cmd(cmd).build(); + } + + public static Action callback(String text, String callbackData) { + return builder().inline(true).text(text).callbackData(callbackData).build(); + } + + public static Action requestContact(String text) { + return builder().text(text).requestContact(true).build(); + } + + private boolean inline; + + private String text; + + private String cmd; + + // in-line + private String callbackData; + private String url; + + // keyboard + private boolean requestContact; + private boolean requestLocation; + + /** + * Text of the command, 1-32 characters. Can contain only lowercase English + * letters, digits and underscores. + */ + private static void validateCmd(@NonNull String cmd) { + if (cmd.length() > 32) { + throw new IllegalArgumentException("Max length - 32 characters"); + } + if (!cmd.startsWith("/")) { + throw new IllegalArgumentException("Command must start / character"); + } + // TODO validate lowercase etc + } +} diff --git a/stbf-api/src/main/java/ru/penkrat/stbf/api/BotCommandChain.java b/stbf-api/src/main/java/ru/penkrat/stbf/api/BotCommandChain.java new file mode 100644 index 0000000..e143d28 --- /dev/null +++ b/stbf-api/src/main/java/ru/penkrat/stbf/api/BotCommandChain.java @@ -0,0 +1,39 @@ +package ru.penkrat.stbf.api; + +import java.util.ArrayList; +import java.util.List; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class BotCommandChain implements CommandChain { + + private final List commands; + + public BotCommandChain() { + this(new ArrayList<>()); + } + + @Override + public void processCommand(BotRequest botRequest, BotResponse botResponse) { + if (!commands.isEmpty()) { + try { + Command command = commands.get(0); + log.debug("Run command {}", command.getClass().getSimpleName()); + command.process(botRequest, botResponse, + new BotCommandChain(commands.subList(1, commands.size()))); + } catch (Exception e) { + log.error("Error in command:", e); + } + } + } + + public BotCommandChain add(Command cmd) { + commands.add(cmd); + return this; + } + +} diff --git a/stbf-api/src/main/java/ru/penkrat/stbf/api/BotRequest.java b/stbf-api/src/main/java/ru/penkrat/stbf/api/BotRequest.java new file mode 100644 index 0000000..05b9a9b --- /dev/null +++ b/stbf-api/src/main/java/ru/penkrat/stbf/api/BotRequest.java @@ -0,0 +1,17 @@ +package ru.penkrat.stbf.api; + +import java.util.Optional; + +public interface BotRequest { + + Optional getMessageText(); + + Optional getPhoneNumber(); + + Optional getCallbackData(); + + Optional getCallbackMessageText(); + + Long getChatId(); + +} diff --git a/stbf-api/src/main/java/ru/penkrat/stbf/api/BotResponse.java b/stbf-api/src/main/java/ru/penkrat/stbf/api/BotResponse.java new file mode 100644 index 0000000..b86482a --- /dev/null +++ b/stbf-api/src/main/java/ru/penkrat/stbf/api/BotResponse.java @@ -0,0 +1,15 @@ +package ru.penkrat.stbf.api; + +public interface BotResponse { + + void send(Screen screen); + + void sendFile(String filename, byte[] data); + + void editMessage(String text); + + void deleteMessage(); + + void edit(Screen screen); + +} diff --git a/stbf-api/src/main/java/ru/penkrat/stbf/api/Command.java b/stbf-api/src/main/java/ru/penkrat/stbf/api/Command.java new file mode 100644 index 0000000..e8bca75 --- /dev/null +++ b/stbf-api/src/main/java/ru/penkrat/stbf/api/Command.java @@ -0,0 +1,7 @@ +package ru.penkrat.stbf.api; + +public interface Command { + + void process(BotRequest botRequest, BotResponse botResponse, CommandChain chain); + +} diff --git a/stbf-api/src/main/java/ru/penkrat/stbf/api/CommandChain.java b/stbf-api/src/main/java/ru/penkrat/stbf/api/CommandChain.java new file mode 100644 index 0000000..9399f7e --- /dev/null +++ b/stbf-api/src/main/java/ru/penkrat/stbf/api/CommandChain.java @@ -0,0 +1,7 @@ +package ru.penkrat.stbf.api; + +public interface CommandChain { + + void processCommand(BotRequest botRequest, BotResponse botResponse); + +} diff --git a/stbf-api/src/main/java/ru/penkrat/stbf/api/Keyboard.java b/stbf-api/src/main/java/ru/penkrat/stbf/api/Keyboard.java new file mode 100644 index 0000000..19d7d37 --- /dev/null +++ b/stbf-api/src/main/java/ru/penkrat/stbf/api/Keyboard.java @@ -0,0 +1,5 @@ +package ru.penkrat.stbf.api; + +public interface Keyboard { + +} diff --git a/stbf-api/src/main/java/ru/penkrat/stbf/api/KeyboardBuilder.java b/stbf-api/src/main/java/ru/penkrat/stbf/api/KeyboardBuilder.java new file mode 100644 index 0000000..cc93768 --- /dev/null +++ b/stbf-api/src/main/java/ru/penkrat/stbf/api/KeyboardBuilder.java @@ -0,0 +1,33 @@ +package ru.penkrat.stbf.api; + +import java.util.ServiceLoader; + +public interface KeyboardBuilder { + + ServiceLoader keyboardBuilderLoader = ServiceLoader.load(KeyboardBuilder.class); + + public static KeyboardBuilder newKeyboard() { + for (KeyboardBuilder kb : keyboardBuilderLoader) { + return kb.newInstance(); + } + throw new IllegalStateException("No service KeyboardBuilder found"); + } + + KeyboardBuilder newInstance(); + + public static Keyboard singleKey(Action action) { + return newKeyboard().add(action).build(); + } + + default KeyboardBuilder add(String text, String callbackData) { + return add(Action.callback(text, callbackData)); + } + + KeyboardBuilder add(Action action); + + KeyboardBuilder row(Action... buttons); + + KeyboardBuilder column(Action... buttons); + + Keyboard build(); +} \ No newline at end of file diff --git a/stbf-api/src/main/java/ru/penkrat/stbf/api/RequestMatcher.java b/stbf-api/src/main/java/ru/penkrat/stbf/api/RequestMatcher.java new file mode 100644 index 0000000..48da97d --- /dev/null +++ b/stbf-api/src/main/java/ru/penkrat/stbf/api/RequestMatcher.java @@ -0,0 +1,8 @@ +package ru.penkrat.stbf.api; + +@FunctionalInterface +public interface RequestMatcher { + + boolean match(BotRequest botRequest); + +} diff --git a/stbf-api/src/main/java/ru/penkrat/stbf/api/Screen.java b/stbf-api/src/main/java/ru/penkrat/stbf/api/Screen.java new file mode 100644 index 0000000..c4b7a33 --- /dev/null +++ b/stbf-api/src/main/java/ru/penkrat/stbf/api/Screen.java @@ -0,0 +1,14 @@ +package ru.penkrat.stbf.api; + +public interface Screen { + + String getText(); + + default Keyboard getKeyboard() { + return null; + } + + default ScreenProperties getScreenProperties() { + return ScreenProperties.DEFAULT; + } +} diff --git a/stbf-api/src/main/java/ru/penkrat/stbf/api/ScreenProperties.java b/stbf-api/src/main/java/ru/penkrat/stbf/api/ScreenProperties.java new file mode 100644 index 0000000..cfd7fda --- /dev/null +++ b/stbf-api/src/main/java/ru/penkrat/stbf/api/ScreenProperties.java @@ -0,0 +1,23 @@ +package ru.penkrat.stbf.api; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Getter +@Setter +@Accessors(chain = true) +public class ScreenProperties { + + public static final ScreenProperties DEFAULT = new ScreenProperties() + .setDisableWebPagePreview(true) + .setDisableNotification(true) + .setParseModeHtml(true); + + private boolean disableWebPagePreview; + + private boolean disableNotification; + + private boolean parseModeHtml; + +} diff --git a/stbf-api/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder b/stbf-api/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder new file mode 100644 index 0000000..5a8c114 --- /dev/null +++ b/stbf-api/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder @@ -0,0 +1 @@ +ru.penkrat.stbf.impl.pengrad.PengradKeyboardBuilder \ No newline at end of file diff --git a/stbf-common/pom.xml b/stbf-common/pom.xml new file mode 100644 index 0000000..3491cc9 --- /dev/null +++ b/stbf-common/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + stbf-parent + ru.penkrat.stbf + 0.0.1-SNAPSHOT + + + stbf-common + stbf-common + + + + ru.penkrat.stbf + stbf-api + 0.0.1-SNAPSHOT + + + junit + junit + 4.11 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + diff --git a/stbf-common/src/main/java/ru/penkrat/stbf/common/command/AbstractActionCommand.java b/stbf-common/src/main/java/ru/penkrat/stbf/common/command/AbstractActionCommand.java new file mode 100644 index 0000000..478b01e --- /dev/null +++ b/stbf-common/src/main/java/ru/penkrat/stbf/common/command/AbstractActionCommand.java @@ -0,0 +1,29 @@ +package ru.penkrat.stbf.common.command; + +import ru.penkrat.stbf.api.Action; +import ru.penkrat.stbf.api.BotRequest; +import ru.penkrat.stbf.api.BotResponse; +import ru.penkrat.stbf.api.Command; +import ru.penkrat.stbf.api.CommandChain; +import ru.penkrat.stbf.api.RequestMatcher; +import ru.penkrat.stbf.tools.RequestMatchers; + +public abstract class AbstractActionCommand implements Command { + + private RequestMatcher matcher; + + public AbstractActionCommand(Action action) { + matcher = RequestMatchers.action(action); + } + + @Override + public void process(BotRequest botRequest, BotResponse botResponse, CommandChain chain) { + if (matcher.match(botRequest)) { + doProcess(botRequest, botResponse); + } + + chain.processCommand(botRequest, botResponse); + } + + protected abstract void doProcess(BotRequest botRequest, BotResponse botResponse); +} diff --git a/stbf-common/src/main/java/ru/penkrat/stbf/common/command/SimpleCommand.java b/stbf-common/src/main/java/ru/penkrat/stbf/common/command/SimpleCommand.java new file mode 100644 index 0000000..2027510 --- /dev/null +++ b/stbf-common/src/main/java/ru/penkrat/stbf/common/command/SimpleCommand.java @@ -0,0 +1,36 @@ +package ru.penkrat.stbf.common.command; + +import ru.penkrat.stbf.api.Action; +import ru.penkrat.stbf.api.BotRequest; +import ru.penkrat.stbf.api.BotResponse; +import ru.penkrat.stbf.api.Command; +import ru.penkrat.stbf.api.CommandChain; +import ru.penkrat.stbf.api.RequestMatcher; +import ru.penkrat.stbf.api.Screen; +import ru.penkrat.stbf.common.screen.TextScreen; +import ru.penkrat.stbf.tools.RequestMatchers; + +public class SimpleCommand implements Command { + + private final RequestMatcher matcher; + private final Screen screen; + + public SimpleCommand(String input, String output) { + this(Action.simple(input), new TextScreen(output)); + } + + public SimpleCommand(Action action, Screen screen) { + this.screen = screen; + matcher = RequestMatchers.action(action); + } + + @Override + public void process(BotRequest botRequest, BotResponse botResponse, CommandChain chain) { + if (matcher.match(botRequest)) { + botResponse.send(screen); + } + + chain.processCommand(botRequest, botResponse); + } + +} diff --git a/stbf-common/src/main/java/ru/penkrat/stbf/common/screen/TextScreen.java b/stbf-common/src/main/java/ru/penkrat/stbf/common/screen/TextScreen.java new file mode 100644 index 0000000..1bd6d1c --- /dev/null +++ b/stbf-common/src/main/java/ru/penkrat/stbf/common/screen/TextScreen.java @@ -0,0 +1,34 @@ +package ru.penkrat.stbf.common.screen; + +import lombok.Getter; +import ru.penkrat.stbf.api.Action; +import ru.penkrat.stbf.api.Keyboard; +import ru.penkrat.stbf.api.Screen; +import ru.penkrat.stbf.api.KeyboardBuilder; + +@Getter +public class TextScreen implements Screen { + + private final String text; + + private final Keyboard keyboard; + + public TextScreen(String text) { + this.text = text; + this.keyboard = null; + } + + public TextScreen(String text, Keyboard keyboard) { + this.text = text; + this.keyboard = keyboard; + } + + public TextScreen(String text, Action btn) { + this(text, KeyboardBuilder.singleKey(btn)); + } + + public TextScreen(String text, Action btn1, Action btn2) { + this(text, KeyboardBuilder.newKeyboard().add(btn1).add(btn2).build()); + } + +} diff --git a/stbf-common/src/main/java/ru/penkrat/stbf/tools/RequestMatchers.java b/stbf-common/src/main/java/ru/penkrat/stbf/tools/RequestMatchers.java new file mode 100644 index 0000000..8f70086 --- /dev/null +++ b/stbf-common/src/main/java/ru/penkrat/stbf/tools/RequestMatchers.java @@ -0,0 +1,20 @@ +package ru.penkrat.stbf.tools; + +import lombok.experimental.UtilityClass; +import ru.penkrat.stbf.api.Action; +import ru.penkrat.stbf.api.RequestMatcher; + +@UtilityClass +public class RequestMatchers { + + public RequestMatcher action(Action action) { + return request -> request.getMessageText() + .filter(text -> matchText(action, text)) + .isPresent(); + } + + private static boolean matchText(Action action, String inputText) { + return inputText.equalsIgnoreCase(action.getText()) + || (action.getCmd() != null && inputText.equalsIgnoreCase(action.getCmd())); + } +} diff --git a/stbf-common/src/test/java/ru/penkrat/stbf/AppTest.java b/stbf-common/src/test/java/ru/penkrat/stbf/AppTest.java new file mode 100644 index 0000000..804dd7a --- /dev/null +++ b/stbf-common/src/test/java/ru/penkrat/stbf/AppTest.java @@ -0,0 +1,20 @@ +package ru.penkrat.stbf; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest +{ + /** + * Rigorous Test :-) + */ + @Test + public void shouldAnswerWithTrue() + { + assertTrue( true ); + } +} diff --git a/stbf-pengrad/pom.xml b/stbf-pengrad/pom.xml new file mode 100644 index 0000000..adc79b4 --- /dev/null +++ b/stbf-pengrad/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + ru.penkrat.stbf + stbf-parent + 0.0.1-SNAPSHOT + + + stbf-pengrad + stbf-pengrad + Simple Telegram Bot Facade with com.github.pengrad implementation + + + + ru.penkrat.stbf + stbf-api + 0.0.1-SNAPSHOT + + + com.github.pengrad + java-telegram-bot-api + [4.9.0,5.2.0] + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + diff --git a/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/BotRequestImpl.java b/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/BotRequestImpl.java new file mode 100644 index 0000000..a60df3b --- /dev/null +++ b/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/BotRequestImpl.java @@ -0,0 +1,61 @@ +package ru.penkrat.stbf.impl.pengrad; + +import static lombok.AccessLevel.PROTECTED; + +import java.util.Optional; + +import com.pengrad.telegrambot.model.CallbackQuery; +import com.pengrad.telegrambot.model.Contact; +import com.pengrad.telegrambot.model.Message; +import com.pengrad.telegrambot.model.Update; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import ru.penkrat.stbf.api.BotRequest; + +@RequiredArgsConstructor +public class BotRequestImpl implements BotRequest { + + @Getter(PROTECTED) + private final Update update; + + @Override + public Optional getMessageText() { + if (update.message() != null) { + return Optional.ofNullable(update.message().text()); + } + return Optional.empty(); + } + + @Override + public Optional getPhoneNumber() { + return Optional.of(update) + .map(Update::message) + .map(Message::contact) + .map(Contact::phoneNumber); + } + + @Override + public Optional getCallbackData() { + return Optional.of(update) + .map(Update::callbackQuery) + .map(CallbackQuery::data); + } + + @Override + public Optional getCallbackMessageText() { + return Optional.of(update) + .map(Update::callbackQuery) + .map(CallbackQuery::message) + .map(Message::text); + } + + @Override + public Long getChatId() { + return Optional.of(update) + .map(Update::callbackQuery) + .map(CallbackQuery::message) + .orElseGet(() -> update.message()).chat().id(); + } + +} diff --git a/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/BotResponseImpl.java b/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/BotResponseImpl.java new file mode 100644 index 0000000..4799156 --- /dev/null +++ b/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/BotResponseImpl.java @@ -0,0 +1,151 @@ +package ru.penkrat.stbf.impl.pengrad; + +import java.lang.reflect.Field; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.google.gson.internal.reflect.ReflectionAccessor; +import com.pengrad.telegrambot.TelegramBot; +import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.model.request.InlineKeyboardButton; +import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup; +import com.pengrad.telegrambot.model.request.KeyboardButton; +import com.pengrad.telegrambot.model.request.ParseMode; +import com.pengrad.telegrambot.model.request.ReplyKeyboardMarkup; +import com.pengrad.telegrambot.request.DeleteMessage; +import com.pengrad.telegrambot.request.EditMessageText; +import com.pengrad.telegrambot.request.SendDocument; +import com.pengrad.telegrambot.request.SendMessage; + +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import ru.penkrat.stbf.api.BotResponse; +import ru.penkrat.stbf.api.Screen; + +@Slf4j +@RequiredArgsConstructor +public class BotResponseImpl implements BotResponse { + + private final Update update; + + private final TelegramBot telegramBot; + + @Override + public void send(Screen screen) { + log.debug("Send message: \n============\n{}\n============", screen.getText().trim()); + + SendMessage sendMessage = new SendMessage(chatId(), screen.getText().trim()) + .parseMode(screen.getScreenProperties().isParseModeHtml() ? ParseMode.HTML : ParseMode.MarkdownV2) + .disableWebPagePreview(screen.getScreenProperties().isDisableWebPagePreview()) + .disableNotification(screen.getScreenProperties().isDisableNotification()); + + if (screen.getKeyboard() instanceof KeyboardImpl) { + KeyboardImpl kk = (KeyboardImpl) screen.getKeyboard(); + KeyboardButton[][] keyboard = kk.getKeyboard(); + InlineKeyboardButton[][] inlineKeyboard = kk.getInlineKeyboard(); + + if (inlineKeyboard != null && inlineKeyboard.length > 0) { + logKeyboard(inlineKeyboard); + sendMessage = sendMessage.replyMarkup(new InlineKeyboardMarkup(inlineKeyboard)); + } else if (keyboard != null && keyboard.length > 0) { + logKeyboard(keyboard); + sendMessage = sendMessage.replyMarkup(new ReplyKeyboardMarkup(keyboard)); + } else { + log.debug("No keyboard"); + } + } + + telegramBot.execute(sendMessage); + } + + @Override + public void sendFile(String filename, byte[] data) { + telegramBot.execute(new SendDocument(chatId(), data).fileName(filename)); + } + + @Override + @Deprecated + public void editMessage(String text) { + telegramBot.execute(new EditMessageText(chatId(), messageId(), text) + .parseMode(ParseMode.HTML) + .disableWebPagePreview(true)); + } + + @Override + public void edit(Screen screen) { + EditMessageText editMessage = new EditMessageText(chatId(), messageId(), screen.getText()) + .parseMode(screen.getScreenProperties().isParseModeHtml() ? ParseMode.HTML : ParseMode.MarkdownV2) + .disableWebPagePreview(screen.getScreenProperties().isDisableWebPagePreview()); + + if (screen.getKeyboard() instanceof KeyboardImpl) { + KeyboardImpl kk = (KeyboardImpl) screen.getKeyboard(); + InlineKeyboardButton[][] inlineKeyboard = kk.getInlineKeyboard(); + + if (inlineKeyboard != null && inlineKeyboard.length > 0) { + logKeyboard(inlineKeyboard); + editMessage = editMessage.replyMarkup(new InlineKeyboardMarkup(inlineKeyboard)); + } else { + log.debug("No keyboard"); + } + } + + telegramBot.execute(editMessage); + } + + @Override + public void deleteMessage() { + telegramBot.execute(new DeleteMessage(chatId(), messageId())); + } + + private Long chatId() { + if (update.callbackQuery() != null) { + return update.callbackQuery().message().chat().id(); + } + return update.message().chat().id(); + } + + private Integer messageId() { + if (update.callbackQuery() != null) { + return update.callbackQuery().message().messageId(); + } + return update.message().messageId(); + } + + private void logKeyboard(KeyboardButton[][] keyboard) { + if (log.isDebugEnabled()) { + for (int i = 0; i < keyboard.length; i++) { + if (keyboard[i].length > 0) { + String row = Stream.of(keyboard[i]).map(this::getText).collect(Collectors.joining(" | ")); + log.debug("Keyboard: {}", row); + } + } + } + } + + private void logKeyboard(InlineKeyboardButton[][] keyboard) { + if (log.isDebugEnabled()) { + for (int i = 0; i < keyboard.length; i++) { + if (keyboard[i].length > 0) { + String row = Stream.of(keyboard[i]).map(this::getText).collect(Collectors.joining(" | ")); + log.debug("Inline keyboard: {}", row); + } + } + } + } + + @SneakyThrows + private String getText(KeyboardButton btn) { + Field text = KeyboardButton.class.getDeclaredField("text"); + ReflectionAccessor.getInstance().makeAccessible(text); + return String.valueOf(text.get(btn)); + } + + @SneakyThrows + private String getText(InlineKeyboardButton btn) { + Field text = InlineKeyboardButton.class.getDeclaredField("text"); + ReflectionAccessor.getInstance().makeAccessible(text); + return String.valueOf(text.get(btn)); + } + +} diff --git a/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/KeyboardImpl.java b/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/KeyboardImpl.java new file mode 100644 index 0000000..8f34976 --- /dev/null +++ b/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/KeyboardImpl.java @@ -0,0 +1,16 @@ +package ru.penkrat.stbf.impl.pengrad; + +import com.pengrad.telegrambot.model.request.InlineKeyboardButton; +import com.pengrad.telegrambot.model.request.KeyboardButton; + +import lombok.Value; +import ru.penkrat.stbf.api.Keyboard; + +@Value +class KeyboardImpl implements Keyboard { + + private KeyboardButton[][] keyboard; + + private InlineKeyboardButton[][] inlineKeyboard; + +} diff --git a/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/PengradKeyboardBuilder.java b/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/PengradKeyboardBuilder.java new file mode 100644 index 0000000..2974b84 --- /dev/null +++ b/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/PengradKeyboardBuilder.java @@ -0,0 +1,149 @@ +package ru.penkrat.stbf.impl.pengrad; + +import com.pengrad.telegrambot.model.request.InlineKeyboardButton; +import com.pengrad.telegrambot.model.request.KeyboardButton; + +import ru.penkrat.stbf.api.Action; +import ru.penkrat.stbf.api.Keyboard; +import ru.penkrat.stbf.api.KeyboardBuilder; + +public class PengradKeyboardBuilder implements KeyboardBuilder { + + private KeyboardButton[][] keyboard; + + private InlineKeyboardButton[][] inlineKeyboard; + + public static Keyboard singleKey(Action action) { + return KeyboardBuilder.newKeyboard().add(action).build(); + } + + @Override + public Keyboard build() { + return new KeyboardImpl(keyboard, inlineKeyboard); + } + + public KeyboardBuilder addGetContact(String text) { + put(new KeyboardButton(text).requestContact(true)); + return self(); + } + + public KeyboardBuilder add(String text) { + put(new KeyboardButton(text)); + return self(); + } + + @Override + public KeyboardBuilder add(Action action) { + put(action); + return self(); + } + + public KeyboardBuilder row(KeyboardButton... buttons) { + for (int i = 0; i < buttons.length; i++) { + put(buttons[i]); + } + nextRow(); + return self(); + } + + @Override + public KeyboardBuilder row(Action... buttons) { + for (int i = 0; i < buttons.length; i++) { + put(buttons[i]); + } + nextRow(); + return self(); + } + + @Override + public KeyboardBuilder column(Action... buttons) { + for (int i = 0; i < buttons.length; i++) { + put(buttons[i]); + if (buttons[i].isInline()) { + nextRowI(); + } else { + nextRow(); + } + } + return self(); + } + + public KeyboardBuilder addNl(String text) { + add(text); + nextRow(); + return self(); + } + + public PengradKeyboardBuilder add(String text, String data) { + put(new InlineKeyboardButton(text).callbackData(data)); + return self(); + } + + public KeyboardBuilder addNl(String text, String data) { + add(text, data); + nextRowI(); + return self(); + } + + private void nextRow() { + KeyboardButton[][] n = new KeyboardButton[keyboard.length + 1][]; + System.arraycopy(keyboard, 0, n, 0, keyboard.length); + keyboard = n; + keyboard[keyboard.length - 1] = new KeyboardButton[] {}; + } + + private void nextRowI() { + InlineKeyboardButton[][] n = new InlineKeyboardButton[inlineKeyboard.length + 1][]; + System.arraycopy(inlineKeyboard, 0, n, 0, inlineKeyboard.length); + inlineKeyboard = n; + inlineKeyboard[inlineKeyboard.length - 1] = new InlineKeyboardButton[] {}; + } + + protected PengradKeyboardBuilder self() { + return this; + } + + private void put(Action action) { + if (action.isInline()) { + put(new InlineKeyboardButton(action.getText()).callbackData(action.getCallbackData())); + } else { + put(new KeyboardButton(action.getText()).requestContact(action.isRequestContact())); + } + } + + private void put(KeyboardButton btn) { + if (keyboard == null) { + keyboard = new KeyboardButton[][] { + new KeyboardButton[] { btn } + }; + } else { + KeyboardButton[] k = keyboard[keyboard.length - 1]; + KeyboardButton[] n = new KeyboardButton[k.length + 1]; + System.arraycopy(k, 0, n, 0, k.length); + n[k.length] = btn; + keyboard[keyboard.length - 1] = n; + } + + } + + private void put(InlineKeyboardButton btn) { + if (inlineKeyboard == null) { + inlineKeyboard = new InlineKeyboardButton[][] { + new InlineKeyboardButton[] { btn } + }; + } else { + InlineKeyboardButton[] k = inlineKeyboard[inlineKeyboard.length - 1]; + InlineKeyboardButton[] n = new InlineKeyboardButton[k.length + 1]; + System.arraycopy(k, 0, n, 0, k.length); + n[k.length] = btn; + inlineKeyboard[inlineKeyboard.length - 1] = n; + } + + } + + @Override + public KeyboardBuilder newInstance() { + return new PengradKeyboardBuilder(); + } + +} diff --git a/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/PengradTelegramBot.java b/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/PengradTelegramBot.java new file mode 100644 index 0000000..3da7fec --- /dev/null +++ b/stbf-pengrad/src/main/java/ru/penkrat/stbf/impl/pengrad/PengradTelegramBot.java @@ -0,0 +1,35 @@ +package ru.penkrat.stbf.impl.pengrad; + +import com.pengrad.telegrambot.TelegramBot; +import com.pengrad.telegrambot.UpdatesListener; +import com.pengrad.telegrambot.model.Update; + +import lombok.extern.slf4j.Slf4j; +import ru.penkrat.stbf.api.CommandChain; + +@Slf4j +public class PengradTelegramBot extends TelegramBot implements AutoCloseable { + + public PengradTelegramBot(String botToken, CommandChain commandChain) { + super(botToken); + this.setUpdatesListener(updates -> { + for (Update update : updates) { + try { + commandChain.processCommand( + new BotRequestImpl(update), + new BotResponseImpl(update, this)); + } catch (Exception e) { + log.error("Bot Error:", e); + } + } + return UpdatesListener.CONFIRMED_UPDATES_ALL; + }); + } + + @Override + public void close() throws Exception { + removeGetUpdatesListener(); + log.debug("Bot closed."); + } + +} diff --git a/stbf-pengrad/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder b/stbf-pengrad/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder new file mode 100644 index 0000000..5a8c114 --- /dev/null +++ b/stbf-pengrad/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder @@ -0,0 +1 @@ +ru.penkrat.stbf.impl.pengrad.PengradKeyboardBuilder \ No newline at end of file diff --git a/stbf-rubenlagus/pom.xml b/stbf-rubenlagus/pom.xml new file mode 100644 index 0000000..e03ec38 --- /dev/null +++ b/stbf-rubenlagus/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + stbf-parent + ru.penkrat.stbf + 0.0.1-SNAPSHOT + + + stbf-rubenlagus + stbf-rubenlagus + Simple Telegram Bot Facade with https://github.com/rubenlagus/TelegramBots implementation + + + + ru.penkrat.stbf + stbf-api + 0.0.1-SNAPSHOT + + + org.telegram + telegrambots + 5.3.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + diff --git a/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/BotRequestImpl.java b/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/BotRequestImpl.java new file mode 100644 index 0000000..2cbc2b0 --- /dev/null +++ b/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/BotRequestImpl.java @@ -0,0 +1,58 @@ +package ru.penkrat.stbf.impl.rubenlagus; + +import static lombok.AccessLevel.PROTECTED; + +import java.util.Optional; + +import org.telegram.telegrambots.meta.api.objects.CallbackQuery; +import org.telegram.telegrambots.meta.api.objects.Contact; +import org.telegram.telegrambots.meta.api.objects.Message; +import org.telegram.telegrambots.meta.api.objects.Update; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import ru.penkrat.stbf.api.BotRequest; + +@RequiredArgsConstructor +class BotRequestImpl implements BotRequest { + + @Getter(PROTECTED) + private final Update update; + + @Override + public Optional getMessageText() { + if (update.hasMessage()) { + return Optional.ofNullable(update.getMessage().getText()); + } + return Optional.empty(); + } + + @Override + public Optional getPhoneNumber() { + return Optional.of(update) + .map(Update::getMessage) + .map(Message::getContact) + .map(Contact::getPhoneNumber); + } + + @Override + public Optional getCallbackData() { + return Optional.of(update) + .map(Update::getCallbackQuery) + .map(CallbackQuery::getData); + } + + @Override + public Optional getCallbackMessageText() { + return Optional.of(update) + .map(Update::getCallbackQuery) + .map(CallbackQuery::getMessage) + .map(Message::getText); + } + + @Override + public Long getChatId() { + return update.getMessage().getChatId(); + } + +} diff --git a/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/BotResponseImpl.java b/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/BotResponseImpl.java new file mode 100644 index 0000000..1bd3f4d --- /dev/null +++ b/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/BotResponseImpl.java @@ -0,0 +1,113 @@ +package ru.penkrat.stbf.impl.rubenlagus; + +import java.util.List; + +import org.telegram.telegrambots.bots.TelegramLongPollingBot; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessage; +import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageText; +import org.telegram.telegrambots.meta.api.objects.Update; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.ReplyKeyboardMarkup; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.KeyboardRow; +import org.telegram.telegrambots.meta.exceptions.TelegramApiException; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import ru.penkrat.stbf.api.BotResponse; +import ru.penkrat.stbf.api.Screen; + +@Slf4j +@RequiredArgsConstructor +class BotResponseImpl implements BotResponse { + + private final Update update; + + private final TelegramLongPollingBot bot; + + @Override + public void send(Screen screen) { + SendMessage send = new SendMessage(chatId(), screen.getText()); + + if (screen.getKeyboard() instanceof KeyboardImpl) { + KeyboardImpl kk = (KeyboardImpl) screen.getKeyboard(); + List keyboard = kk.getKeyboard(); + List> inlineKeyboard = kk.getInlineKeyboard(); + + if (inlineKeyboard != null && !inlineKeyboard.isEmpty()) { + send.setReplyMarkup(new InlineKeyboardMarkup(inlineKeyboard)); + } else if (keyboard != null && !keyboard.isEmpty()) { + send.setReplyMarkup(new ReplyKeyboardMarkup(keyboard)); + } else { + log.debug("No keyboard"); + } + } + + try { + bot.execute(send); + } catch (TelegramApiException e) { + log.error("Send error", e); + } + } + + @Override + public void sendFile(String filename, byte[] data) { + throw new IllegalStateException("Not inplemented"); + } + + @Override + public void editMessage(String text) { + EditMessageText send = new EditMessageText(text); + send.setChatId(chatId()); + send.setMessageId(messageId()); + if (update.hasCallbackQuery()) { + send.setInlineMessageId(update.getCallbackQuery().getInlineMessageId()); + } + try { + bot.execute(send); + } catch (TelegramApiException e) { + log.error("Send error", e); + } + } + + @Override + public void deleteMessage() { + DeleteMessage send = new DeleteMessage(chatId(), messageId()); + try { + bot.execute(send); + } catch (TelegramApiException e) { + log.error("Send error", e); + } + } + + @Override + public void edit(Screen screen) { + EditMessageText send = new EditMessageText(screen.getText()); + send.setChatId(chatId()); + send.setMessageId(messageId()); + if (update.hasCallbackQuery()) { + send.setInlineMessageId(update.getCallbackQuery().getInlineMessageId()); + } + try { + bot.execute(send); + } catch (TelegramApiException e) { + log.error("Send error", e); + } + } + + private String chatId() { + if (update.hasCallbackQuery()) { + return Long.toString(update.getCallbackQuery().getMessage().getChatId()); + } + return Long.toString(update.getMessage().getChatId()); + } + + private Integer messageId() { + if (update.hasCallbackQuery()) { + return Integer.parseInt(update.getCallbackQuery().getInlineMessageId()); + } + return update.getMessage().getMessageId(); + } + +} diff --git a/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/KeyboardImpl.java b/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/KeyboardImpl.java new file mode 100644 index 0000000..3d1a5a7 --- /dev/null +++ b/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/KeyboardImpl.java @@ -0,0 +1,18 @@ +package ru.penkrat.stbf.impl.rubenlagus; + +import java.util.List; + +import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.KeyboardRow; + +import lombok.Value; +import ru.penkrat.stbf.api.Keyboard; + +@Value +class KeyboardImpl implements Keyboard { + + private List keyboard; + + private List> inlineKeyboard; + +} diff --git a/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/RubenlagusKeyboardBuilder.java b/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/RubenlagusKeyboardBuilder.java new file mode 100644 index 0000000..72cbc20 --- /dev/null +++ b/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/RubenlagusKeyboardBuilder.java @@ -0,0 +1,150 @@ +package ru.penkrat.stbf.impl.rubenlagus; + +import java.util.ArrayList; +import java.util.List; + +import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.KeyboardButton; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.KeyboardRow; + +import ru.penkrat.stbf.api.Action; +import ru.penkrat.stbf.api.Keyboard; +import ru.penkrat.stbf.api.KeyboardBuilder; + +public class RubenlagusKeyboardBuilder implements KeyboardBuilder { + + private List keyboard; + + private List> inlineKeyboard; + + public static Keyboard singleKey(Action action) { + return KeyboardBuilder.newKeyboard().add(action).build(); + } + + @Override + public Keyboard build() { + return new KeyboardImpl(keyboard, inlineKeyboard); + } + + public KeyboardBuilder addGetContact(String text) { + KeyboardButton keyboardButton = new KeyboardButton(text); + keyboardButton.setRequestContact(true); + put(keyboardButton); + return self(); + } + + public KeyboardBuilder add(String text) { + put(new KeyboardButton(text)); + return self(); + } + + @Override + public KeyboardBuilder add(Action action) { + put(action); + return self(); + } + + public KeyboardBuilder row(KeyboardButton... buttons) { + for (int i = 0; i < buttons.length; i++) { + put(buttons[i]); + } + nextRow(); + return self(); + } + + @Override + public KeyboardBuilder row(Action... buttons) { + for (int i = 0; i < buttons.length; i++) { + put(buttons[i]); + } + nextRow(); + return self(); + } + + @Override + public KeyboardBuilder column(Action... buttons) { + for (int i = 0; i < buttons.length; i++) { + put(buttons[i]); + if (buttons[i].isInline()) { + nextRowI(); + } else { + nextRow(); + } + } + return self(); + } + + public KeyboardBuilder addNl(String text) { + add(text); + nextRow(); + return self(); + } + + public RubenlagusKeyboardBuilder add(String text, String data) { + InlineKeyboardButton inlineKeyboardButton = new InlineKeyboardButton(text); + inlineKeyboardButton.setCallbackData(data); + put(inlineKeyboardButton); + return self(); + } + + public KeyboardBuilder addNl(String text, String data) { + add(text, data); + nextRowI(); + return self(); + } + + private void nextRow() { + KeyboardRow row = new KeyboardRow(); + keyboard.add(row); + } + + private void nextRowI() { + List row = new ArrayList<>(); + inlineKeyboard.add(row); + } + + protected RubenlagusKeyboardBuilder self() { + return this; + } + + private void put(Action action) { + if (action.isInline()) { + InlineKeyboardButton inlineKeyboardButton = new InlineKeyboardButton(action.getText()); + inlineKeyboardButton.setCallbackData(action.getCallbackData()); + put(inlineKeyboardButton); + } else { + KeyboardButton keyboardButton = new KeyboardButton(action.getText()); + keyboardButton.setRequestContact(action.isRequestContact()); + put(keyboardButton); + } + } + + private void put(KeyboardButton btn) { + if (keyboard == null) { + keyboard = new ArrayList(); + KeyboardRow row = new KeyboardRow(); + keyboard.add(row); + row.add(btn); + } else { + keyboard.get(keyboard.size() - 1).add(btn); + } + + } + + private void put(InlineKeyboardButton btn) { + if (inlineKeyboard == null) { + inlineKeyboard = new ArrayList>(); + List row = new ArrayList<>(); + inlineKeyboard.add(row); + row.add(btn); + } else { + inlineKeyboard.get(inlineKeyboard.size() - 1).add(btn); + } + } + + @Override + public KeyboardBuilder newInstance() { + return new RubenlagusKeyboardBuilder(); + } + +} diff --git a/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/RubenlagusTelegramBot.java b/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/RubenlagusTelegramBot.java new file mode 100644 index 0000000..4e86c75 --- /dev/null +++ b/stbf-rubenlagus/src/main/java/ru/penkrat/stbf/impl/rubenlagus/RubenlagusTelegramBot.java @@ -0,0 +1,54 @@ +package ru.penkrat.stbf.impl.rubenlagus; + +import org.telegram.telegrambots.bots.TelegramLongPollingBot; +import org.telegram.telegrambots.meta.TelegramBotsApi; +import org.telegram.telegrambots.meta.api.objects.Update; +import org.telegram.telegrambots.meta.exceptions.TelegramApiException; +import org.telegram.telegrambots.updatesreceivers.DefaultBotSession; + +import lombok.extern.slf4j.Slf4j; +import ru.penkrat.stbf.api.BotCommandChain; + +@Slf4j +public class RubenlagusTelegramBot extends TelegramLongPollingBot { + + private final String botUsername; + private final String botToken; + private final BotCommandChain commandChain; + + public RubenlagusTelegramBot(String botUsername, String botToken, BotCommandChain botCommandChain) { + this.botUsername = botUsername; + this.botToken = botToken; + this.commandChain = botCommandChain; + + TelegramBotsApi telegramBotsApi; + try { + telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class); + telegramBotsApi.registerBot(this); + } catch (TelegramApiException e) { + log.error("Error", e); + } + } + + @Override + public void onUpdateReceived(Update update) { + try { + commandChain.processCommand( + new BotRequestImpl(update), + new BotResponseImpl(update, this)); + } catch (Exception e) { + log.error("Bot Error:", e); + } + } + + @Override + public String getBotUsername() { + return botUsername; + } + + @Override + public String getBotToken() { + return botToken; + } + +} diff --git a/stbf-rubenlagus/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder b/stbf-rubenlagus/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder new file mode 100644 index 0000000..8e1e0cf --- /dev/null +++ b/stbf-rubenlagus/src/main/resources/META-INF/services/ru.penkrat.stbf.api.KeyboardBuilder @@ -0,0 +1 @@ +ru.penkrat.stbf.impl.rubenlagus.RubenlagusKeyboardBuilder \ No newline at end of file diff --git a/stbf-test/pom.xml b/stbf-test/pom.xml new file mode 100644 index 0000000..6875346 --- /dev/null +++ b/stbf-test/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + stbf-parent + ru.penkrat.stbf + 0.0.1-SNAPSHOT + + + stbf-test + stbf-test + + + + ru.penkrat.stbf + stbf-api + 0.0.1-SNAPSHOT + + + ru.penkrat.stbf + stbf-common + 0.0.1-SNAPSHOT + test + + + junit + junit + 4.11 + test + + + org.hamcrest + hamcrest + 2.2 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + diff --git a/stbf-test/src/main/java/ru/penkrat/stbf/test/BotRequestImpl.java b/stbf-test/src/main/java/ru/penkrat/stbf/test/BotRequestImpl.java new file mode 100644 index 0000000..10ced29 --- /dev/null +++ b/stbf-test/src/main/java/ru/penkrat/stbf/test/BotRequestImpl.java @@ -0,0 +1,42 @@ +package ru.penkrat.stbf.test; + +import java.util.Optional; +import java.util.Random; + +import lombok.Builder; +import lombok.Getter; +import ru.penkrat.stbf.api.BotRequest; + +@Builder +class BotRequestImpl implements BotRequest { + + private String messageText; + private String phoneNumber; + private String callbackData; + private String callbackMessageText; + + @Getter + @Builder.Default + private Long chatId = new Random().nextLong(); + + @Override + public Optional getMessageText() { + return Optional.ofNullable(messageText); + } + + @Override + public Optional getPhoneNumber() { + return Optional.ofNullable(phoneNumber); + } + + @Override + public Optional getCallbackData() { + return Optional.ofNullable(callbackData); + } + + @Override + public Optional getCallbackMessageText() { + return Optional.ofNullable(callbackMessageText); + } + +} diff --git a/stbf-test/src/main/java/ru/penkrat/stbf/test/BotResponseImpl.java b/stbf-test/src/main/java/ru/penkrat/stbf/test/BotResponseImpl.java new file mode 100644 index 0000000..c636df2 --- /dev/null +++ b/stbf-test/src/main/java/ru/penkrat/stbf/test/BotResponseImpl.java @@ -0,0 +1,43 @@ +package ru.penkrat.stbf.test; + +import ru.penkrat.stbf.api.BotResponse; +import ru.penkrat.stbf.api.Screen; + +class BotResponseImpl implements BotResponse { + + private Result result; + + public BotResponseImpl(Result result) { + this.result = result; + } + + @Override + public void send(Screen screen) { + result.addScreen(screen); + } + + @Override + public void sendFile(String filename, byte[] data) { + // TODO Auto-generated method stub + + } + + @Override + public void editMessage(String text) { + // TODO Auto-generated method stub + + } + + @Override + public void deleteMessage() { + // TODO Auto-generated method stub + + } + + @Override + public void edit(Screen screen) { + // TODO Auto-generated method stub + + } + +} diff --git a/stbf-test/src/main/java/ru/penkrat/stbf/test/Result.java b/stbf-test/src/main/java/ru/penkrat/stbf/test/Result.java new file mode 100644 index 0000000..420bb3a --- /dev/null +++ b/stbf-test/src/main/java/ru/penkrat/stbf/test/Result.java @@ -0,0 +1,23 @@ +package ru.penkrat.stbf.test; + +import java.util.ArrayList; +import java.util.List; + +import ru.penkrat.stbf.api.Screen; + +public class Result { + + private final List screens = new ArrayList<>(); + + public String text() { + if (screens.isEmpty()) { + return null; + } + return screens.get(0).getText(); + } + + public void addScreen(Screen screen) { + screens.add(screen); + } + +} diff --git a/stbf-test/src/main/java/ru/penkrat/stbf/test/TestBot.java b/stbf-test/src/main/java/ru/penkrat/stbf/test/TestBot.java new file mode 100644 index 0000000..d537d28 --- /dev/null +++ b/stbf-test/src/main/java/ru/penkrat/stbf/test/TestBot.java @@ -0,0 +1,43 @@ +package ru.penkrat.stbf.test; + +import lombok.RequiredArgsConstructor; +import ru.penkrat.stbf.api.BotCommandChain; +import ru.penkrat.stbf.api.BotRequest; +import ru.penkrat.stbf.api.Command; +import ru.penkrat.stbf.api.CommandChain; +import ru.penkrat.stbf.test.BotRequestImpl; +import ru.penkrat.stbf.test.BotResponseImpl; + +@RequiredArgsConstructor +public class TestBot { + + public static TestBot from(Command... commands) { + BotCommandChain bcc = new BotCommandChain(); + for (int i = 0; i < commands.length; i++) { + bcc.add(commands[i]); + } + return new TestBot(bcc); + } + + private final CommandChain commandChain; + + public Result whenText(String input) { + return process(BotRequestImpl.builder().messageText(input).build()); + } + + public Result whenPhoneNumber(String input) { + return process(BotRequestImpl.builder().phoneNumber(input).build()); + } + + public Result whenCallback(String data) { + return process(BotRequestImpl.builder().callbackData(data).build()); + } + + private Result process(BotRequest botRequest) { + Result result = new Result(); + commandChain.processCommand(botRequest, new BotResponseImpl(result)); + + return result; + } + +} diff --git a/stbf-test/src/test/java/ru/penkrat/stbf/BotTest.java b/stbf-test/src/test/java/ru/penkrat/stbf/BotTest.java new file mode 100644 index 0000000..f17da5d --- /dev/null +++ b/stbf-test/src/test/java/ru/penkrat/stbf/BotTest.java @@ -0,0 +1,23 @@ +package ru.penkrat.stbf; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +import org.junit.Test; + +import ru.penkrat.stbf.api.Command; +import ru.penkrat.stbf.common.command.SimpleCommand; +import ru.penkrat.stbf.test.Result; +import ru.penkrat.stbf.test.TestBot; + +public class BotTest { + + private Command start = new SimpleCommand("/start", "Hello, world"); + + @Test + public void shouldAnswerWithHello() { + Result result = TestBot.from(start).whenText("/start"); + + assertThat(result.text(), containsString("Hello, world")); + } +}