From 421909d5ae484d2964e6f7f8c87f9763f2dd0036 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Tue, 28 Mar 2023 17:26:15 +0200 Subject: [PATCH 1/9] feat: add event dispacher, wip --- schema/json/CallbackQueryMetadata.json | 26 ++++++ .../orm/model/CallbackQueryContext.java | 41 +++++++++ .../polpetta/mezzotre/route/Telegram.java | 91 ++++++++++++++----- .../callbackquery/ChangeLanguage.java | 27 ++++++ .../telegram/callbackquery/Dispatcher.java | 43 +++++++++ .../EventProcessorNotFoundException.java | 22 +++++ .../telegram/callbackquery/Processor.java | 14 +++ .../callbackquery/di/CallbackQuery.java | 18 ++++ .../command/{Executor.java => Processor.java} | 6 +- .../mezzotre/telegram/command/Router.java | 28 +++--- .../mezzotre/telegram/command/Start.java | 6 +- .../mezzotre/telegram/command/di/Command.java | 6 +- .../template/command/{start.vm => start.0.vm} | 0 .../route/TelegramIntegrationTest.java | 63 +++++++++++-- .../mezzotre/telegram/command/RouterTest.java | 20 ++-- 15 files changed, 347 insertions(+), 64 deletions(-) create mode 100644 schema/json/CallbackQueryMetadata.json create mode 100644 src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java create mode 100644 src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java create mode 100644 src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java create mode 100644 src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/EventProcessorNotFoundException.java create mode 100644 src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Processor.java create mode 100644 src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/di/CallbackQuery.java rename src/main/java/com/github/polpetta/mezzotre/telegram/command/{Executor.java => Processor.java} (88%) rename src/main/resources/template/command/{start.vm => start.0.vm} (100%) diff --git a/schema/json/CallbackQueryMetadata.json b/schema/json/CallbackQueryMetadata.json new file mode 100644 index 0000000..3e4d651 --- /dev/null +++ b/schema/json/CallbackQueryMetadata.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "http://example.com/example.json", + "type": "object", + "default": {}, + "required": [ + "event", + "messageId", + "telegramChatId" + ], + "additionalProperties": true, + "properties": { + "event": { + "type": "string", + "default": "" + }, + "messageId": { + "type": "number", + "default": 0 + }, + "telegramChatId": { + "type": "number", + "default": 0 + } + } +} diff --git a/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java b/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java new file mode 100644 index 0000000..9ede71a --- /dev/null +++ b/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java @@ -0,0 +1,41 @@ +package com.github.polpetta.mezzotre.orm.model; + +import com.github.polpetta.types.json.CallbackQueryMetadata; +import io.ebean.annotation.DbJson; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; + +// Could be better to adopt redis? Seems too much overhead for now... + +/** + * This entity allows for the push and retrieval of {@link CallbackQueryMetadata} related to a + * particular InlineKeyboardButton. {@link CallbackQueryMetadata} is a loosely-structured + * object, and this is by design: the fields stored in it depends on the metadata of the callback + * and on the type of event + * + * @author Davide Polonio + * @since 1.0 + */ +@Entity +@Table(name = "callback_query_context") +public class CallbackQueryContext extends Base { + + @Id private final String id; + + @DbJson @NotNull private final CallbackQueryMetadata fields; + + public CallbackQueryContext(String id, CallbackQueryMetadata fields) { + this.id = id; + this.fields = fields; + } + + public String getId() { + return id; + } + + public CallbackQueryMetadata getFields() { + return fields; + } +} diff --git a/src/main/java/com/github/polpetta/mezzotre/route/Telegram.java b/src/main/java/com/github/polpetta/mezzotre/route/Telegram.java index 9fb87da..1b21b4f 100644 --- a/src/main/java/com/github/polpetta/mezzotre/route/Telegram.java +++ b/src/main/java/com/github/polpetta/mezzotre/route/Telegram.java @@ -1,10 +1,13 @@ package com.github.polpetta.mezzotre.route; import com.github.polpetta.mezzotre.orm.model.TgChat; +import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext; import com.github.polpetta.mezzotre.orm.model.query.QTgChat; +import com.github.polpetta.mezzotre.telegram.callbackquery.Dispatcher; import com.github.polpetta.mezzotre.telegram.command.Router; import com.github.polpetta.types.json.ChatContext; import com.google.gson.Gson; +import com.pengrad.telegrambot.model.CallbackQuery; import com.pengrad.telegrambot.model.Message; import com.pengrad.telegrambot.model.Update; import com.pengrad.telegrambot.request.BaseRequest; @@ -15,6 +18,7 @@ import io.jooby.annotations.Path; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -30,17 +34,20 @@ public class Telegram { private final Gson gson; private final Executor completableFutureThreadPool; private final Router router; + private final Dispatcher dispatcher; @Inject public Telegram( Logger log, Gson gson, @Named("eventThreadPool") Executor completableFutureThreadPool, - Router router) { + Router router, + Dispatcher dispatcher) { this.log = log; this.gson = gson; this.completableFutureThreadPool = completableFutureThreadPool; this.router = router; + this.dispatcher = dispatcher; } @Operation( @@ -51,33 +58,30 @@ public class Telegram { requestBody = @RequestBody(required = true)) @POST public CompletableFuture incomingUpdate(Context context, Update update) { - return CompletableFuture.supplyAsync( - () -> { + /* + Steps: + 1 - Retrieve the chat. If new chat, create and entry in the db + 2 - Check if the incoming payload is an inline event (keyboard, query, ecc). Possibly check if there is any context previously saved in the database to retrieve. In that case, process the payload accordingly + 3 - If it is not an inline event, then process it as an incoming message + */ + return CompletableFuture.completedFuture(update) + .thenComposeAsync( + ignored -> { context.setResponseType(MediaType.JSON); log.trace(gson.toJson(update)); - final Message message = update.message(); - return new QTgChat() - .id - .eq(message.chat().id()) - .findOneOrEmpty() - .map( - u -> { - log.debug( - "Telegram chat " + u.getId() + " already registered in the database"); - return u; - }) - .orElseGet( - () -> { - final TgChat newTgChat = new TgChat(message.chat().id(), new ChatContext()); - newTgChat.save(); - log.trace( - "New Telegram chat " + newTgChat.getId() + " added into the database"); - return newTgChat; - }); + if (update.message() != null) { + return processMessage(update); + } + + if (update.callbackQuery() != null) { + return processCallBackQuery(update); + } + + return CompletableFuture.failedFuture( + new IllegalArgumentException("The given update is not formatted correctly")); }, completableFutureThreadPool) - .thenComposeAsync(tgChat -> router.process(tgChat, update), completableFutureThreadPool) // See https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates .thenApply( tgResponse -> { @@ -86,4 +90,45 @@ public class Telegram { return response; }); } + + private CompletableFuture>> processMessage(Update update) { + final Message message = update.message(); + final TgChat tgChat = + new QTgChat() + .id + .eq(message.chat().id()) + .findOneOrEmpty() + .map( + u -> { + log.debug("Telegram chat " + u.getId() + " already registered in the database"); + return u; + }) + .orElseGet( + () -> { + final TgChat newTgChat = new TgChat(message.chat().id(), new ChatContext()); + newTgChat.save(); + log.trace("New Telegram chat " + newTgChat.getId() + " added into the database"); + return newTgChat; + }); + + return router.process(tgChat, update); + } + + private CompletableFuture>> processCallBackQuery(Update update) { + final CallbackQuery callbackQuery = update.callbackQuery(); + return new QCallbackQueryContext() + .id + .eq(callbackQuery.data()) + .findOneOrEmpty() + .map( + c -> { + log.debug("CallbackQuery " + c.getId() + " find in the database"); + + return dispatcher.dispatch(c, update); + }) + .orElse( + CompletableFuture.failedFuture( + new IllegalStateException( + "No such callback query in our database " + callbackQuery.data()))); + } } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java new file mode 100644 index 0000000..8f599fe --- /dev/null +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java @@ -0,0 +1,27 @@ +package com.github.polpetta.mezzotre.telegram.callbackquery; + +import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; +import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.request.BaseRequest; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +public class ChangeLanguage implements Processor { + + @Inject + public ChangeLanguage() {} + + @Override + public String getEventName() { + return "changeLanguage"; + } + + @Override + public CompletableFuture>> process( + CallbackQueryContext callbackQueryContext, Update update) { + return null; + } +} diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java new file mode 100644 index 0000000..91037ee --- /dev/null +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java @@ -0,0 +1,43 @@ +package com.github.polpetta.mezzotre.telegram.callbackquery; + +import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; +import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.request.BaseRequest; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +@Singleton +public class Dispatcher { + + private final Set tgEventProcessors; + private final Executor threadPool; + + @Inject + public Dispatcher( + @Named("eventProcessors") Set tgEventProcessors, + @Named("eventThreadPool") Executor threadPool) { + this.tgEventProcessors = tgEventProcessors; + this.threadPool = threadPool; + } + + public CompletableFuture>> dispatch( + CallbackQueryContext callbackQueryContext, Update update) { + return CompletableFuture.completedFuture(update) + .thenComposeAsync( + ignored -> + Optional.of(callbackQueryContext.getFields().getEvent()) + .flatMap( + eventName -> + tgEventProcessors.stream() + .filter(processor -> processor.getEventName().equals(eventName)) + .findAny()) + .map(processor -> processor.process(callbackQueryContext, update)) + .orElse(CompletableFuture.failedFuture(new EventProcessorNotFoundException())), + threadPool); + } +} diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/EventProcessorNotFoundException.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/EventProcessorNotFoundException.java new file mode 100644 index 0000000..16f2b8e --- /dev/null +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/EventProcessorNotFoundException.java @@ -0,0 +1,22 @@ +package com.github.polpetta.mezzotre.telegram.callbackquery; + +public class EventProcessorNotFoundException extends RuntimeException { + public EventProcessorNotFoundException() {} + + public EventProcessorNotFoundException(String message) { + super(message); + } + + public EventProcessorNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public EventProcessorNotFoundException(Throwable cause) { + super(cause); + } + + public EventProcessorNotFoundException( + String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Processor.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Processor.java new file mode 100644 index 0000000..e37bbb1 --- /dev/null +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Processor.java @@ -0,0 +1,14 @@ +package com.github.polpetta.mezzotre.telegram.callbackquery; + +import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; +import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.request.BaseRequest; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public interface Processor { + String getEventName(); + + CompletableFuture>> process( + CallbackQueryContext callbackQueryContext, Update update); +} diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/di/CallbackQuery.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/di/CallbackQuery.java new file mode 100644 index 0000000..02b7781 --- /dev/null +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/di/CallbackQuery.java @@ -0,0 +1,18 @@ +package com.github.polpetta.mezzotre.telegram.callbackquery.di; + +import com.github.polpetta.mezzotre.telegram.callbackquery.ChangeLanguage; +import com.github.polpetta.mezzotre.telegram.callbackquery.Processor; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import java.util.Set; +import javax.inject.Named; +import javax.inject.Singleton; + +public class CallbackQuery extends AbstractModule { + @Provides + @Singleton + @Named("eventProcessors") + public Set getEventProcessor(ChangeLanguage changeLanguage) { + return Set.of(changeLanguage); + } +} diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Executor.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Processor.java similarity index 88% rename from src/main/java/com/github/polpetta/mezzotre/telegram/command/Executor.java rename to src/main/java/com/github/polpetta/mezzotre/telegram/command/Processor.java index 5f7610e..36dc493 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Executor.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Processor.java @@ -13,20 +13,20 @@ import java.util.concurrent.CompletableFuture; * @author Davide Polonio * @since 1.0 */ -public interface Executor { +public interface Processor { /** * Provides the keyword to trigger this executor. Note that it must start with "/" at the * beginning, e.g. {@code /start}. * - * @return a {@link String} providing the keyword to trigger the current {@link Executor} + * @return a {@link String} providing the keyword to trigger the current {@link Processor} */ String getTriggerKeyword(); /** * Process the current update * - * @param chat the chat the {@link Executor} is currently replying to + * @param chat the chat the {@link Processor} is currently replying to * @param update the update to process * @return a {@link CompletableFuture} with the result of the computation */ diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java index 45dde03..848c4d3 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java @@ -8,11 +8,12 @@ import com.pengrad.telegrambot.request.BaseRequest; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import javax.inject.Named; /** * This class has the goal of dispatching incoming {@link Update} events to the right {@link - * Executor}, that will provide an adequate response. + * Processor}, that will provide an adequate response. * * @author Davide Polonio * @since 1.0 @@ -20,31 +21,30 @@ import javax.inject.Named; @Singleton public class Router { - private final Set tgExecutors; - private final java.util.concurrent.Executor threadPool; + private final Set tgCommandProcessors; + private final Executor threadPool; @Inject public Router( - @Named("commands") Set tgExecutors, - @Named("eventThreadPool") java.util.concurrent.Executor threadPool) { - this.tgExecutors = tgExecutors; + @Named("commandProcessor") Set tgCommandProcessors, + @Named("eventThreadPool") Executor threadPool) { + this.tgCommandProcessors = tgCommandProcessors; this.threadPool = threadPool; } /** - * Process the incoming {@link Update}. If no suitable {@link Executor} is able to process the + * Process the incoming {@link Update}. If no suitable {@link Processor} is able to process the * command, then {@link CommandNotFoundException} is used to signal this event. * * @param update the update coming from Telegram Servers * @return a {@link CompletableFuture} that is marked as failure and containing a {@link - * CommandNotFoundException} exception if no suitable {@link Executor} is found + * CommandNotFoundException} exception if no suitable {@link Processor} is found */ public CompletableFuture>> process(TgChat chat, Update update) { // This way exceptions are always under control - return CompletableFuture.completedStage(update) - .toCompletableFuture() + return CompletableFuture.completedFuture(update) .thenComposeAsync( - up -> + ignored -> /* Brief explanation of this chain: 1 - Check if the message has a command in it (e.g. "/start hey!") @@ -53,17 +53,17 @@ public class Router { could be in (maybe we're continuing a chat from previous messages?) 2.a - If there's a context with a valid stage, then continue with it */ - Optional.of(up.message().text().split(" ")) + Optional.of(update.message().text().split(" ")) .filter(list -> list.length > 0) .map(list -> list[0]) .filter(wannabeCommand -> wannabeCommand.startsWith("/")) .or(() -> Optional.ofNullable(chat.getChatContext().getStage())) .flatMap( command -> - tgExecutors.stream() + tgCommandProcessors.stream() .filter(ex -> ex.getTriggerKeyword().equals(command)) .findAny()) - .map(executor -> executor.process(chat, up)) + .map(executor -> executor.process(chat, update)) .orElse(CompletableFuture.failedFuture(new CommandNotFoundException())), threadPool); } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java index 8cfad7b..a98a650 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java @@ -21,13 +21,13 @@ import org.apache.velocity.util.StringBuilderWriter; import org.slf4j.Logger; /** - * This {@link Executor} has the goal to greet a user that typed {@code /start} to the bot. + * This {@link Processor} has the goal to greet a user that typed {@code /start} to the bot. * * @author Davide Polonio * @since 1.0 */ @Singleton -public class Start implements Executor { +public class Start implements Processor { private final java.util.concurrent.Executor threadPool; private final Logger log; @@ -75,7 +75,7 @@ public class Start implements Executor { toolContext .getVelocityEngine() .mergeTemplate( - "template/command/start.vm", + "template/command/start.0.vm", StandardCharsets.UTF_8.name(), context, stringBuilderWriter); diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/di/Command.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/di/Command.java index cc1edb2..ef0ee6b 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/command/di/Command.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/di/Command.java @@ -1,6 +1,6 @@ package com.github.polpetta.mezzotre.telegram.command.di; -import com.github.polpetta.mezzotre.telegram.command.Executor; +import com.github.polpetta.mezzotre.telegram.command.Processor; import com.github.polpetta.mezzotre.telegram.command.Start; import com.google.inject.AbstractModule; import com.google.inject.Provides; @@ -14,8 +14,8 @@ import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; public class Command extends AbstractModule { @Provides @Singleton - @Named("commands") - public Set getCommandExecutors(Start start) { + @Named("commandProcessor") + public Set getCommandProcessor(Start start) { return Set.of(start); } diff --git a/src/main/resources/template/command/start.vm b/src/main/resources/template/command/start.0.vm similarity index 100% rename from src/main/resources/template/command/start.vm rename to src/main/resources/template/command/start.0.vm diff --git a/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java index 654dd56..267c191 100644 --- a/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java +++ b/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java @@ -6,14 +6,16 @@ import static org.mockito.Mockito.*; import com.github.polpetta.mezzotre.helper.Loader; import com.github.polpetta.mezzotre.helper.TestConfig; +import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; import com.github.polpetta.mezzotre.orm.model.TgChat; +import com.github.polpetta.mezzotre.telegram.callbackquery.Dispatcher; import com.github.polpetta.mezzotre.telegram.command.Router; +import com.github.polpetta.types.json.CallbackQueryMetadata; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.pengrad.telegrambot.TelegramBot; import com.pengrad.telegrambot.model.Update; import com.pengrad.telegrambot.request.SendMessage; -import com.pengrad.telegrambot.response.SendResponse; import io.ebean.Database; import io.jooby.Context; import io.jooby.MediaType; @@ -42,6 +44,7 @@ class TelegramIntegrationTest { private Telegram telegram; private TelegramBot fakeTelegramBot; private Router fakeRouter; + private Dispatcher fakeDispatcher; @BeforeAll static void beforeAll() { @@ -55,13 +58,15 @@ class TelegramIntegrationTest { fakeTelegramBot = mock(TelegramBot.class); fakeRouter = mock(Router.class); + fakeDispatcher = mock(Dispatcher.class); telegram = new Telegram( LoggerFactory.getLogger(getClass()), new GsonBuilder().setPrettyPrinting().create(), Executors.newSingleThreadExecutor(), - fakeRouter); + fakeRouter, + fakeDispatcher); } @Test @@ -72,9 +77,6 @@ class TelegramIntegrationTest { when(fakeRouter.process(any(TgChat.class), any(Update.class))) .thenReturn(CompletableFuture.completedFuture(Optional.of(expectedBaseRequest))); - final SendResponse fakeResponse = mock(SendResponse.class); - when(fakeResponse.isOk()).thenReturn(true); - when(fakeTelegramBot.execute(any(SendMessage.class))).thenReturn(fakeResponse); final Context fakeContext = mock(Context.class); final Update update = gson.fromJson( @@ -100,11 +102,56 @@ class TelegramIntegrationTest { + "}\n" + "}\n", Update.class); - final CompletableFuture integerCompletableFuture = + final CompletableFuture gotResponseFuture = telegram.incomingUpdate(fakeContext, update); + final String gotReply = gotResponseFuture.get(); verify(fakeContext, times(1)).setResponseType(MediaType.JSON); - final String gotReply = integerCompletableFuture.get(); - assertDoesNotThrow(() -> gotReply); + verify(fakeRouter, times(1)).process(any(), any()); + verify(fakeDispatcher, times(0)).dispatch(any(), any()); + assertEquals(expectedBaseRequest.toWebhookResponse(), gotReply); + } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void shouldProcessAnIncomingCallbackQueryThatExistsInTheDb() throws Exception { + final SendMessage expectedBaseRequest = new SendMessage(1111111, "Hello world"); + when(fakeDispatcher.dispatch(any(CallbackQueryContext.class), any(Update.class))) + .thenReturn(CompletableFuture.completedFuture(Optional.of(expectedBaseRequest))); + final CallbackQueryContext callbackQueryContext = + new CallbackQueryContext( + "41427473-0d81-40a8-af60-9517163615a4", + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withTelegramChatId(666L) + .withMessageId(42L) + .withEvent("testEvent") + .build()); + callbackQueryContext.save(); + + final Context fakeContext = mock(Context.class); + final Update update = + gson.fromJson( + "{\n" + + "\"update_id\":10000,\n" + + "\"callback_query\":{\n" + + " \"id\": \"4382bfdwdsb323b2d9\",\n" + + " \"from\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"type\": \"private\",\n" + + " \"id\":1111111,\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"data\": \"41427473-0d81-40a8-af60-9517163615a4\",\n" + + " \"inline_message_id\": \"1234csdbsk4839\"\n" + + "}\n" + + "}", + Update.class); + final CompletableFuture gotResponseFuture = + telegram.incomingUpdate(fakeContext, update); + final String gotReply = gotResponseFuture.get(); + verify(fakeContext, times(1)).setResponseType(MediaType.JSON); + verify(fakeRouter, times(0)).process(any(), any()); + verify(fakeDispatcher, times(1)).dispatch(eq(callbackQueryContext), any()); assertEquals(expectedBaseRequest.toWebhookResponse(), gotReply); } } diff --git a/src/test/java/com/github/polpetta/mezzotre/telegram/command/RouterTest.java b/src/test/java/com/github/polpetta/mezzotre/telegram/command/RouterTest.java index 45e29ac..1ca3bdf 100644 --- a/src/test/java/com/github/polpetta/mezzotre/telegram/command/RouterTest.java +++ b/src/test/java/com/github/polpetta/mezzotre/telegram/command/RouterTest.java @@ -23,22 +23,22 @@ import org.junit.jupiter.api.parallel.ExecutionMode; @Execution(ExecutionMode.CONCURRENT) class RouterTest { - private static Executor dummyEmptyExampleExecutor; - private static Executor anotherKeyWithResultExecutor; + private static Processor dummyEmptyExampleProcessor; + private static Processor anotherKeyWithResultProcessor; private static Gson gson; @BeforeAll static void beforeAll() { gson = new Gson(); - dummyEmptyExampleExecutor = mock(Executor.class); - when(dummyEmptyExampleExecutor.getTriggerKeyword()).thenReturn("/example"); - when(dummyEmptyExampleExecutor.process(any(), any())) + dummyEmptyExampleProcessor = mock(Processor.class); + when(dummyEmptyExampleProcessor.getTriggerKeyword()).thenReturn("/example"); + when(dummyEmptyExampleProcessor.process(any(), any())) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); - anotherKeyWithResultExecutor = mock(Executor.class); - when(anotherKeyWithResultExecutor.getTriggerKeyword()).thenReturn("/anotherExample"); - when(anotherKeyWithResultExecutor.process(any(), any())) + anotherKeyWithResultProcessor = mock(Processor.class); + when(anotherKeyWithResultProcessor.getTriggerKeyword()).thenReturn("/anotherExample"); + when(anotherKeyWithResultProcessor.process(any(), any())) .thenReturn( CompletableFuture.completedFuture(Optional.of(new SendMessage(1234L, "hello world")))); } @@ -46,7 +46,7 @@ class RouterTest { @Test void shouldMessageExampleMessageAndGetEmptyOptional() throws Exception { final Router router = - new Router(Set.of(dummyEmptyExampleExecutor), Executors.newSingleThreadExecutor()); + new Router(Set.of(dummyEmptyExampleProcessor), Executors.newSingleThreadExecutor()); final TgChat fakeChat = mock(TgChat.class); when(fakeChat.getChatContext()).thenReturn(new ChatContext()); final Update update = @@ -85,7 +85,7 @@ class RouterTest { void shouldSelectRightExecutorAndReturnResult() throws Exception { final Router router = new Router( - Set.of(dummyEmptyExampleExecutor, anotherKeyWithResultExecutor), + Set.of(dummyEmptyExampleProcessor, anotherKeyWithResultProcessor), Executors.newSingleThreadExecutor()); final TgChat fakeChat = mock(TgChat.class); when(fakeChat.getChatContext()).thenReturn(new ChatContext()); -- 2.40.1 From 37058409899df25b10a25ff9bf388497f0dc2975 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Thu, 30 Mar 2023 17:12:43 +0200 Subject: [PATCH 2/9] feat: add first changeLanguage event implementation --- pom.xml | 8 + schema/json/CallbackQueryMetadata.json | 1 - .../com/github/polpetta/mezzotre/App.java | 2 + .../i18n/LocalizedMessageFactory.java | 2 - .../orm/model/CallbackQueryContext.java | 16 +- .../callbackquery/ChangeLanguage.java | 136 +++++++++++++++- .../mezzotre/telegram/command/Start.java | 71 +++++++-- src/main/resources/i18n/message.properties | 9 +- .../resources/i18n/message_en_US.properties | 9 +- src/main/resources/i18n/message_it.properties | 9 +- .../resources/i18n/message_it_IT.properties | 9 +- .../template/callbackQuery/changeLanguage.vm | 5 + .../resources/template/command/start.0.vm | 5 +- .../polpetta/mezzotre/helper/Loader.java | 12 ++ .../route/TelegramIntegrationTest.java | 1 + .../ChangeLanguageIntegrationTest.java | 148 ++++++++++++++++++ .../command/StartIntegrationTest.java | 116 +++++++++++++- .../mezzotre/telegram/command/StartTest.java | 140 ----------------- 18 files changed, 522 insertions(+), 177 deletions(-) create mode 100644 src/main/resources/template/callbackQuery/changeLanguage.vm create mode 100644 src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguageIntegrationTest.java delete mode 100644 src/test/java/com/github/polpetta/mezzotre/telegram/command/StartTest.java diff --git a/pom.xml b/pom.xml index 4795c70..529f51d 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ 1.16.3 1.1.1 2.13.3 + 5.9.1 @@ -68,6 +69,13 @@ test + + org.junit.jupiter + junit-jupiter-params + ${junit-jupiter-params.version} + test + + io.jooby jooby-test diff --git a/schema/json/CallbackQueryMetadata.json b/schema/json/CallbackQueryMetadata.json index 3e4d651..ad290b5 100644 --- a/schema/json/CallbackQueryMetadata.json +++ b/schema/json/CallbackQueryMetadata.json @@ -5,7 +5,6 @@ "default": {}, "required": [ "event", - "messageId", "telegramChatId" ], "additionalProperties": true, diff --git a/src/main/java/com/github/polpetta/mezzotre/App.java b/src/main/java/com/github/polpetta/mezzotre/App.java index c11e464..9261d48 100644 --- a/src/main/java/com/github/polpetta/mezzotre/App.java +++ b/src/main/java/com/github/polpetta/mezzotre/App.java @@ -3,6 +3,7 @@ package com.github.polpetta.mezzotre; import com.github.polpetta.mezzotre.orm.di.Db; import com.github.polpetta.mezzotre.route.Telegram; import com.github.polpetta.mezzotre.route.di.Route; +import com.github.polpetta.mezzotre.telegram.callbackquery.di.CallbackQuery; import com.github.polpetta.mezzotre.telegram.command.di.Command; import com.github.polpetta.mezzotre.util.di.ThreadPool; import com.google.inject.*; @@ -26,6 +27,7 @@ public class App extends Jooby { modules.add(new ThreadPool()); modules.add(new Route()); modules.add(new Command()); + modules.add(new CallbackQuery()); return modules; }; diff --git a/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java b/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java index 86a070a..b905c18 100644 --- a/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java +++ b/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java @@ -22,8 +22,6 @@ public class LocalizedMessageFactory { public ToolManager create(Locale locale) { - // properties.setProperty("file.resource.loader.class", FileResourceLoader.class.getName());. - final ToolManager toolManager = new ToolManager(); toolManager.setVelocityEngine(velocityEngine); final FactoryConfiguration factoryConfiguration = new FactoryConfiguration(); diff --git a/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java b/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java index 9ede71a..3c29f91 100644 --- a/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java +++ b/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java @@ -2,6 +2,7 @@ package com.github.polpetta.mezzotre.orm.model; import com.github.polpetta.types.json.CallbackQueryMetadata; import io.ebean.annotation.DbJson; +import io.ebean.annotation.Length; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @@ -22,12 +23,19 @@ import javax.validation.constraints.NotNull; @Table(name = "callback_query_context") public class CallbackQueryContext extends Base { - @Id private final String id; + @Id + @Length(36) + private final String id; + + @NotNull + @Length(36) + private final String entryGroup; @DbJson @NotNull private final CallbackQueryMetadata fields; - public CallbackQueryContext(String id, CallbackQueryMetadata fields) { + public CallbackQueryContext(String id, String entryGroup, CallbackQueryMetadata fields) { this.id = id; + this.entryGroup = entryGroup; this.fields = fields; } @@ -35,6 +43,10 @@ public class CallbackQueryContext extends Base { return id; } + public String getEntryGroup() { + return entryGroup; + } + public CallbackQueryMetadata getFields() { return fields; } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java index 8f599fe..c0a0aaf 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java @@ -1,27 +1,157 @@ package com.github.polpetta.mezzotre.telegram.callbackquery; +import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; +import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext; +import com.github.polpetta.mezzotre.orm.model.query.QTgChat; import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.model.request.ParseMode; import com.pengrad.telegrambot.request.BaseRequest; +import com.pengrad.telegrambot.request.SendMessage; +import io.vavr.control.Try; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Singleton; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.tools.ToolManager; +import org.apache.velocity.util.StringBuilderWriter; +import org.slf4j.Logger; @Singleton public class ChangeLanguage implements Processor { + public static final String EVENT_NAME = "changeLanguage"; + private final Executor threadPool; + private final LocalizedMessageFactory localizedMessageFactory; + private final Logger log; + + public enum Field { + NewLanguage("newLanguage"); + + private final String name; + + Field(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + public enum Language { + English("en-US"), + Italian("it-IT"); + + private final String locale; + + Language(String locale) { + this.locale = locale; + } + + public String getLocale() { + return locale; + } + } + @Inject - public ChangeLanguage() {} + public ChangeLanguage( + @Named("eventThreadPool") Executor threadPool, + LocalizedMessageFactory localizedMessageFactory, + Logger log) { + this.threadPool = threadPool; + this.localizedMessageFactory = localizedMessageFactory; + this.log = log; + } @Override public String getEventName() { - return "changeLanguage"; + return EVENT_NAME; } @Override public CompletableFuture>> process( CallbackQueryContext callbackQueryContext, Update update) { - return null; + return CompletableFuture.supplyAsync( + () -> + Optional.of(callbackQueryContext.getFields().getTelegramChatId()) + .filter( + chatId -> + chatId.longValue() != 0L + && !chatId.isNaN() + && !chatId.isInfinite() + && chatId != Double.MIN_VALUE) + .flatMap(chatId -> new QTgChat().id.eq(chatId.longValue()).findOneOrEmpty()) + .map( + tgChat -> { + tgChat.setLocale( + (String) + callbackQueryContext + .getFields() + .getAdditionalProperties() + .getOrDefault( + Field.NewLanguage.getName(), + Language.English.getLocale())); + tgChat.save(); + return tgChat; + }) + .orElseThrow( + () -> + new NoSuchElementException( + "Unable to find telegram chat " + + Double.valueOf( + callbackQueryContext.getFields().getTelegramChatId()) + .longValue() + + " in the database")), + threadPool) + .thenApplyAsync( + // If we are here then we're sure there is at least a chat associated with this callback + tgChat -> { + final String message = + Try.of( + () -> { + final Locale locale = Locale.forLanguageTag(tgChat.getLocale()); + final ToolManager toolManager = localizedMessageFactory.create(locale); + final VelocityContext velocityContext = + new VelocityContext(toolManager.createContext()); + + final StringBuilder content = new StringBuilder(); + final StringBuilderWriter stringBuilderWriter = + new StringBuilderWriter(content); + + toolManager + .getVelocityEngine() + .mergeTemplate( + "/template/callbackQuery/changeLanguage.vm", + StandardCharsets.UTF_8.name(), + velocityContext, + stringBuilderWriter); + + stringBuilderWriter.close(); + return content.toString(); + }) + .get(); + + log.trace("ChangeLanguage event - message to send back: " + message); + + final String callBackGroupToDelete = callbackQueryContext.getEntryGroup(); + final int delete = + new QCallbackQueryContext().entryGroup.eq(callBackGroupToDelete).delete(); + log.trace( + "Deleted " + + delete + + " entries regarding callback group " + + callBackGroupToDelete); + + return Optional.of( + new SendMessage(tgChat.getId(), message).parseMode(ParseMode.Markdown)); + }, + threadPool); } } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java index a98a650..8c5aae5 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java @@ -1,10 +1,17 @@ package com.github.polpetta.mezzotre.telegram.command; import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; +import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; import com.github.polpetta.mezzotre.orm.model.TgChat; +import com.github.polpetta.mezzotre.telegram.callbackquery.ChangeLanguage; +import com.github.polpetta.mezzotre.util.Clock; +import com.github.polpetta.mezzotre.util.UUIDGenerator; +import com.github.polpetta.types.json.CallbackQueryMetadata; import com.github.polpetta.types.json.ChatContext; import com.google.inject.Singleton; 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.ParseMode; import com.pengrad.telegrambot.request.BaseRequest; import com.pengrad.telegrambot.request.SendMessage; @@ -13,6 +20,7 @@ import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Named; import org.apache.velocity.VelocityContext; @@ -29,18 +37,24 @@ import org.slf4j.Logger; @Singleton public class Start implements Processor { - private final java.util.concurrent.Executor threadPool; + private final Executor threadPool; private final Logger log; + private final UUIDGenerator uuidGenerator; + private final Clock clock; private final LocalizedMessageFactory localizedMessageFactory; @Inject public Start( LocalizedMessageFactory localizedMessageFactory, - @Named("eventThreadPool") java.util.concurrent.Executor threadPool, - Logger log) { + @Named("eventThreadPool") Executor threadPool, + Logger log, + UUIDGenerator uuidGenerator, + Clock clock) { this.localizedMessageFactory = localizedMessageFactory; this.threadPool = threadPool; this.log = log; + this.uuidGenerator = uuidGenerator; + this.clock = clock; } @Override @@ -62,17 +76,17 @@ public class Start implements Processor { Try.of( () -> { final Locale locale = Locale.forLanguageTag(chat.getLocale()); - final ToolManager toolContext = localizedMessageFactory.create(locale); + final ToolManager toolManager = localizedMessageFactory.create(locale); final VelocityContext context = - new VelocityContext(toolContext.createContext()); + new VelocityContext(toolManager.createContext()); context.put("firstName", update.message().chat().firstName()); - context.put("programName", "Mezzotre"); + context.put("programName", "_Mezzotre_"); final StringBuilder content = new StringBuilder(); final StringBuilderWriter stringBuilderWriter = new StringBuilderWriter(content); - toolContext + toolManager .getVelocityEngine() .mergeTemplate( "template/command/start.0.vm", @@ -87,14 +101,51 @@ public class Start implements Processor { log.trace("Start command - message to send back: " + message); final ChatContext chatContext = chat.getChatContext(); - chatContext.setLastMessageSentId(update.message().messageId()); chatContext.setStage(getTriggerKeyword()); chatContext.setStep(0); - chatContext.setPreviousMessageUnixTimestampInSeconds(update.message().date()); + chatContext.setPreviousMessageUnixTimestampInSeconds(clock.now()); chat.setChatContext(chatContext); chat.save(); - return Optional.of(new SendMessage(chat.getId(), message).parseMode(ParseMode.Markdown)); + // To get the messageId we should send the message first, then save it in the database! + final String groupId = uuidGenerator.generateAsString(); + final CallbackQueryContext switchToEnglish = + new CallbackQueryContext( + uuidGenerator.generateAsString(), + groupId, + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withEvent(ChangeLanguage.EVENT_NAME) + .withTelegramChatId(update.message().chat().id()) + .withAdditionalProperty( + ChangeLanguage.Field.NewLanguage.getName(), + ChangeLanguage.Language.English) + .build()); + + final CallbackQueryContext switchToItalian = + new CallbackQueryContext( + uuidGenerator.generateAsString(), + groupId, + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withEvent(ChangeLanguage.EVENT_NAME) + .withTelegramChatId(update.message().chat().id()) + .withAdditionalProperty( + ChangeLanguage.Field.NewLanguage.getName(), + ChangeLanguage.Language.Italian) + .build()); + + final SendMessage messageToSend = + new SendMessage(chat.getId(), message) + .parseMode(ParseMode.Markdown) + .replyMarkup( + new InlineKeyboardMarkup( + new InlineKeyboardButton("English").callbackData(switchToEnglish.getId()), + new InlineKeyboardButton("Italian") + .callbackData(switchToItalian.getId()))); + + switchToEnglish.save(); + switchToItalian.save(); + + return Optional.of(messageToSend); }, threadPool); } diff --git a/src/main/resources/i18n/message.properties b/src/main/resources/i18n/message.properties index e18c881..1a07ce4 100644 --- a/src/main/resources/i18n/message.properties +++ b/src/main/resources/i18n/message.properties @@ -1,3 +1,6 @@ -start.hello=Hello -start.thisIs=This is -start.description=a simple bot focused on DnD content management! Please start by choosing a language down below. +start.helloFirstName=Hello {0}! \ud83d\udc4b +start.description=This is {0}, a simple bot focused on DnD content management! Please start by choosing a language down below \ud83d\udc47 +changeLanguage.drinkAction=*Proceeds to drink a potion with a strange, multicolor liquid* +changeLanguage.setLanguage=Thanks! Now that I drank this modified potion of {0} that I''ve found at the "Crystal Fermentary" magic potion shop yesterday I can speak with you in the language you prefer! +changeLanguage.instructions=You can always change your language settings by typing /changeLanguage in the chat. +spell.speakWithAnimals=Speak with animals diff --git a/src/main/resources/i18n/message_en_US.properties b/src/main/resources/i18n/message_en_US.properties index e18c881..1a07ce4 100644 --- a/src/main/resources/i18n/message_en_US.properties +++ b/src/main/resources/i18n/message_en_US.properties @@ -1,3 +1,6 @@ -start.hello=Hello -start.thisIs=This is -start.description=a simple bot focused on DnD content management! Please start by choosing a language down below. +start.helloFirstName=Hello {0}! \ud83d\udc4b +start.description=This is {0}, a simple bot focused on DnD content management! Please start by choosing a language down below \ud83d\udc47 +changeLanguage.drinkAction=*Proceeds to drink a potion with a strange, multicolor liquid* +changeLanguage.setLanguage=Thanks! Now that I drank this modified potion of {0} that I''ve found at the "Crystal Fermentary" magic potion shop yesterday I can speak with you in the language you prefer! +changeLanguage.instructions=You can always change your language settings by typing /changeLanguage in the chat. +spell.speakWithAnimals=Speak with animals diff --git a/src/main/resources/i18n/message_it.properties b/src/main/resources/i18n/message_it.properties index 0f7424c..739ea44 100644 --- a/src/main/resources/i18n/message_it.properties +++ b/src/main/resources/i18n/message_it.properties @@ -1,3 +1,6 @@ -start.hello=Ciao -start.thisIs=Questo è -start.description=un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto +start.helloFirstName=Ciao {0}! \ud83d\udc4b +start.description=Questo è {0}, un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto \ud83d\udc47 +changeLanguage.drinkAction=*Procede a bere una pozione al cui suo interno si trova uno strano liquido multicolore* +changeLanguage.setLanguage=Grazie! Ora che ho bevuto quest posizione modificata di {0} che ho trovato ieri al negozio di pozioni magiche la "Cristalleria Fermentatrice" posso parlare con te nel linguaggio che preferisci! +changeLanguage.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /changeLanguage nella chat. +spell.speakWithAnimals=Parlare con animali diff --git a/src/main/resources/i18n/message_it_IT.properties b/src/main/resources/i18n/message_it_IT.properties index 0f7424c..739ea44 100644 --- a/src/main/resources/i18n/message_it_IT.properties +++ b/src/main/resources/i18n/message_it_IT.properties @@ -1,3 +1,6 @@ -start.hello=Ciao -start.thisIs=Questo è -start.description=un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto +start.helloFirstName=Ciao {0}! \ud83d\udc4b +start.description=Questo è {0}, un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto \ud83d\udc47 +changeLanguage.drinkAction=*Procede a bere una pozione al cui suo interno si trova uno strano liquido multicolore* +changeLanguage.setLanguage=Grazie! Ora che ho bevuto quest posizione modificata di {0} che ho trovato ieri al negozio di pozioni magiche la "Cristalleria Fermentatrice" posso parlare con te nel linguaggio che preferisci! +changeLanguage.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /changeLanguage nella chat. +spell.speakWithAnimals=Parlare con animali diff --git a/src/main/resources/template/callbackQuery/changeLanguage.vm b/src/main/resources/template/callbackQuery/changeLanguage.vm new file mode 100644 index 0000000..57fa7c0 --- /dev/null +++ b/src/main/resources/template/callbackQuery/changeLanguage.vm @@ -0,0 +1,5 @@ +_${i18n.changeLanguage.drinkAction}_ + +${i18n.changeLanguage.setLanguage.insert(${i18n.spell.speakWithAnimals})} + +${i18n.changeLanguage.instructions} \ No newline at end of file diff --git a/src/main/resources/template/command/start.0.vm b/src/main/resources/template/command/start.0.vm index 4e6a73d..7889c0b 100644 --- a/src/main/resources/template/command/start.0.vm +++ b/src/main/resources/template/command/start.0.vm @@ -1,4 +1,3 @@ -## https://velocity.apache.org/tools/2.0/apidocs/org/apache/velocity/tools/generic/ResourceTool.html -**$i18n.start.hello $firstName! 👋** +**${i18n.start.helloFirstName.insert(${firstName})}** -$i18n.start.thisIs _${programName}_, $i18n.start.description 👇 \ No newline at end of file +${i18n.start.description.insert(${programName})} \ No newline at end of file diff --git a/src/test/java/com/github/polpetta/mezzotre/helper/Loader.java b/src/test/java/com/github/polpetta/mezzotre/helper/Loader.java index 4f6d18e..90bc7b9 100644 --- a/src/test/java/com/github/polpetta/mezzotre/helper/Loader.java +++ b/src/test/java/com/github/polpetta/mezzotre/helper/Loader.java @@ -11,6 +11,9 @@ import java.io.InputStream; import java.net.URL; import java.util.Properties; import org.apache.commons.lang3.tuple.Pair; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; import org.testcontainers.containers.PostgreSQLContainer; public class Loader { @@ -49,4 +52,13 @@ public class Loader { public static Database connectToDatabase(Pair connectionProperties) { return connectToDatabase(connectionProperties.getLeft(), connectionProperties.getRight()); } + + public static VelocityEngine defaultVelocityEngine() { + final VelocityEngine velocityEngine = new VelocityEngine(); + velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "classpath"); + velocityEngine.setProperty( + "resource.loader.classpath.class", ClasspathResourceLoader.class.getName()); + velocityEngine.init(); + return velocityEngine; + } } diff --git a/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java index 267c191..7479666 100644 --- a/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java +++ b/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java @@ -120,6 +120,7 @@ class TelegramIntegrationTest { final CallbackQueryContext callbackQueryContext = new CallbackQueryContext( "41427473-0d81-40a8-af60-9517163615a4", + "2ee7f5c6-93f0-4859-b902-af9476cf74ad", new CallbackQueryMetadata.CallbackQueryMetadataBuilder() .withTelegramChatId(666L) .withMessageId(42L) diff --git a/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguageIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguageIntegrationTest.java new file mode 100644 index 0000000..26056a1 --- /dev/null +++ b/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguageIntegrationTest.java @@ -0,0 +1,148 @@ +package com.github.polpetta.mezzotre.telegram.callbackquery; + +import static org.junit.jupiter.api.Assertions.*; + +import com.github.polpetta.mezzotre.helper.Loader; +import com.github.polpetta.mezzotre.helper.TestConfig; +import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; +import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; +import com.github.polpetta.mezzotre.orm.model.TgChat; +import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext; +import com.github.polpetta.mezzotre.orm.model.query.QTgChat; +import com.github.polpetta.types.json.CallbackQueryMetadata; +import com.github.polpetta.types.json.ChatContext; +import com.google.gson.Gson; +import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.request.BaseRequest; +import com.pengrad.telegrambot.request.SendMessage; +import io.ebean.Database; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Tag("slow") +@Tag("database") +@Tag("velocity") +@Testcontainers +class ChangeLanguageIntegrationTest { + + private static Gson gson; + + @Container + private final PostgreSQLContainer postgresServer = + new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE); + + private Database database; + private ChangeLanguage changeLanguage; + + @BeforeAll + static void beforeAll() { + gson = new Gson(); + } + + @BeforeEach + void setUp() throws Exception { + database = + Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer)); + + changeLanguage = + new ChangeLanguage( + Executors.newSingleThreadExecutor(), + new LocalizedMessageFactory(Loader.defaultVelocityEngine()), + LoggerFactory.getLogger(ChangeLanguage.class)); + } + + private static Stream getTestLocales() { + return Stream.of( + Arguments.of( + ChangeLanguage.Language.Italian, + "_*Procede a bere una pozione al cui suo interno si trova uno strano liquido" + + " multicolore*_\n" + + "\n" + + "Grazie! Ora che ho bevuto quest posizione modificata di Parlare con animali che" + + " ho trovato ieri al negozio di pozioni magiche la \"Cristalleria Fermentatrice\"" + + " posso parlare con te nel linguaggio che preferisci!\n" + + "\n" + + "Puoi sempre cambiare le preferenze della tua lingua scrivendo /changeLanguage" + + " nella chat.", + "en-US"), + Arguments.of( + ChangeLanguage.Language.English, + "_*Proceeds to drink a potion with a strange, multicolor liquid*_\n" + + "\n" + + "Thanks! Now that I drank this modified potion of Speak with animals that I've" + + " found at the \"Crystal Fermentary\" magic potion shop yesterday I can speak" + + " with you in the language you prefer!\n" + + "\n" + + "You can always change your language settings by typing /changeLanguage in the" + + " chat.", + "it-IT")); + } + + @ParameterizedTest + @Timeout(value = 1, unit = TimeUnit.MINUTES) + @MethodSource("getTestLocales") + void shouldProcessChangeLanguageToDesiredOne( + ChangeLanguage.Language language, String expectedResult, String startingLocale) + throws Exception { + + final Update update = + gson.fromJson( + "{\n" + + "\"update_id\":10000,\n" + + "\"callback_query\":{\n" + + " \"id\": \"4382bfdwdsb323b2d9\",\n" + + " \"from\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"type\": \"private\",\n" + + " \"id\":1111111,\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"data\": \"Data from button callback\",\n" + + " \"inline_message_id\": \"1234csdbsk4839\"\n" + + "}\n" + + "}", + Update.class); + + final long tgChatId = 1111111L; + final ChatContext chatContext = new ChatContext(); + final TgChat tgChat = new TgChat(tgChatId, chatContext, startingLocale); + tgChat.save(); + + final CallbackQueryMetadata callbackQueryMetadata = + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withEvent("changeLanguage") + .withTelegramChatId(tgChatId) + .withAdditionalProperty( + ChangeLanguage.Field.NewLanguage.getName(), language.getLocale()) + .build(); + final String entryGroupId = "2e67774a-e4e4-4369-a414-a7f8bfe74b80"; + final CallbackQueryContext changeLanguageCallbackQueryContext = + new CallbackQueryContext( + "c018108f-6612-4848-8fca-cf301460d4eb", entryGroupId, callbackQueryMetadata); + changeLanguageCallbackQueryContext.save(); + + final CompletableFuture>> processFuture = + changeLanguage.process(changeLanguageCallbackQueryContext, update); + final Optional> gotResponseOpt = processFuture.get(); + final SendMessage gotMessage = (SendMessage) gotResponseOpt.get(); + assertEquals(expectedResult, gotMessage.getParameters().get("text")); + + final TgChat retrievedTgChat = new QTgChat().id.eq(tgChatId).findOne(); + assertNotNull(retrievedTgChat); + assertEquals(language.getLocale(), retrievedTgChat.getLocale()); + + assertEquals(0, new QCallbackQueryContext().entryGroup.eq(entryGroupId).findCount()); + } +} diff --git a/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java index 4d12134..08999a8 100644 --- a/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java +++ b/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java @@ -1,12 +1,15 @@ package com.github.polpetta.mezzotre.telegram.command; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; import com.github.polpetta.mezzotre.helper.Loader; import com.github.polpetta.mezzotre.helper.TestConfig; import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; import com.github.polpetta.mezzotre.orm.model.TgChat; import com.github.polpetta.mezzotre.orm.model.query.QTgChat; +import com.github.polpetta.mezzotre.util.Clock; +import com.github.polpetta.mezzotre.util.UUIDGenerator; import com.github.polpetta.types.json.ChatContext; import com.google.gson.Gson; import com.pengrad.telegrambot.model.Update; @@ -15,6 +18,7 @@ import com.pengrad.telegrambot.request.SendMessage; import io.ebean.Database; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.RuntimeConstants; @@ -45,6 +49,8 @@ class StartIntegrationTest { private LocalizedMessageFactory localizedMessageFactory; private Start start; private Database database; + private Clock fakeClock; + private UUIDGenerator fakeUUIDGenerator; @BeforeAll static void beforeAll() { @@ -64,11 +70,25 @@ class StartIntegrationTest { final Logger log = LoggerFactory.getLogger(Start.class); - start = new Start(localizedMessageFactory, Executors.newSingleThreadExecutor(), log); + fakeClock = mock(Clock.class); + fakeUUIDGenerator = mock(UUIDGenerator.class); + + start = + new Start( + localizedMessageFactory, + Executors.newSingleThreadExecutor(), + log, + fakeUUIDGenerator, + fakeClock); } @Test void shouldUpdateContextInTheDatabase() throws Exception { + when(fakeClock.now()).thenReturn(42L); + when(fakeUUIDGenerator.generateAsString()) + .thenReturn("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5") + .thenReturn("16507fbd-9f28-48a8-9de1-3ea1c943af67") + .thenReturn("0b0ac18e-f621-484e-aa8d-9b176be5b930"); final TgChat tgChat = new TgChat(1111111L, new ChatContext()); tgChat.setLocale("en-US"); tgChat.save(); @@ -108,16 +128,104 @@ class StartIntegrationTest { assertEquals( "**Hello Test Firstname! \uD83D\uDC4B**\n\n" + "This is _Mezzotre_, a simple bot focused on DnD content management! Please start by" - + " choosing a language down below. \uD83D\uDC47", + + " choosing a language down below \uD83D\uDC47", message); assertEquals(1111111L, (Long) gotMessage.getParameters().get("chat_id")); final TgChat retrievedTgChat = new QTgChat().id.eq(1111111L).findOne(); assertNotNull(retrievedTgChat); final ChatContext gotChatContext = retrievedTgChat.getChatContext(); - assertEquals(1441645532, gotChatContext.getPreviousMessageUnixTimestampInSeconds()); - assertEquals(1365, gotChatContext.getLastMessageSentId()); + assertEquals(42, gotChatContext.getPreviousMessageUnixTimestampInSeconds()); + assertEquals(0, gotChatContext.getLastMessageSentId()); assertEquals("/start", gotChatContext.getStage()); assertEquals(0, gotChatContext.getStep()); } + + @Test + void shouldReceiveHelloIntroduction() throws Exception { + when(fakeClock.now()).thenReturn(42L); + when(fakeUUIDGenerator.generateAsString()) + .thenReturn("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5") + .thenReturn("16507fbd-9f28-48a8-9de1-3ea1c943af67") + .thenReturn("0b0ac18e-f621-484e-aa8d-9b176be5b930"); + + final TgChat tgChat = new TgChat(1111111L, new ChatContext(), "en-US"); + + final Update update = + gson.fromJson( + "{\n" + + "\"update_id\":10000,\n" + + "\"message\":{\n" + + " \"date\":1441645532,\n" + + " \"chat\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"id\":1111111,\n" + + " \"type\": \"private\",\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"message_id\":1365,\n" + + " \"from\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"id\":1111111,\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"text\":\"/start\"\n" + + "}\n" + + "}", + Update.class); + + final CompletableFuture>> gotFuture = start.process(tgChat, update); + assertDoesNotThrow(() -> gotFuture.get()); + final Optional> gotMessageOptional = gotFuture.get(); + assertDoesNotThrow(gotMessageOptional::get); + final BaseRequest gotMessage = gotMessageOptional.get(); + assertInstanceOf(SendMessage.class, gotMessage); + final String message = (String) gotMessage.getParameters().get("text"); + assertEquals( + "**Hello Test Firstname! \uD83D\uDC4B**\n\n" + + "This is _Mezzotre_, a simple bot focused on DnD content management! Please start by" + + " choosing a language down below \uD83D\uDC47", + message); + assertEquals(1111111L, (Long) gotMessage.getParameters().get("chat_id")); + } + + @Test + void shouldThrowErrorIfLocaleNonExists() { + when(fakeClock.now()).thenReturn(42L); + when(fakeUUIDGenerator.generateAsString()) + .thenReturn("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5") + .thenReturn("16507fbd-9f28-48a8-9de1-3ea1c943af67") + .thenReturn("0b0ac18e-f621-484e-aa8d-9b176be5b930"); + final TgChat tgChat = new TgChat(1111111L, null); + + final Update update = + gson.fromJson( + "{\n" + + "\"update_id\":10000,\n" + + "\"message\":{\n" + + " \"date\":1441645532,\n" + + " \"chat\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"id\":1111111,\n" + + " \"type\": \"private\",\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"message_id\":1365,\n" + + " \"from\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"id\":1111111,\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"text\":\"/start\"\n" + + "}\n" + + "}", + Update.class); + + final CompletableFuture>> gotFuture = start.process(tgChat, update); + assertThrows(ExecutionException.class, gotFuture::get); + } } diff --git a/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartTest.java b/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartTest.java deleted file mode 100644 index 31e6cf4..0000000 --- a/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartTest.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.github.polpetta.mezzotre.telegram.command; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; -import com.github.polpetta.mezzotre.orm.model.TgChat; -import com.github.polpetta.types.json.ChatContext; -import com.google.gson.Gson; -import com.pengrad.telegrambot.model.Update; -import com.pengrad.telegrambot.request.BaseRequest; -import com.pengrad.telegrambot.request.SendMessage; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import org.apache.velocity.app.VelocityEngine; -import org.apache.velocity.runtime.RuntimeConstants; -import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.slf4j.Logger; - -@Tag("velocity") -@Execution(ExecutionMode.CONCURRENT) -class StartTest { - - private VelocityEngine velocityEngine; - private LocalizedMessageFactory localizedMessageFactory; - private Start start; - private static Gson gson; - - @BeforeAll - static void beforeAll() { - gson = new Gson(); - } - - @BeforeEach - void setUp() { - velocityEngine = new VelocityEngine(); - velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "classpath"); - velocityEngine.setProperty( - "resource.loader.classpath.class", ClasspathResourceLoader.class.getName()); - velocityEngine.init(); - localizedMessageFactory = new LocalizedMessageFactory(velocityEngine); - - final Logger fakeLog = mock(Logger.class); - - start = new Start(localizedMessageFactory, Executors.newSingleThreadExecutor(), fakeLog); - } - - @Test - void shouldReceiveHelloIntroduction() throws Exception { - final TgChat fakeChat = mock(TgChat.class); - when(fakeChat.getLocale()).thenReturn("en-US"); - when(fakeChat.getChatContext()).thenReturn(new ChatContext()); - when(fakeChat.getId()).thenReturn(1111111L); - - final Update update = - gson.fromJson( - "{\n" - + "\"update_id\":10000,\n" - + "\"message\":{\n" - + " \"date\":1441645532,\n" - + " \"chat\":{\n" - + " \"last_name\":\"Test Lastname\",\n" - + " \"id\":1111111,\n" - + " \"type\": \"private\",\n" - + " \"first_name\":\"Test Firstname\",\n" - + " \"username\":\"Testusername\"\n" - + " },\n" - + " \"message_id\":1365,\n" - + " \"from\":{\n" - + " \"last_name\":\"Test Lastname\",\n" - + " \"id\":1111111,\n" - + " \"first_name\":\"Test Firstname\",\n" - + " \"username\":\"Testusername\"\n" - + " },\n" - + " \"text\":\"/start\"\n" - + "}\n" - + "}", - Update.class); - - final CompletableFuture>> gotFuture = - start.process(fakeChat, update); - assertDoesNotThrow(() -> gotFuture.get()); - final Optional> gotMessageOptional = gotFuture.get(); - assertDoesNotThrow(gotMessageOptional::get); - final BaseRequest gotMessage = gotMessageOptional.get(); - assertInstanceOf(SendMessage.class, gotMessage); - final String message = (String) gotMessage.getParameters().get("text"); - assertEquals( - "**Hello Test Firstname! \uD83D\uDC4B**\n\n" - + "This is _Mezzotre_, a simple bot focused on DnD content management! Please start by" - + " choosing a language down below. \uD83D\uDC47", - message); - assertEquals(1111111L, (Long) gotMessage.getParameters().get("chat_id")); - verify(fakeChat, times(1)).save(); - } - - @Test - void shouldThrowErrorIfLocaleNonExists() { - final TgChat fakeChat = mock(TgChat.class); - // Do not set Locale on purpose - when(fakeChat.getId()).thenReturn(1111111L); - - final Update update = - gson.fromJson( - "{\n" - + "\"update_id\":10000,\n" - + "\"message\":{\n" - + " \"date\":1441645532,\n" - + " \"chat\":{\n" - + " \"last_name\":\"Test Lastname\",\n" - + " \"id\":1111111,\n" - + " \"type\": \"private\",\n" - + " \"first_name\":\"Test Firstname\",\n" - + " \"username\":\"Testusername\"\n" - + " },\n" - + " \"message_id\":1365,\n" - + " \"from\":{\n" - + " \"last_name\":\"Test Lastname\",\n" - + " \"id\":1111111,\n" - + " \"first_name\":\"Test Firstname\",\n" - + " \"username\":\"Testusername\"\n" - + " },\n" - + " \"text\":\"/start\"\n" - + "}\n" - + "}", - Update.class); - - final CompletableFuture>> gotFuture = - start.process(fakeChat, update); - assertThrows(ExecutionException.class, gotFuture::get); - } -} -- 2.40.1 From 9889d25bf3cb93d23b33eddd178a4db3fff1dea0 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Fri, 31 Mar 2023 09:53:42 +0200 Subject: [PATCH 3/9] feat: add Javadoc, add tests --- .../orm/model/CallbackQueryContext.java | 20 +++- .../callbackquery/ChangeLanguage.java | 18 +++ .../migration/V1_0_0__Create_initial_db.sql | 9 ++ .../CallbackQueryContextIntegrationTest.java | 108 ++++++++++++++++++ 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContextIntegrationTest.java diff --git a/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java b/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java index 3c29f91..8cf1ef7 100644 --- a/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java +++ b/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java @@ -3,6 +3,7 @@ package com.github.polpetta.mezzotre.orm.model; import com.github.polpetta.types.json.CallbackQueryMetadata; import io.ebean.annotation.DbJson; import io.ebean.annotation.Length; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @@ -31,7 +32,10 @@ public class CallbackQueryContext extends Base { @Length(36) private final String entryGroup; - @DbJson @NotNull private final CallbackQueryMetadata fields; + @DbJson + @Column(columnDefinition = "json not null default '{}'::json") + @NotNull + private final CallbackQueryMetadata fields; public CallbackQueryContext(String id, String entryGroup, CallbackQueryMetadata fields) { this.id = id; @@ -50,4 +54,18 @@ public class CallbackQueryContext extends Base { public CallbackQueryMetadata getFields() { return fields; } + + @Override + public String toString() { + return "CallbackQueryContext{" + + "id='" + + id + + '\'' + + ", entryGroup='" + + entryGroup + + '\'' + + ", fields=" + + fields + + '}'; + } } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java index c0a0aaf..fa533ac 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java @@ -23,6 +23,12 @@ import org.apache.velocity.tools.ToolManager; import org.apache.velocity.util.StringBuilderWriter; import org.slf4j.Logger; +/** + * ChangeLanguage event is related to support locale change for a particular chat + * + * @author Davide Polonio + * @since 1.0 + */ @Singleton public class ChangeLanguage implements Processor { @@ -31,6 +37,12 @@ public class ChangeLanguage implements Processor { private final LocalizedMessageFactory localizedMessageFactory; private final Logger log; + /** + * Additional fields that are related to {@code changeLanguage} event + * + * @author Davide Polonio + * @since 1.0 + */ public enum Field { NewLanguage("newLanguage"); @@ -45,6 +57,12 @@ public class ChangeLanguage implements Processor { } } + /** + * Possible values for the additional {@link Field} of {@code changeLanguage} event + * + * @author Davide Polonio + * @since 1.0 + */ public enum Language { English("en-US"), Italian("it-IT"); diff --git a/src/main/resources/db/migration/V1_0_0__Create_initial_db.sql b/src/main/resources/db/migration/V1_0_0__Create_initial_db.sql index 5808186..0146fa6 100644 --- a/src/main/resources/db/migration/V1_0_0__Create_initial_db.sql +++ b/src/main/resources/db/migration/V1_0_0__Create_initial_db.sql @@ -1,4 +1,13 @@ -- apply changes +create table callback_query_context ( + id varchar(36) not null, + entry_group varchar(36) not null, + fields json not null, + entry_created timestamptz not null, + entry_modified timestamptz not null, + constraint pk_callback_query_context primary key (id) +); + create table telegram_chat ( id bigint generated by default as identity not null, chat_context jsonb not null default '{}'::jsonb not null, diff --git a/src/test/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContextIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContextIntegrationTest.java new file mode 100644 index 0000000..2c45563 --- /dev/null +++ b/src/test/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContextIntegrationTest.java @@ -0,0 +1,108 @@ +package com.github.polpetta.mezzotre.orm.model; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.polpetta.mezzotre.helper.Loader; +import com.github.polpetta.mezzotre.helper.TestConfig; +import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext; +import com.github.polpetta.mezzotre.util.Clock; +import com.github.polpetta.mezzotre.util.UUIDGenerator; +import com.github.polpetta.types.json.CallbackQueryMetadata; +import io.ebean.Database; +import io.ebean.SqlRow; +import java.sql.Timestamp; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Tag("slow") +@Tag("database") +@Testcontainers +class CallbackQueryContextIntegrationTest { + + private static ObjectMapper objectMapper; + private static UUIDGenerator uuidGenerator; + + @Container + private final PostgreSQLContainer postgresServer = + new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE); + + private Database database; + + @BeforeAll + static void beforeAll() { + objectMapper = new ObjectMapper(); + uuidGenerator = new UUIDGenerator(); + } + + @BeforeEach + void setUp() throws Exception { + database = + Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer)); + } + + @Test + void shouldInsertEntryIntoDatabase() throws Exception { + final String uuidContext = uuidGenerator.generateAsString(); + final String uuidGroup = uuidGenerator.generateAsString(); + final CallbackQueryMetadata callbackQueryMetadata = + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withEvent("eventExample") + .withTelegramChatId(1234L) + .withMessageId(4242L) + .build(); + + final CallbackQueryContext callbackQueryContext = + new CallbackQueryContext(uuidContext, uuidGroup, callbackQueryMetadata); + callbackQueryContext.save(); + + final String query = "select * from callback_query_context where id = ?"; + final SqlRow savedCallbackQueryContext = + database.sqlQuery(query).setParameter(uuidContext).findOne(); + + assertNotNull(savedCallbackQueryContext); + assertEquals(uuidGroup, savedCallbackQueryContext.getString("entry_group")); + assertEquals(uuidContext, savedCallbackQueryContext.getString("id")); + assertEquals( + objectMapper.writeValueAsString(callbackQueryMetadata), + savedCallbackQueryContext.getString("fields")); + } + + @Test + void shouldRetrieveEntryInDatabase() throws Exception { + final Timestamp timestampFromUnixEpoch = Clock.getTimestampFromUnixEpoch(1L); + final String uuidId = uuidGenerator.generateAsString(); + final String uuidEntryGroup = uuidGenerator.generateAsString(); + final CallbackQueryMetadata callbackQueryMetadata = + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withEvent("eventExample") + .withTelegramChatId(12324L) + .withMessageId(42342L) + .build(); + final String insertQuery = "insert into callback_query_context values (?, ?, ?::json, ?, ?)"; + final int affectedRows = + database + .sqlUpdate(insertQuery) + .setParameter(uuidId) + .setParameter(uuidEntryGroup) + .setParameter(objectMapper.writeValueAsString(callbackQueryMetadata)) + .setParameter(timestampFromUnixEpoch) + .setParameter(timestampFromUnixEpoch) + .execute(); + + assertEquals(1, affectedRows); + + final CallbackQueryContext got = new QCallbackQueryContext().id.eq(uuidId).findOne(); + assertNotNull(got); + final CallbackQueryMetadata gotFields = got.getFields(); + assertNotNull(gotFields); + assertEquals("eventExample", gotFields.getEvent()); + assertEquals(12324L, Double.valueOf(gotFields.getTelegramChatId()).longValue()); + assertEquals(42342L, Double.valueOf(gotFields.getMessageId()).longValue()); + } +} -- 2.40.1 From d6529aaae809a1473514d613f838b379cb8f162b Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Fri, 31 Mar 2023 18:33:35 +0200 Subject: [PATCH 4/9] feat: complete change language flow, add tests * to check: possible missing javadoc --- .markdownlint.yaml | 1 + README.md | 4 +- src/etc/stork/stork.yml | 2 +- .../i18n/LocalizedMessageFactory.java | 7 +- .../polpetta/mezzotre/orm/model/TgChat.java | 36 +- .../polpetta/mezzotre/route/Telegram.java | 2 +- ...guage.java => SelectLanguageTutorial.java} | 99 ++++- .../telegram/callbackquery/ShowHelp.java | 26 ++ .../callbackquery/di/CallbackQuery.java | 8 +- .../mezzotre/telegram/command/Processor.java | 1 - .../mezzotre/telegram/command/Start.java | 31 +- .../migration/V1_0_0__Create_initial_db.sql | 3 +- src/main/resources/i18n/message.properties | 11 +- .../resources/i18n/message_en_US.properties | 11 +- src/main/resources/i18n/message_it.properties | 11 +- .../resources/i18n/message_it_IT.properties | 11 +- .../template/callbackQuery/changeLanguage.vm | 5 - .../callbackQuery/selectLanguageTutorial.vm | 9 + .../orm/model/TgChatIntegrationTest.java | 7 +- .../ChangeLanguageIntegrationTest.java | 148 -------- ...SelectLanguageTutorialIntegrationTest.java | 338 ++++++++++++++++++ .../command/StartIntegrationTest.java | 12 +- 22 files changed, 568 insertions(+), 215 deletions(-) rename src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/{ChangeLanguage.java => SelectLanguageTutorial.java} (55%) create mode 100644 src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ShowHelp.java delete mode 100644 src/main/resources/template/callbackQuery/changeLanguage.vm create mode 100644 src/main/resources/template/callbackQuery/selectLanguageTutorial.vm delete mode 100644 src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguageIntegrationTest.java create mode 100644 src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/SelectLanguageTutorialIntegrationTest.java diff --git a/.markdownlint.yaml b/.markdownlint.yaml index f90ed06..552e9d8 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -1,2 +1,3 @@ MD013: line_length: 120 + code_blocks: false diff --git a/README.md b/README.md index cb9c81f..611bb8c 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ valid Telegram Bot token. To configure Webhook configuration for your bot, open up a terminal and type: ```shell -curl -F "url=https://example.com/api/tg" https://api.telegram.org/bot/setWebhook +curl -F "url=https://example.com/api/tg" \ + -F "allowed_updates=[\"message\", \"edited_message\", \"channel_post\", \"edited_channel_post\", \"inline_query\", \"choosen_inline_result\", \"callback_query\", \"poll\", \"poll_answer\", \"my_chat_member\", \"chat_member\", \"chat_join_request\"]" \ + https://api.telegram.org/bot/setWebhook ``` ## Building diff --git a/src/etc/stork/stork.yml b/src/etc/stork/stork.yml index ee568c7..09dd917 100644 --- a/src/etc/stork/stork.yml +++ b/src/etc/stork/stork.yml @@ -35,7 +35,7 @@ max_java_memory: 512 #min_java_memory_pct: 10 #max_java_memory_pct: 20 -# Try to create a symbolic link to java executable in /run with +# Try to createVelocityToolManager a symbolic link to java executable in /run with # the name of "-java" so that commands like "ps" will make it # easier to find your app symlink_java: true diff --git a/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java b/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java index b905c18..df124db 100644 --- a/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java +++ b/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java @@ -1,6 +1,7 @@ package com.github.polpetta.mezzotre.i18n; import java.util.Locale; +import java.util.ResourceBundle; import javax.inject.Inject; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.resource.loader.JarResourceLoader; @@ -20,7 +21,7 @@ public class LocalizedMessageFactory { this.velocityEngine = velocityEngine; } - public ToolManager create(Locale locale) { + public ToolManager createVelocityToolManager(Locale locale) { final ToolManager toolManager = new ToolManager(); toolManager.setVelocityEngine(velocityEngine); @@ -37,4 +38,8 @@ public class LocalizedMessageFactory { toolManager.configure(factoryConfiguration); return toolManager; } + + public ResourceBundle createResourceBundle(Locale locale) { + return ResourceBundle.getBundle("i18n/message", locale); + } } diff --git a/src/main/java/com/github/polpetta/mezzotre/orm/model/TgChat.java b/src/main/java/com/github/polpetta/mezzotre/orm/model/TgChat.java index eeebce9..8c559f5 100644 --- a/src/main/java/com/github/polpetta/mezzotre/orm/model/TgChat.java +++ b/src/main/java/com/github/polpetta/mezzotre/orm/model/TgChat.java @@ -3,7 +3,6 @@ package com.github.polpetta.mezzotre.orm.model; import com.github.polpetta.types.json.ChatContext; import io.ebean.annotation.DbJsonB; import io.ebean.annotation.Length; -import javax.annotation.Nullable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; @@ -41,18 +40,13 @@ public class TgChat extends Base { /** The locale to use when chatting. Defaults to {@code en-US} */ @NotNull - @Length(5) + @Length(10) @Column(columnDefinition = "varchar(5) not null default 'en-US'") private String locale; - /** - * See {@link #TgChat( Long, ChatContext, String)}. The default locale set here is {@code en-US} - */ - public TgChat(Long id, ChatContext chatContext) { - this.id = id; - this.chatContext = chatContext; - this.locale = "en"; - } + @NotNull + @Column(columnDefinition = "boolean default false") + private Boolean hasHelpBeenShown; /** * Build a new Telegram Chat @@ -61,11 +55,21 @@ public class TgChat extends Base { * @param chatContext the context of the chat, where possible metadata can be stored. See {@link * ChatContext} * @param locale a specific locale for this chat + * @param hasHelpBeenShown boolean indicating if the user has seen the /help command at least once */ - public TgChat(Long id, @Nullable ChatContext chatContext, String locale) { + public TgChat(Long id, ChatContext chatContext, String locale, boolean hasHelpBeenShown) { this.id = id; this.chatContext = chatContext; this.locale = locale; + this.hasHelpBeenShown = hasHelpBeenShown; + } + + /** + * See {@link #TgChat( Long, ChatContext, String,boolean)}. The default locale set here is {@code + * en-US}, and {@link #hasHelpBeenShown} is set to {@code false} + */ + public TgChat(Long id, ChatContext chatContext) { + this(id, chatContext, "en-US", false); } public Long getId() { @@ -89,6 +93,14 @@ public class TgChat extends Base { this.locale = locale; } + public Boolean getHasHelpBeenShown() { + return hasHelpBeenShown; + } + + public void setHasHelpBeenShown(Boolean hasHelpBeenShown) { + this.hasHelpBeenShown = hasHelpBeenShown; + } + @Override public String toString() { return "TgChat{" @@ -99,6 +111,8 @@ public class TgChat extends Base { + ", locale='" + locale + '\'' + + ", isTutorialComplete=" + + hasHelpBeenShown + '}'; } } diff --git a/src/main/java/com/github/polpetta/mezzotre/route/Telegram.java b/src/main/java/com/github/polpetta/mezzotre/route/Telegram.java index 1b21b4f..e6bd2ef 100644 --- a/src/main/java/com/github/polpetta/mezzotre/route/Telegram.java +++ b/src/main/java/com/github/polpetta/mezzotre/route/Telegram.java @@ -60,7 +60,7 @@ public class Telegram { public CompletableFuture incomingUpdate(Context context, Update update) { /* Steps: - 1 - Retrieve the chat. If new chat, create and entry in the db + 1 - Retrieve the chat. If new chat, createVelocityToolManager and entry in the db 2 - Check if the incoming payload is an inline event (keyboard, query, ecc). Possibly check if there is any context previously saved in the database to retrieve. In that case, process the payload accordingly 3 - If it is not an inline event, then process it as an incoming message */ diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/SelectLanguageTutorial.java similarity index 55% rename from src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java rename to src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/SelectLanguageTutorial.java index fa533ac..df940a8 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/SelectLanguageTutorial.java @@ -4,9 +4,15 @@ import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext; import com.github.polpetta.mezzotre.orm.model.query.QTgChat; +import com.github.polpetta.mezzotre.util.UUIDGenerator; +import com.github.polpetta.types.json.CallbackQueryMetadata; +import com.pengrad.telegrambot.model.Message; 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.ParseMode; import com.pengrad.telegrambot.request.BaseRequest; +import com.pengrad.telegrambot.request.EditMessageText; import com.pengrad.telegrambot.request.SendMessage; import io.vavr.control.Try; import java.nio.charset.StandardCharsets; @@ -24,18 +30,20 @@ import org.apache.velocity.util.StringBuilderWriter; import org.slf4j.Logger; /** - * ChangeLanguage event is related to support locale change for a particular chat + * SelectLanguageTutorial event is related to support locale change for a particular chat when the + * interaction with a new user starts * * @author Davide Polonio * @since 1.0 */ @Singleton -public class ChangeLanguage implements Processor { +public class SelectLanguageTutorial implements Processor { - public static final String EVENT_NAME = "changeLanguage"; + public static final String EVENT_NAME = "selectLanguageTutorial"; private final Executor threadPool; private final LocalizedMessageFactory localizedMessageFactory; private final Logger log; + private final UUIDGenerator uuidGenerator; /** * Additional fields that are related to {@code changeLanguage} event @@ -79,13 +87,15 @@ public class ChangeLanguage implements Processor { } @Inject - public ChangeLanguage( + public SelectLanguageTutorial( @Named("eventThreadPool") Executor threadPool, LocalizedMessageFactory localizedMessageFactory, - Logger log) { + Logger log, + UUIDGenerator uuidGenerator) { this.threadPool = threadPool; this.localizedMessageFactory = localizedMessageFactory; this.log = log; + this.uuidGenerator = uuidGenerator; } @Override @@ -99,13 +109,15 @@ public class ChangeLanguage implements Processor { return CompletableFuture.supplyAsync( () -> Optional.of(callbackQueryContext.getFields().getTelegramChatId()) - .filter( - chatId -> - chatId.longValue() != 0L - && !chatId.isNaN() - && !chatId.isInfinite() - && chatId != Double.MIN_VALUE) - .flatMap(chatId -> new QTgChat().id.eq(chatId.longValue()).findOneOrEmpty()) + .map(Double::longValue) + // If we're desperate, search in the message for the chat id + .or( + () -> + Optional.ofNullable(update.callbackQuery().message()) + .map(Message::messageId) + .map(Long::valueOf)) + .filter(chatId -> chatId != 0L && chatId != Long.MIN_VALUE) + .flatMap(chatId -> new QTgChat().id.eq(chatId).findOneOrEmpty()) .map( tgChat -> { tgChat.setLocale( @@ -117,6 +129,11 @@ public class ChangeLanguage implements Processor { Field.NewLanguage.getName(), Language.English.getLocale())); tgChat.save(); + log.trace( + "Locale for chat " + + tgChat.getId() + + " is now set in " + + tgChat.getLocale()); return tgChat; }) .orElseThrow( @@ -135,10 +152,13 @@ public class ChangeLanguage implements Processor { Try.of( () -> { final Locale locale = Locale.forLanguageTag(tgChat.getLocale()); - final ToolManager toolManager = localizedMessageFactory.create(locale); + final ToolManager toolManager = + localizedMessageFactory.createVelocityToolManager(locale); final VelocityContext velocityContext = new VelocityContext(toolManager.createContext()); + velocityContext.put("hasHelpBeenShown", tgChat.getHasHelpBeenShown()); + final StringBuilder content = new StringBuilder(); final StringBuilderWriter stringBuilderWriter = new StringBuilderWriter(content); @@ -146,7 +166,7 @@ public class ChangeLanguage implements Processor { toolManager .getVelocityEngine() .mergeTemplate( - "/template/callbackQuery/changeLanguage.vm", + "/template/callbackQuery/selectLanguageTutorial.vm", StandardCharsets.UTF_8.name(), velocityContext, stringBuilderWriter); @@ -156,7 +176,7 @@ public class ChangeLanguage implements Processor { }) .get(); - log.trace("ChangeLanguage event - message to send back: " + message); + log.trace("SelectLanguageTutorial event - message to send back: " + message); final String callBackGroupToDelete = callbackQueryContext.getEntryGroup(); final int delete = @@ -167,8 +187,53 @@ public class ChangeLanguage implements Processor { + " entries regarding callback group " + callBackGroupToDelete); - return Optional.of( - new SendMessage(tgChat.getId(), message).parseMode(ParseMode.Markdown)); + final Optional messageId = + Optional.ofNullable(update.callbackQuery().message()).map(Message::messageId); + BaseRequest baseRequest; + Optional helpButton = Optional.empty(); + if (!tgChat.getHasHelpBeenShown()) { + // Add a button to show all the possible commands + final String showMeTutorialString = + localizedMessageFactory + .createResourceBundle(Locale.forLanguageTag(tgChat.getLocale())) + .getString("button.showMeTutorial"); + + final CallbackQueryMetadata callbackQueryMetadata = + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withEvent(ShowHelp.EVENT_NAME) + .withTelegramChatId(tgChat.getId()) + .build(); + + final CallbackQueryContext callbackData = + new CallbackQueryContext( + uuidGenerator.generateAsString(), + uuidGenerator.generateAsString(), + callbackQueryMetadata); + callbackData.save(); + + helpButton = + Optional.of( + new InlineKeyboardMarkup( + new InlineKeyboardButton(showMeTutorialString) + .callbackData(callbackData.getId()))); + } + + if (messageId.isPresent()) { + log.trace("message id is present - editing old message"); + final EditMessageText editMessageText = + new EditMessageText(tgChat.getId(), messageId.get(), message) + .parseMode(ParseMode.Markdown); + helpButton.ifPresent(editMessageText::replyMarkup); + baseRequest = editMessageText; + } else { + log.trace("no message id - sending a new message"); + final SendMessage sendMessage = + new SendMessage(tgChat.getId(), message).parseMode(ParseMode.Markdown); + helpButton.ifPresent(sendMessage::replyMarkup); + baseRequest = sendMessage; + } + + return Optional.of(baseRequest); }, threadPool); } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ShowHelp.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ShowHelp.java new file mode 100644 index 0000000..6beb58e --- /dev/null +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ShowHelp.java @@ -0,0 +1,26 @@ +package com.github.polpetta.mezzotre.telegram.callbackquery; + +import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; +import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.request.BaseRequest; +import com.pengrad.telegrambot.request.SendMessage; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public class ShowHelp implements Processor { + + public static String EVENT_NAME = "showHelp"; + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public CompletableFuture>> process( + CallbackQueryContext callbackQueryContext, Update update) { + // TODO implement this method and put `hasHelpBeenShown` in tgChat to false + return CompletableFuture.completedFuture( + Optional.of(new SendMessage(callbackQueryContext.getFields().getTelegramChatId(), "TODO"))); + } +} diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/di/CallbackQuery.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/di/CallbackQuery.java index 02b7781..0e252d4 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/di/CallbackQuery.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/di/CallbackQuery.java @@ -1,7 +1,8 @@ package com.github.polpetta.mezzotre.telegram.callbackquery.di; -import com.github.polpetta.mezzotre.telegram.callbackquery.ChangeLanguage; import com.github.polpetta.mezzotre.telegram.callbackquery.Processor; +import com.github.polpetta.mezzotre.telegram.callbackquery.SelectLanguageTutorial; +import com.github.polpetta.mezzotre.telegram.callbackquery.ShowHelp; import com.google.inject.AbstractModule; import com.google.inject.Provides; import java.util.Set; @@ -12,7 +13,8 @@ public class CallbackQuery extends AbstractModule { @Provides @Singleton @Named("eventProcessors") - public Set getEventProcessor(ChangeLanguage changeLanguage) { - return Set.of(changeLanguage); + public Set getEventProcessor( + SelectLanguageTutorial selectLanguageTutorial, ShowHelp showHelp) { + return Set.of(selectLanguageTutorial, showHelp); } } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Processor.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Processor.java index 36dc493..c83673b 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Processor.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Processor.java @@ -30,6 +30,5 @@ public interface Processor { * @param update the update to process * @return a {@link CompletableFuture} with the result of the computation */ - // FIXME cannot be void - we don't want pesky side effects! CompletableFuture>> process(TgChat chat, Update update); } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java index 8c5aae5..adeca62 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java @@ -3,7 +3,7 @@ package com.github.polpetta.mezzotre.telegram.command; import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; import com.github.polpetta.mezzotre.orm.model.TgChat; -import com.github.polpetta.mezzotre.telegram.callbackquery.ChangeLanguage; +import com.github.polpetta.mezzotre.telegram.callbackquery.SelectLanguageTutorial; import com.github.polpetta.mezzotre.util.Clock; import com.github.polpetta.mezzotre.util.UUIDGenerator; import com.github.polpetta.types.json.CallbackQueryMetadata; @@ -76,7 +76,8 @@ public class Start implements Processor { Try.of( () -> { final Locale locale = Locale.forLanguageTag(chat.getLocale()); - final ToolManager toolManager = localizedMessageFactory.create(locale); + final ToolManager toolManager = + localizedMessageFactory.createVelocityToolManager(locale); final VelocityContext context = new VelocityContext(toolManager.createContext()); context.put("firstName", update.message().chat().firstName()); @@ -114,11 +115,11 @@ public class Start implements Processor { uuidGenerator.generateAsString(), groupId, new CallbackQueryMetadata.CallbackQueryMetadataBuilder() - .withEvent(ChangeLanguage.EVENT_NAME) + .withEvent(SelectLanguageTutorial.EVENT_NAME) .withTelegramChatId(update.message().chat().id()) .withAdditionalProperty( - ChangeLanguage.Field.NewLanguage.getName(), - ChangeLanguage.Language.English) + SelectLanguageTutorial.Field.NewLanguage.getName(), + SelectLanguageTutorial.Language.English.getLocale()) .build()); final CallbackQueryContext switchToItalian = @@ -126,20 +127,30 @@ public class Start implements Processor { uuidGenerator.generateAsString(), groupId, new CallbackQueryMetadata.CallbackQueryMetadataBuilder() - .withEvent(ChangeLanguage.EVENT_NAME) + .withEvent(SelectLanguageTutorial.EVENT_NAME) .withTelegramChatId(update.message().chat().id()) .withAdditionalProperty( - ChangeLanguage.Field.NewLanguage.getName(), - ChangeLanguage.Language.Italian) + SelectLanguageTutorial.Field.NewLanguage.getName(), + SelectLanguageTutorial.Language.Italian.getLocale()) .build()); + final String englishButton = + localizedMessageFactory + .createResourceBundle(Locale.US) + .getString("changeLanguage.english"); + final String italianButton = + localizedMessageFactory + .createResourceBundle(Locale.ITALY) + .getString("changeLanguage.italian"); + final SendMessage messageToSend = new SendMessage(chat.getId(), message) .parseMode(ParseMode.Markdown) .replyMarkup( new InlineKeyboardMarkup( - new InlineKeyboardButton("English").callbackData(switchToEnglish.getId()), - new InlineKeyboardButton("Italian") + new InlineKeyboardButton(englishButton) + .callbackData(switchToEnglish.getId()), + new InlineKeyboardButton(italianButton) .callbackData(switchToItalian.getId()))); switchToEnglish.save(); diff --git a/src/main/resources/db/migration/V1_0_0__Create_initial_db.sql b/src/main/resources/db/migration/V1_0_0__Create_initial_db.sql index 0146fa6..97b5e0a 100644 --- a/src/main/resources/db/migration/V1_0_0__Create_initial_db.sql +++ b/src/main/resources/db/migration/V1_0_0__Create_initial_db.sql @@ -2,7 +2,7 @@ create table callback_query_context ( id varchar(36) not null, entry_group varchar(36) not null, - fields json not null, + fields json not null default '{}'::json not null, entry_created timestamptz not null, entry_modified timestamptz not null, constraint pk_callback_query_context primary key (id) @@ -12,6 +12,7 @@ create table telegram_chat ( id bigint generated by default as identity not null, chat_context jsonb not null default '{}'::jsonb not null, locale varchar(5) not null default 'en-US' not null, + has_help_been_shown boolean default false not null, entry_created timestamptz not null, entry_modified timestamptz not null, constraint pk_telegram_chat primary key (id) diff --git a/src/main/resources/i18n/message.properties b/src/main/resources/i18n/message.properties index 1a07ce4..cce17e2 100644 --- a/src/main/resources/i18n/message.properties +++ b/src/main/resources/i18n/message.properties @@ -1,6 +1,11 @@ start.helloFirstName=Hello {0}! \ud83d\udc4b start.description=This is {0}, a simple bot focused on DnD content management! Please start by choosing a language down below \ud83d\udc47 -changeLanguage.drinkAction=*Proceeds to drink a potion with a strange, multicolor liquid* -changeLanguage.setLanguage=Thanks! Now that I drank this modified potion of {0} that I''ve found at the "Crystal Fermentary" magic potion shop yesterday I can speak with you in the language you prefer! -changeLanguage.instructions=You can always change your language settings by typing /changeLanguage in the chat. +selectLanguageTutorial.drinkAction=*Proceeds to drink a potion with a strange, multicolor liquid* +selectLanguageTutorial.setLanguage=Thanks! Now that I drank this modified potion of {0} that I''ve found at the "Crystal Fermentary" magic potion shop yesterday I can speak with you in the language that you prefer! +selectLanguageTutorial.instructions=You can always change your language settings by typing /selectLanguageTutorial in the chat. +changeLanguage.english=English +changeLanguage.italian=Italian spell.speakWithAnimals=Speak with animals +button.showMeTutorial=Show me what you can do! +help.notShownYet=It seems you haven''t checked out what I can do yet! To have a complete list of my abilities, type /help in chat at any time! +help.buttonBelow=Alternatively, you can click the button down below. diff --git a/src/main/resources/i18n/message_en_US.properties b/src/main/resources/i18n/message_en_US.properties index 1a07ce4..64ca1f3 100644 --- a/src/main/resources/i18n/message_en_US.properties +++ b/src/main/resources/i18n/message_en_US.properties @@ -1,6 +1,11 @@ start.helloFirstName=Hello {0}! \ud83d\udc4b start.description=This is {0}, a simple bot focused on DnD content management! Please start by choosing a language down below \ud83d\udc47 -changeLanguage.drinkAction=*Proceeds to drink a potion with a strange, multicolor liquid* -changeLanguage.setLanguage=Thanks! Now that I drank this modified potion of {0} that I''ve found at the "Crystal Fermentary" magic potion shop yesterday I can speak with you in the language you prefer! -changeLanguage.instructions=You can always change your language settings by typing /changeLanguage in the chat. +selectLanguageTutorial.drinkAction=*Proceeds to drink a potion with a strange, multicolor liquid* +selectLanguageTutorial.setLanguage=Thanks! Now that I drank this modified potion of {0} that I''ve found at the "Crystal Fermentary" magic potion shop yesterday I can speak with you in the language that you prefer! +selectLanguageTutorial.instructions=You can always change your language settings by typing /selectLanguageTutorial in the chat. +selectLanguageTutorial.english=English +selectLanguageTutorial.italian=Italian spell.speakWithAnimals=Speak with animals +button.showMeTutorial=Show me what you can do! +help.notShownYet=It seems you haven''t checked out what I can do yet! To have a complete list of my abilities, type /help in chat at any time! +help.buttonBelow=Alternatively, you can click the button down below. diff --git a/src/main/resources/i18n/message_it.properties b/src/main/resources/i18n/message_it.properties index 739ea44..1c13674 100644 --- a/src/main/resources/i18n/message_it.properties +++ b/src/main/resources/i18n/message_it.properties @@ -1,6 +1,11 @@ start.helloFirstName=Ciao {0}! \ud83d\udc4b start.description=Questo è {0}, un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto \ud83d\udc47 -changeLanguage.drinkAction=*Procede a bere una pozione al cui suo interno si trova uno strano liquido multicolore* -changeLanguage.setLanguage=Grazie! Ora che ho bevuto quest posizione modificata di {0} che ho trovato ieri al negozio di pozioni magiche la "Cristalleria Fermentatrice" posso parlare con te nel linguaggio che preferisci! -changeLanguage.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /changeLanguage nella chat. +selectLanguageTutorial.drinkAction=*Procede a bere una pozione al cui suo interno si trova uno strano liquido multicolore* +selectLanguageTutorial.setLanguage=Grazie! Ora che ho bevuto questa posizione modificata di {0} che ho trovato ieri al negozio di pozioni magiche la "Cristalleria Fermentatrice" posso parlare con te nel linguaggio che preferisci! +selectLanguageTutorial.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /selectLanguageTutorial nella chat. +selectLanguageTutorial.english=Inglese +selectLanguageTutorial.italian=Italiano spell.speakWithAnimals=Parlare con animali +button.showMeTutorial=Mostrami cosa puoi fare! +help.notShownYet=Sembra tu non abbia ancora visto cosa posso fare! Per avere una lista completa delle mie abilità, scrivi /help nella chat in qualsiasi momento! +help.buttonBelow=Alternativamente, puoi premere il bottone qui sotto. diff --git a/src/main/resources/i18n/message_it_IT.properties b/src/main/resources/i18n/message_it_IT.properties index 739ea44..1c13674 100644 --- a/src/main/resources/i18n/message_it_IT.properties +++ b/src/main/resources/i18n/message_it_IT.properties @@ -1,6 +1,11 @@ start.helloFirstName=Ciao {0}! \ud83d\udc4b start.description=Questo è {0}, un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto \ud83d\udc47 -changeLanguage.drinkAction=*Procede a bere una pozione al cui suo interno si trova uno strano liquido multicolore* -changeLanguage.setLanguage=Grazie! Ora che ho bevuto quest posizione modificata di {0} che ho trovato ieri al negozio di pozioni magiche la "Cristalleria Fermentatrice" posso parlare con te nel linguaggio che preferisci! -changeLanguage.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /changeLanguage nella chat. +selectLanguageTutorial.drinkAction=*Procede a bere una pozione al cui suo interno si trova uno strano liquido multicolore* +selectLanguageTutorial.setLanguage=Grazie! Ora che ho bevuto questa posizione modificata di {0} che ho trovato ieri al negozio di pozioni magiche la "Cristalleria Fermentatrice" posso parlare con te nel linguaggio che preferisci! +selectLanguageTutorial.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /selectLanguageTutorial nella chat. +selectLanguageTutorial.english=Inglese +selectLanguageTutorial.italian=Italiano spell.speakWithAnimals=Parlare con animali +button.showMeTutorial=Mostrami cosa puoi fare! +help.notShownYet=Sembra tu non abbia ancora visto cosa posso fare! Per avere una lista completa delle mie abilità, scrivi /help nella chat in qualsiasi momento! +help.buttonBelow=Alternativamente, puoi premere il bottone qui sotto. diff --git a/src/main/resources/template/callbackQuery/changeLanguage.vm b/src/main/resources/template/callbackQuery/changeLanguage.vm deleted file mode 100644 index 57fa7c0..0000000 --- a/src/main/resources/template/callbackQuery/changeLanguage.vm +++ /dev/null @@ -1,5 +0,0 @@ -_${i18n.changeLanguage.drinkAction}_ - -${i18n.changeLanguage.setLanguage.insert(${i18n.spell.speakWithAnimals})} - -${i18n.changeLanguage.instructions} \ No newline at end of file diff --git a/src/main/resources/template/callbackQuery/selectLanguageTutorial.vm b/src/main/resources/template/callbackQuery/selectLanguageTutorial.vm new file mode 100644 index 0000000..5a1a6b6 --- /dev/null +++ b/src/main/resources/template/callbackQuery/selectLanguageTutorial.vm @@ -0,0 +1,9 @@ +_${i18n.selectLanguageTutorial.drinkAction}_ + +${i18n.selectLanguageTutorial.setLanguage.insert(${i18n.spell.speakWithAnimals})} + +${i18n.selectLanguageTutorial.instructions} + +#if(${hasHelpBeenShown} == false) +${i18n.help.notShownYet} ${i18n.help.buttonBelow} +#end \ No newline at end of file diff --git a/src/test/java/com/github/polpetta/mezzotre/orm/model/TgChatIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/orm/model/TgChatIntegrationTest.java index edf757a..1d051d7 100644 --- a/src/test/java/com/github/polpetta/mezzotre/orm/model/TgChatIntegrationTest.java +++ b/src/test/java/com/github/polpetta/mezzotre/orm/model/TgChatIntegrationTest.java @@ -54,7 +54,7 @@ class TgChatIntegrationTest { .withPreviousMessageUnixTimestampInSeconds(42) .build(); - final TgChat user = new TgChat(1234L, chatContext, "en"); + final TgChat user = new TgChat(1234L, chatContext, "en", false); user.save(); final String query = "select * from telegram_chat where id = ?"; @@ -63,6 +63,7 @@ class TgChatIntegrationTest { assertNotNull(savedChat); assertEquals(1234L, savedChat.getLong("id")); assertEquals("en", savedChat.getString("locale")); + assertFalse(savedChat.getBoolean("has_help_been_shown")); assertNotNull(savedChat.get("chat_context")); assertEquals("jsonb", ((PGobject) savedChat.get("chat_context")).getType()); assertEquals( @@ -82,13 +83,14 @@ class TgChatIntegrationTest { .withPreviousMessageUnixTimestampInSeconds(42) .build(); - final String insertQuery = "insert into telegram_chat values (?, ?::jsonb, ?, ?, ?)"; + final String insertQuery = "insert into telegram_chat values (?, ?::jsonb, ?, ?, ?, ?)"; final int affectedRows = database .sqlUpdate(insertQuery) .setParameter(1234L) .setParameter(objectMapper.writeValueAsString(chatContext)) .setParameter("en-US") + .setParameter(false) .setParameter(timestampFromUnixEpoch) .setParameter(timestampFromUnixEpoch) .execute(); @@ -97,6 +99,7 @@ class TgChatIntegrationTest { final TgChat got = new QTgChat().id.eq(1234L).findOne(); assertNotNull(got); + assertFalse(got.getHasHelpBeenShown()); final ChatContext gotChatContext = got.getChatContext(); assertNotNull(gotChatContext); assertEquals("/start", gotChatContext.getStage()); diff --git a/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguageIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguageIntegrationTest.java deleted file mode 100644 index 26056a1..0000000 --- a/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguageIntegrationTest.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.github.polpetta.mezzotre.telegram.callbackquery; - -import static org.junit.jupiter.api.Assertions.*; - -import com.github.polpetta.mezzotre.helper.Loader; -import com.github.polpetta.mezzotre.helper.TestConfig; -import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; -import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; -import com.github.polpetta.mezzotre.orm.model.TgChat; -import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext; -import com.github.polpetta.mezzotre.orm.model.query.QTgChat; -import com.github.polpetta.types.json.CallbackQueryMetadata; -import com.github.polpetta.types.json.ChatContext; -import com.google.gson.Gson; -import com.pengrad.telegrambot.model.Update; -import com.pengrad.telegrambot.request.BaseRequest; -import com.pengrad.telegrambot.request.SendMessage; -import io.ebean.Database; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; -import org.junit.jupiter.api.*; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -@Tag("slow") -@Tag("database") -@Tag("velocity") -@Testcontainers -class ChangeLanguageIntegrationTest { - - private static Gson gson; - - @Container - private final PostgreSQLContainer postgresServer = - new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE); - - private Database database; - private ChangeLanguage changeLanguage; - - @BeforeAll - static void beforeAll() { - gson = new Gson(); - } - - @BeforeEach - void setUp() throws Exception { - database = - Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer)); - - changeLanguage = - new ChangeLanguage( - Executors.newSingleThreadExecutor(), - new LocalizedMessageFactory(Loader.defaultVelocityEngine()), - LoggerFactory.getLogger(ChangeLanguage.class)); - } - - private static Stream getTestLocales() { - return Stream.of( - Arguments.of( - ChangeLanguage.Language.Italian, - "_*Procede a bere una pozione al cui suo interno si trova uno strano liquido" - + " multicolore*_\n" - + "\n" - + "Grazie! Ora che ho bevuto quest posizione modificata di Parlare con animali che" - + " ho trovato ieri al negozio di pozioni magiche la \"Cristalleria Fermentatrice\"" - + " posso parlare con te nel linguaggio che preferisci!\n" - + "\n" - + "Puoi sempre cambiare le preferenze della tua lingua scrivendo /changeLanguage" - + " nella chat.", - "en-US"), - Arguments.of( - ChangeLanguage.Language.English, - "_*Proceeds to drink a potion with a strange, multicolor liquid*_\n" - + "\n" - + "Thanks! Now that I drank this modified potion of Speak with animals that I've" - + " found at the \"Crystal Fermentary\" magic potion shop yesterday I can speak" - + " with you in the language you prefer!\n" - + "\n" - + "You can always change your language settings by typing /changeLanguage in the" - + " chat.", - "it-IT")); - } - - @ParameterizedTest - @Timeout(value = 1, unit = TimeUnit.MINUTES) - @MethodSource("getTestLocales") - void shouldProcessChangeLanguageToDesiredOne( - ChangeLanguage.Language language, String expectedResult, String startingLocale) - throws Exception { - - final Update update = - gson.fromJson( - "{\n" - + "\"update_id\":10000,\n" - + "\"callback_query\":{\n" - + " \"id\": \"4382bfdwdsb323b2d9\",\n" - + " \"from\":{\n" - + " \"last_name\":\"Test Lastname\",\n" - + " \"type\": \"private\",\n" - + " \"id\":1111111,\n" - + " \"first_name\":\"Test Firstname\",\n" - + " \"username\":\"Testusername\"\n" - + " },\n" - + " \"data\": \"Data from button callback\",\n" - + " \"inline_message_id\": \"1234csdbsk4839\"\n" - + "}\n" - + "}", - Update.class); - - final long tgChatId = 1111111L; - final ChatContext chatContext = new ChatContext(); - final TgChat tgChat = new TgChat(tgChatId, chatContext, startingLocale); - tgChat.save(); - - final CallbackQueryMetadata callbackQueryMetadata = - new CallbackQueryMetadata.CallbackQueryMetadataBuilder() - .withEvent("changeLanguage") - .withTelegramChatId(tgChatId) - .withAdditionalProperty( - ChangeLanguage.Field.NewLanguage.getName(), language.getLocale()) - .build(); - final String entryGroupId = "2e67774a-e4e4-4369-a414-a7f8bfe74b80"; - final CallbackQueryContext changeLanguageCallbackQueryContext = - new CallbackQueryContext( - "c018108f-6612-4848-8fca-cf301460d4eb", entryGroupId, callbackQueryMetadata); - changeLanguageCallbackQueryContext.save(); - - final CompletableFuture>> processFuture = - changeLanguage.process(changeLanguageCallbackQueryContext, update); - final Optional> gotResponseOpt = processFuture.get(); - final SendMessage gotMessage = (SendMessage) gotResponseOpt.get(); - assertEquals(expectedResult, gotMessage.getParameters().get("text")); - - final TgChat retrievedTgChat = new QTgChat().id.eq(tgChatId).findOne(); - assertNotNull(retrievedTgChat); - assertEquals(language.getLocale(), retrievedTgChat.getLocale()); - - assertEquals(0, new QCallbackQueryContext().entryGroup.eq(entryGroupId).findCount()); - } -} diff --git a/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/SelectLanguageTutorialIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/SelectLanguageTutorialIntegrationTest.java new file mode 100644 index 0000000..a4e53c1 --- /dev/null +++ b/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/SelectLanguageTutorialIntegrationTest.java @@ -0,0 +1,338 @@ +package com.github.polpetta.mezzotre.telegram.callbackquery; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.github.polpetta.mezzotre.helper.Loader; +import com.github.polpetta.mezzotre.helper.TestConfig; +import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; +import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; +import com.github.polpetta.mezzotre.orm.model.TgChat; +import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext; +import com.github.polpetta.mezzotre.orm.model.query.QTgChat; +import com.github.polpetta.mezzotre.util.UUIDGenerator; +import com.github.polpetta.types.json.CallbackQueryMetadata; +import com.github.polpetta.types.json.ChatContext; +import com.google.gson.Gson; +import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.model.request.InlineKeyboardButton; +import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup; +import com.pengrad.telegrambot.request.BaseRequest; +import com.pengrad.telegrambot.request.EditMessageText; +import com.pengrad.telegrambot.request.SendMessage; +import io.ebean.Database; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Tag("slow") +@Tag("database") +@Tag("velocity") +@Testcontainers +class SelectLanguageTutorialIntegrationTest { + + private static Gson gson; + + @Container + private final PostgreSQLContainer postgresServer = + new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE); + + private Database database; + private SelectLanguageTutorial selectLanguageTutorial; + private UUIDGenerator fakeUUIDGenerator; + + @BeforeAll + static void beforeAll() { + gson = new Gson(); + } + + @BeforeEach + void setUp() throws Exception { + database = + Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer)); + + fakeUUIDGenerator = mock(UUIDGenerator.class); + + selectLanguageTutorial = + new SelectLanguageTutorial( + Executors.newSingleThreadExecutor(), + new LocalizedMessageFactory(Loader.defaultVelocityEngine()), + LoggerFactory.getLogger(SelectLanguageTutorial.class), + fakeUUIDGenerator); + } + + private static Stream getTestLocales() { + return Stream.of( + Arguments.of( + SelectLanguageTutorial.Language.Italian, + "_*Procede a bere una pozione al cui suo interno si trova uno strano liquido" + + " multicolore*_\n" + + "\n" + + "Grazie! Ora che ho bevuto questa posizione modificata di Parlare con animali che" + + " ho trovato ieri al negozio di pozioni magiche la \"Cristalleria Fermentatrice\"" + + " posso parlare con te nel linguaggio che preferisci!\n" + + "\n" + + "Puoi sempre cambiare le preferenze della tua lingua scrivendo" + + " /selectLanguageTutorial nella chat.\n" + + "\n" + + "Sembra tu non abbia ancora visto cosa posso fare! Per avere una lista completa" + + " delle mie abilità, scrivi /help nella chat in qualsiasi momento!" + + " Alternativamente, puoi premere il bottone qui sotto.\n", + "en-US", + "Mostrami cosa puoi fare!", + false), + Arguments.of( + SelectLanguageTutorial.Language.English, + "_*Proceeds to drink a potion with a strange, multicolor liquid*_\n" + + "\n" + + "Thanks! Now that I drank this modified potion of Speak with animals that I've" + + " found at the \"Crystal Fermentary\" magic potion shop yesterday I can speak" + + " with you in the language that you prefer!\n" + + "\n" + + "You can always change your language settings by typing /selectLanguageTutorial" + + " in the chat.\n" + + "\n" + + "It seems you haven't checked out what I can do yet! To have a complete list of" + + " my abilities, type /help in chat at any time! Alternatively, you can click the" + + " button down below.\n", + "it-IT", + "Show me what you can do!", + false), + Arguments.of( + SelectLanguageTutorial.Language.Italian, + "_*Procede a bere una pozione al cui suo interno si trova uno strano liquido" + + " multicolore*_\n" + + "\n" + + "Grazie! Ora che ho bevuto questa posizione modificata di Parlare con animali che" + + " ho trovato ieri al negozio di pozioni magiche la \"Cristalleria Fermentatrice\"" + + " posso parlare con te nel linguaggio che preferisci!\n" + + "\n" + + "Puoi sempre cambiare le preferenze della tua lingua scrivendo" + + " /selectLanguageTutorial nella chat.\n\n", + "en-US", + "Mostrami cosa puoi fare!", + true), + Arguments.of( + SelectLanguageTutorial.Language.English, + "_*Proceeds to drink a potion with a strange, multicolor liquid*_\n" + + "\n" + + "Thanks! Now that I drank this modified potion of Speak with animals that I've" + + " found at the \"Crystal Fermentary\" magic potion shop yesterday I can speak" + + " with you in the language that you prefer!\n" + + "\n" + + "You can always change your language settings by typing /selectLanguageTutorial" + + " in the chat.\n\n", + "it-IT", + "Show me what you can do!", + true)); + } + + @ParameterizedTest + @Timeout(value = 1, unit = TimeUnit.MINUTES) + @MethodSource("getTestLocales") + void shouldProcessChangeLanguageToDesiredOneSendMessage( + SelectLanguageTutorial.Language language, + String expectedResult, + String startingLocale, + String buttonLocale, + boolean hasHelpBeenShown) + throws Exception { + + when(fakeUUIDGenerator.generateAsString()) + .thenReturn("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5") + .thenReturn("16507fbd-9f28-48a8-9de1-3ea1c943af67"); + + final Update update = + gson.fromJson( + "{\n" + + "\"update_id\":10000,\n" + + "\"callback_query\":{\n" + + " \"id\": \"4382bfdwdsb323b2d9\",\n" + + " \"from\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"type\": \"private\",\n" + + " \"id\":1111111,\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"data\": \"Data from button callback\",\n" + + " \"inline_message_id\": \"1234csdbsk4839\"\n" + + "}\n" + + "}", + Update.class); + + final long tgChatId = 1111111L; + final ChatContext chatContext = new ChatContext(); + final TgChat tgChat = new TgChat(tgChatId, chatContext, startingLocale, hasHelpBeenShown); + tgChat.save(); + + final CallbackQueryMetadata callbackQueryMetadata = + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withEvent("selectLanguageTutorial") + .withTelegramChatId(tgChatId) + .withAdditionalProperty( + SelectLanguageTutorial.Field.NewLanguage.getName(), language.getLocale()) + .build(); + final String entryGroupId = "2e67774a-e4e4-4369-a414-a7f8bfe74b80"; + final CallbackQueryContext changeLanguageCallbackQueryContext = + new CallbackQueryContext( + "c018108f-6612-4848-8fca-cf301460d4eb", entryGroupId, callbackQueryMetadata); + changeLanguageCallbackQueryContext.save(); + + final CompletableFuture>> processFuture = + selectLanguageTutorial.process(changeLanguageCallbackQueryContext, update); + final Optional> gotResponseOpt = processFuture.get(); + final SendMessage gotMessage = (SendMessage) gotResponseOpt.get(); + assertEquals(expectedResult, gotMessage.getParameters().get("text")); + + final InlineKeyboardButton[][] replyMarkups = + ((InlineKeyboardMarkup) + gotMessage.getParameters().getOrDefault("reply_markup", new InlineKeyboardMarkup())) + .inlineKeyboard(); + final List keyboardButtons = + Stream.of(replyMarkups).flatMap(Stream::of).toList(); + if (!hasHelpBeenShown) { + assertEquals(1, keyboardButtons.size()); + assertEquals(buttonLocale, keyboardButtons.get(0).text()); + + verify(fakeUUIDGenerator, times(2)).generateAsString(); + assertEquals( + 1, new QCallbackQueryContext().id.eq("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5").findCount()); + } else { + assertEquals(0, keyboardButtons.size()); + assertEquals(0, new QCallbackQueryContext().findCount()); + } + + final TgChat retrievedTgChat = new QTgChat().id.eq(tgChatId).findOne(); + assertNotNull(retrievedTgChat); + assertEquals(language.getLocale(), retrievedTgChat.getLocale()); + + assertEquals(0, new QCallbackQueryContext().entryGroup.eq(entryGroupId).findCount()); + } + + @ParameterizedTest + @Timeout(value = 1, unit = TimeUnit.MINUTES) + @MethodSource("getTestLocales") + void shouldProcessChangeLanguageToDesiredOneEditMessage( + SelectLanguageTutorial.Language language, + String expectedResult, + String startingLocale, + String buttonLocale, + boolean hasHelpBeenShown) + throws Exception { + + when(fakeUUIDGenerator.generateAsString()) + .thenReturn("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5") + .thenReturn("16507fbd-9f28-48a8-9de1-3ea1c943af67"); + + final Update update = + gson.fromJson( + "{\n" + + " \"update_id\": 10000,\n" + + " \"callback_query\": {\n" + + " \"id\": \"4382bfdwdsb323b2d9\",\n" + + " \"from\": {\n" + + " \"last_name\": \"Test Lastname\",\n" + + " \"type\": \"private\",\n" + + " \"id\": 1111111,\n" + + " \"first_name\": \"Test Firstname\",\n" + + " \"username\": \"Testusername\"\n" + + " },\n" + + " \"message\": {\n" + + " \"message_id\": 2631,\n" + + " \"from\": {\n" + + " \"id\": 244745330,\n" + + " \"is_bot\": true,\n" + + " \"first_name\": \"test\",\n" + + " \"username\": \"test\"\n" + + " },\n" + + " \"date\": 1680276288,\n" + + " \"chat\": {\n" + + " \"id\": 4772108,\n" + + " \"type\": \"private\",\n" + + " \"username\": \"userTest\",\n" + + " \"first_name\": \"First Name\",\n" + + " \"last_name\": \"Last Name\"\n" + + " },\n" + + " \"text\": \"Previous message content\",\n" + + " \"reply_markup\": {\n" + + " \"inline_keyboard\": [\n" + + " [\n" + + " {\n" + + " \"text\": \"English\",\n" + + " \"callback_data\": \"b345ea60-391a-40a4-96ac-8c36c4366498\"\n" + + " },\n" + + " {\n" + + " \"text\": \"Italian\",\n" + + " \"callback_data\": \"ab6487fa-b010-4eb6-a316-5b60f3b6bc1e\"\n" + + " }\n" + + " ]\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"data\": \"Data from button callback\",\n" + + " \"inline_message_id\": \"1234csdbsk4839\"\n" + + " }\n" + + "}", + Update.class); + + final long tgChatId = 1111111L; + final ChatContext chatContext = new ChatContext(); + final TgChat tgChat = new TgChat(tgChatId, chatContext, startingLocale, hasHelpBeenShown); + tgChat.save(); + + final CallbackQueryMetadata callbackQueryMetadata = + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withEvent("selectLanguageTutorial") + .withTelegramChatId(tgChatId) + .withAdditionalProperty( + SelectLanguageTutorial.Field.NewLanguage.getName(), language.getLocale()) + .build(); + final String entryGroupId = "2e67774a-e4e4-4369-a414-a7f8bfe74b80"; + final CallbackQueryContext changeLanguageCallbackQueryContext = + new CallbackQueryContext( + "c018108f-6612-4848-8fca-cf301460d4eb", entryGroupId, callbackQueryMetadata); + changeLanguageCallbackQueryContext.save(); + + final CompletableFuture>> processFuture = + selectLanguageTutorial.process(changeLanguageCallbackQueryContext, update); + final Optional> gotResponseOpt = processFuture.get(); + final EditMessageText gotMessage = (EditMessageText) gotResponseOpt.get(); + assertEquals(expectedResult, gotMessage.getParameters().get("text")); + + final InlineKeyboardButton[][] replyMarkups = + ((InlineKeyboardMarkup) + gotMessage.getParameters().getOrDefault("reply_markup", new InlineKeyboardMarkup())) + .inlineKeyboard(); + final List keyboardButtons = + Stream.of(replyMarkups).flatMap(Stream::of).toList(); + if (!hasHelpBeenShown) { + assertEquals(1, keyboardButtons.size()); + assertEquals(buttonLocale, keyboardButtons.get(0).text()); + + verify(fakeUUIDGenerator, times(2)).generateAsString(); + assertEquals( + 1, new QCallbackQueryContext().id.eq("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5").findCount()); + } else { + assertEquals(0, keyboardButtons.size()); + assertEquals(0, new QCallbackQueryContext().findCount()); + } + + final TgChat retrievedTgChat = new QTgChat().id.eq(tgChatId).findOne(); + assertNotNull(retrievedTgChat); + assertEquals(language.getLocale(), retrievedTgChat.getLocale()); + + assertEquals(0, new QCallbackQueryContext().entryGroup.eq(entryGroupId).findCount()); + } +} diff --git a/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java index 08999a8..cfc3e90 100644 --- a/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java +++ b/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java @@ -7,6 +7,7 @@ import com.github.polpetta.mezzotre.helper.Loader; import com.github.polpetta.mezzotre.helper.TestConfig; import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; import com.github.polpetta.mezzotre.orm.model.TgChat; +import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext; import com.github.polpetta.mezzotre.orm.model.query.QTgChat; import com.github.polpetta.mezzotre.util.Clock; import com.github.polpetta.mezzotre.util.UUIDGenerator; @@ -149,7 +150,7 @@ class StartIntegrationTest { .thenReturn("16507fbd-9f28-48a8-9de1-3ea1c943af67") .thenReturn("0b0ac18e-f621-484e-aa8d-9b176be5b930"); - final TgChat tgChat = new TgChat(1111111L, new ChatContext(), "en-US"); + final TgChat tgChat = new TgChat(1111111L, new ChatContext(), "en-US", false); final Update update = gson.fromJson( @@ -189,6 +190,15 @@ class StartIntegrationTest { + " choosing a language down below \uD83D\uDC47", message); assertEquals(1111111L, (Long) gotMessage.getParameters().get("chat_id")); + + verify(fakeUUIDGenerator, times(3)).generateAsString(); + assertEquals( + 2, + new QCallbackQueryContext() + .entryGroup + .eq("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5") + .findCount()); + assertEquals(2, new QCallbackQueryContext().findCount()); } @Test -- 2.40.1 From 56882c1c4640a33f7656600f69a637277f6f31aa Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Fri, 31 Mar 2023 18:51:35 +0200 Subject: [PATCH 5/9] docs: add missing Javadoc --- src/etc/stork/stork.yml | 2 +- .../i18n/LocalizedMessageFactory.java | 21 ++++++++++++++++ .../orm/model/CallbackQueryContext.java | 4 ++++ .../telegram/callbackquery/Dispatcher.java | 19 +++++++++++++++ .../EventProcessorNotFoundException.java | 6 +++++ .../telegram/callbackquery/Processor.java | 24 +++++++++++++++++++ 6 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/etc/stork/stork.yml b/src/etc/stork/stork.yml index 09dd917..ee568c7 100644 --- a/src/etc/stork/stork.yml +++ b/src/etc/stork/stork.yml @@ -35,7 +35,7 @@ max_java_memory: 512 #min_java_memory_pct: 10 #max_java_memory_pct: 20 -# Try to createVelocityToolManager a symbolic link to java executable in /run with +# Try to create a symbolic link to java executable in /run with # the name of "-java" so that commands like "ps" will make it # easier to find your app symlink_java: true diff --git a/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java b/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java index df124db..97a0c8b 100644 --- a/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java +++ b/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java @@ -12,6 +12,13 @@ import org.apache.velocity.tools.config.FactoryConfiguration; import org.apache.velocity.tools.config.ToolConfiguration; import org.apache.velocity.tools.config.ToolboxConfiguration; +/** + * This class provides utility to create {@link ToolManager} for {@link VelocityEngine} or to + * retrieve simple localized strings from the system. + * + * @author Davide Polonio + * @since 1.0 + */ public class LocalizedMessageFactory { private final VelocityEngine velocityEngine; @@ -21,6 +28,14 @@ public class LocalizedMessageFactory { this.velocityEngine = velocityEngine; } + /** + * Provide a {@link ToolManager} completely setup. Localization will be taken from jar resources, + * and {@link LocalizedTool} can be used in Velocity templates to automatically access them. + * + * @param locale the language needed + * @return a {@link ToolManager} ready with the desired locale, if it exists. Fallback to english + * otherwise + */ public ToolManager createVelocityToolManager(Locale locale) { final ToolManager toolManager = new ToolManager(); @@ -39,6 +54,12 @@ public class LocalizedMessageFactory { return toolManager; } + /** + * Creates a {@link ResourceBundle} object that can be used to access single localized strings + * + * @param locale the language desired to retrieve the translated strings + * @return a {@link ResourceBundle} with the provided locale + */ public ResourceBundle createResourceBundle(Locale locale) { return ResourceBundle.getBundle("i18n/message", locale); } diff --git a/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java b/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java index 8cf1ef7..3687d12 100644 --- a/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java +++ b/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java @@ -28,6 +28,10 @@ public class CallbackQueryContext extends Base { @Length(36) private final String id; + /** + * Each callback can belong to a particular group. In that case, this variable will be the same + * for all the related {@link CallbackQueryMetadata} + */ @NotNull @Length(36) private final String entryGroup; diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java index 91037ee..a27d181 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java @@ -11,6 +11,14 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; +/** + * This class dispatches incoming {@link Update} containing a {@link + * com.pengrad.telegrambot.model.CallbackQuery} entry to the right {@link Processor} that will take + * care of processing them. + * + * @author Davide Polonio + * @since 1.0 + */ @Singleton public class Dispatcher { @@ -25,6 +33,17 @@ public class Dispatcher { this.threadPool = threadPool; } + /** + * This method searches for the right {@link Processor} installed in the system. A {@link + * CallbackQueryContext} has to be previously saved in the database in order for this method to + * find the right event to process. Once the corresponding {@link Processor} is found, the + * computation is delegated to it + * + * @param callbackQueryContext the context coming from the database storage - cannot be null + * @param update the incoming {@link Update} coming from Telegram + * @return a {@link CompletableFuture} containing an {@link Optional} that may or not have a + * {@link BaseRequest} response to send back to Telegram. + */ public CompletableFuture>> dispatch( CallbackQueryContext callbackQueryContext, Update update) { return CompletableFuture.completedFuture(update) diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/EventProcessorNotFoundException.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/EventProcessorNotFoundException.java index 16f2b8e..dbb23cf 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/EventProcessorNotFoundException.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/EventProcessorNotFoundException.java @@ -1,5 +1,11 @@ package com.github.polpetta.mezzotre.telegram.callbackquery; +/** + * Wrapper exception when a valid {@link Processor} is not found in the system + * + * @author Davide Polonio + * @since 1.0 + */ public class EventProcessorNotFoundException extends RuntimeException { public EventProcessorNotFoundException() {} diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Processor.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Processor.java index e37bbb1..021a177 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Processor.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Processor.java @@ -6,9 +6,33 @@ import com.pengrad.telegrambot.request.BaseRequest; import java.util.Optional; import java.util.concurrent.CompletableFuture; +/** + * A processor is an object which is able to process an incoming Telegram {@link Update} containing + * a {@link com.pengrad.telegrambot.model.CallbackQuery}. Any event of this type is related to a + * previous message. + * + * @author Davide Polonio + * @since 1.0 + */ public interface Processor { + + /** + * The even name this processor is able to process + * + * @return a {@link String} containig the name of the event supported + */ String getEventName(); + /** + * Process the current event + * + * @param callbackQueryContext the corresponding even {@link CallbackQueryContext} previously + * stored in the database + * @param update the Telegram {@link Update} containing a {@link + * com.pengrad.telegrambot.model.CallbackQuery} incoming Telegram + * @return a {@link CompletableFuture} containing an {@link Optional} that may or maybe not have a + * {@link BaseRequest}, that is the response that will be sent back to Telegram + */ CompletableFuture>> process( CallbackQueryContext callbackQueryContext, Update update); } -- 2.40.1 From 7b72b3c8e9d9274790242e4cb46058192c4eded3 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Fri, 31 Mar 2023 19:03:12 +0200 Subject: [PATCH 6/9] chore: add FIXME for future iterations --- .../polpetta/mezzotre/telegram/callbackquery/Dispatcher.java | 2 ++ .../com/github/polpetta/mezzotre/telegram/command/Router.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java index a27d181..cf3f67e 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java @@ -53,6 +53,8 @@ public class Dispatcher { .flatMap( eventName -> tgEventProcessors.stream() + // FIXME that fucking stupid, why iterate over, just use a map! Make + // mapping at startup then we're gucci for the rest of the run .filter(processor -> processor.getEventName().equals(eventName)) .findAny()) .map(processor -> processor.process(callbackQueryContext, update)) diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java index 848c4d3..69ca7b9 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java @@ -61,6 +61,8 @@ public class Router { .flatMap( command -> tgCommandProcessors.stream() + // FIXME that fucking stupid, why iterate over, just use a map! Make + // mapping at startup then we're gucci for the rest of the run .filter(ex -> ex.getTriggerKeyword().equals(command)) .findAny()) .map(executor -> executor.process(chat, update)) -- 2.40.1 From 480f2e79efb1b6b0236d73ea8ced7c74133d17e4 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Mon, 3 Apr 2023 09:29:14 +0200 Subject: [PATCH 7/9] chore: fix typo --- .../polpetta/mezzotre/telegram/callbackquery/Dispatcher.java | 4 ++-- .../com/github/polpetta/mezzotre/telegram/command/Router.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java index cf3f67e..3d0c3fc 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Dispatcher.java @@ -53,8 +53,8 @@ public class Dispatcher { .flatMap( eventName -> tgEventProcessors.stream() - // FIXME that fucking stupid, why iterate over, just use a map! Make - // mapping at startup then we're gucci for the rest of the run + // FIXME this is fucking stupid, why iterate over, just use a map! + // Make mapping at startup then we're gucci for the rest of the run .filter(processor -> processor.getEventName().equals(eventName)) .findAny()) .map(processor -> processor.process(callbackQueryContext, update)) diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java index 69ca7b9..5ff9eca 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Router.java @@ -61,8 +61,8 @@ public class Router { .flatMap( command -> tgCommandProcessors.stream() - // FIXME that fucking stupid, why iterate over, just use a map! Make - // mapping at startup then we're gucci for the rest of the run + // FIXME this is fucking stupid, why iterate over, just use a map! + // Make mapping at startup then we're gucci for the rest of the run .filter(ex -> ex.getTriggerKeyword().equals(command)) .findAny()) .map(executor -> executor.process(chat, update)) -- 2.40.1 From d2a2f8013532649baa64bc361db7dca118780904 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Mon, 3 Apr 2023 09:37:12 +0200 Subject: [PATCH 8/9] chore: use variable instead or hardcoding strings --- .../polpetta/mezzotre/i18n/LocalizedMessageFactory.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java b/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java index 97a0c8b..36db9e4 100644 --- a/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java +++ b/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java @@ -21,6 +21,8 @@ import org.apache.velocity.tools.config.ToolboxConfiguration; */ public class LocalizedMessageFactory { + private static final String MESSAGE_PATH = "i18n/message"; + private final VelocityEngine velocityEngine; @Inject @@ -47,7 +49,7 @@ public class LocalizedMessageFactory { toolConfiguration.setClassname(LocalizedTool.class.getName()); toolConfiguration.setProperty("file.resource.loader.class", JarResourceLoader.class.getName()); toolConfiguration.setProperty(ToolContext.LOCALE_KEY, locale); - toolConfiguration.setProperty(LocalizedTool.BUNDLES_KEY, "i18n/message"); + toolConfiguration.setProperty(LocalizedTool.BUNDLES_KEY, MESSAGE_PATH); toolboxConfiguration.addTool(toolConfiguration); factoryConfiguration.addToolbox(toolboxConfiguration); toolManager.configure(factoryConfiguration); @@ -61,6 +63,6 @@ public class LocalizedMessageFactory { * @return a {@link ResourceBundle} with the provided locale */ public ResourceBundle createResourceBundle(Locale locale) { - return ResourceBundle.getBundle("i18n/message", locale); + return ResourceBundle.getBundle(MESSAGE_PATH, locale); } } -- 2.40.1 From 4aa6b371da24a302a1c54092a78dcb692d934eb3 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Mon, 3 Apr 2023 10:33:36 +0200 Subject: [PATCH 9/9] feat: return old locale instead of putting english --- .../telegram/callbackquery/SelectLanguageTutorial.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/SelectLanguageTutorial.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/SelectLanguageTutorial.java index df940a8..c52ad0d 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/SelectLanguageTutorial.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/SelectLanguageTutorial.java @@ -126,8 +126,7 @@ public class SelectLanguageTutorial implements Processor { .getFields() .getAdditionalProperties() .getOrDefault( - Field.NewLanguage.getName(), - Language.English.getLocale())); + Field.NewLanguage.getName(), tgChat.getLocale())); tgChat.save(); log.trace( "Locale for chat " -- 2.40.1