feat: first implementation of help command, wip
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
00bac97e59
commit
c4db3be4cb
|
@ -36,22 +36,22 @@ curl -F "url=https://example.com/api/tg" \
|
|||
Build is achieved through Maven. To build a `jar` run:
|
||||
|
||||
```shell
|
||||
mvn package -DskipTests=true -Dmaven.test.skip=true -Dmaven.site.skip=true -Dmaven.javadoc.skip=true
|
||||
./mvnw package -DskipTests=true -Dmaven.test.skip=true -Dmaven.site.skip=true -Dmaven.javadoc.skip=true
|
||||
```
|
||||
|
||||
In the `target/` folder wou will find an uber-jar and optionally the possibility to run it via a script and to setup
|
||||
In the `target/` folder you will find an uber-jar and optionally the possibility to run it via a script and to setup
|
||||
auto-startup via systemd or openrc units.
|
||||
|
||||
## Developing
|
||||
|
||||
### Automatic testing
|
||||
|
||||
You can simply run tests with `mvn test`. This will run `UT` and `IT` tests together.
|
||||
You can simply run tests with `./mvnw test`. This will run `UT` and `IT` tests together.
|
||||
|
||||
### Manual testing
|
||||
|
||||
For a manual approach, just open a terminal and type `mvn jooby:run`. Assuming you have a database locally available
|
||||
(check out [application.conf](conf/application.conf)) and a valid Telegram token set (maybe as enviroment variable) you
|
||||
(check out [application.conf](conf/application.conf)) and a valid Telegram token set (maybe as environment variable) you
|
||||
can develop and see live changes of your Mezzotre on the fly. Finally, by using Postman, you can simulate incoming
|
||||
Telegram events.
|
||||
|
||||
|
|
7
pom.xml
7
pom.xml
|
@ -41,6 +41,7 @@
|
|||
<jsonschema2pojo.version>1.1.1</jsonschema2pojo.version>
|
||||
<jackson-databind.version>2.13.3</jackson-databind.version>
|
||||
<junit-jupiter-params.version>5.9.1</junit-jupiter-params.version>
|
||||
<google-guice.version>5.1.0</google-guice.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -86,6 +87,12 @@
|
|||
<artifactId>jooby-guice</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.inject.extensions</groupId>
|
||||
<artifactId>guice-assistedinject</artifactId>
|
||||
<version>${google-guice.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
|
|
|
@ -3,9 +3,14 @@ package com.github.polpetta.mezzotre;
|
|||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import io.jooby.Jooby;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class InjectionModule extends AbstractModule {
|
||||
|
||||
// In the future we can get this name from mvn, by now it is good as it is
|
||||
public static final String APPLICATION_NAME = "Mezzotre";
|
||||
private final Jooby jooby;
|
||||
|
||||
public InjectionModule(Jooby jooby) {
|
||||
|
@ -16,4 +21,11 @@ public class InjectionModule extends AbstractModule {
|
|||
public Logger getLogger() {
|
||||
return jooby.getLog();
|
||||
}
|
||||
|
||||
@Named("applicationName")
|
||||
@Singleton
|
||||
@Provides
|
||||
public String getAppName() {
|
||||
return APPLICATION_NAME;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package com.github.polpetta.mezzotre.i18n;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import javax.inject.Inject;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.tools.ToolManager;
|
||||
import org.apache.velocity.util.StringBuilderWriter;
|
||||
|
||||
public class TemplateContentGenerator {
|
||||
|
||||
private final LocalizedMessageFactory localizedMessageFactory;
|
||||
|
||||
@Inject
|
||||
public TemplateContentGenerator(LocalizedMessageFactory localizedMessageFactory) {
|
||||
this.localizedMessageFactory = localizedMessageFactory;
|
||||
}
|
||||
|
||||
public String mergeTemplate(
|
||||
Consumer<VelocityContext> velocityContextConsumer,
|
||||
String localeAsString,
|
||||
String templateName) {
|
||||
final Locale locale = Locale.forLanguageTag(localeAsString);
|
||||
final ToolManager toolManager = localizedMessageFactory.createVelocityToolManager(locale);
|
||||
final VelocityContext velocityContext = new VelocityContext(toolManager.createContext());
|
||||
|
||||
velocityContextConsumer.accept(velocityContext);
|
||||
|
||||
final StringBuilder content = new StringBuilder();
|
||||
final StringBuilderWriter stringBuilderWriter = new StringBuilderWriter(content);
|
||||
|
||||
toolManager
|
||||
.getVelocityEngine()
|
||||
.mergeTemplate(
|
||||
templateName, StandardCharsets.UTF_8.name(), velocityContext, stringBuilderWriter);
|
||||
|
||||
stringBuilderWriter.close();
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
public String getString(Locale locale, String key) {
|
||||
return localizedMessageFactory.createResourceBundle(locale).getString(key);
|
||||
}
|
||||
|
||||
public String getString(String localeAsString, String key) {
|
||||
return getString(Locale.forLanguageTag(localeAsString), key);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory;
|
||||
import com.github.polpetta.mezzotre.i18n.TemplateContentGenerator;
|
||||
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;
|
||||
|
@ -14,9 +14,6 @@ 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;
|
||||
import java.util.Locale;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
@ -24,9 +21,6 @@ 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;
|
||||
|
||||
/**
|
||||
|
@ -41,7 +35,7 @@ public class SelectLanguageTutorial implements Processor {
|
|||
|
||||
public static final String EVENT_NAME = "selectLanguageTutorial";
|
||||
private final Executor threadPool;
|
||||
private final LocalizedMessageFactory localizedMessageFactory;
|
||||
private final TemplateContentGenerator templateContentGenerator;
|
||||
private final Logger log;
|
||||
private final UUIDGenerator uuidGenerator;
|
||||
|
||||
|
@ -89,11 +83,11 @@ public class SelectLanguageTutorial implements Processor {
|
|||
@Inject
|
||||
public SelectLanguageTutorial(
|
||||
@Named("eventThreadPool") Executor threadPool,
|
||||
LocalizedMessageFactory localizedMessageFactory,
|
||||
TemplateContentGenerator templateContentGenerator,
|
||||
Logger log,
|
||||
UUIDGenerator uuidGenerator) {
|
||||
this.threadPool = threadPool;
|
||||
this.localizedMessageFactory = localizedMessageFactory;
|
||||
this.templateContentGenerator = templateContentGenerator;
|
||||
this.log = log;
|
||||
this.uuidGenerator = uuidGenerator;
|
||||
}
|
||||
|
@ -148,32 +142,11 @@ public class SelectLanguageTutorial implements Processor {
|
|||
// 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.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);
|
||||
|
||||
toolManager
|
||||
.getVelocityEngine()
|
||||
.mergeTemplate(
|
||||
"/template/callbackQuery/selectLanguageTutorial.vm",
|
||||
StandardCharsets.UTF_8.name(),
|
||||
velocityContext,
|
||||
stringBuilderWriter);
|
||||
|
||||
stringBuilderWriter.close();
|
||||
return content.toString();
|
||||
})
|
||||
.get();
|
||||
templateContentGenerator.mergeTemplate(
|
||||
velocityContext ->
|
||||
velocityContext.put("hasHelpBeenShown", tgChat.getHasHelpBeenShown()),
|
||||
tgChat.getLocale(),
|
||||
"/template/telegram/selectLanguageTutorial.vm");
|
||||
|
||||
log.trace("SelectLanguageTutorial event - message to send back: " + message);
|
||||
|
||||
|
@ -193,9 +166,7 @@ public class SelectLanguageTutorial implements Processor {
|
|||
if (!tgChat.getHasHelpBeenShown()) {
|
||||
// Add a button to show all the possible commands
|
||||
final String showMeTutorialString =
|
||||
localizedMessageFactory
|
||||
.createResourceBundle(Locale.forLanguageTag(tgChat.getLocale()))
|
||||
.getString("button.showMeTutorial");
|
||||
templateContentGenerator.getString(tgChat.getLocale(), "button.showMeTutorial");
|
||||
|
||||
final CallbackQueryMetadata callbackQueryMetadata =
|
||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||
|
|
|
@ -5,16 +5,21 @@ import com.github.polpetta.mezzotre.telegram.callbackquery.SelectLanguageTutoria
|
|||
import com.github.polpetta.mezzotre.telegram.callbackquery.ShowHelp;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import java.util.Set;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
public class CallbackQuery extends AbstractModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("eventProcessors")
|
||||
public Set<Processor> getEventProcessor(
|
||||
public Map<String, Processor> getEventProcessor(
|
||||
SelectLanguageTutorial selectLanguageTutorial, ShowHelp showHelp) {
|
||||
return Set.of(selectLanguageTutorial, showHelp);
|
||||
final HashMap<String, Processor> commandMap = new HashMap<>();
|
||||
commandMap.put(selectLanguageTutorial.getEventName(), selectLanguageTutorial);
|
||||
commandMap.put(showHelp.getEventName(), showHelp);
|
||||
return commandMap;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
import com.github.polpetta.mezzotre.i18n.TemplateContentGenerator;
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.github.polpetta.mezzotre.util.Clock;
|
||||
import com.github.polpetta.types.json.ChatContext;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import com.pengrad.telegrambot.request.SendMessage;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
public class Help implements Processor {
|
||||
|
||||
private static final String TRIGGERING_STAGING_NAME = "/help";
|
||||
|
||||
private final TemplateContentGenerator templateContentGenerator;
|
||||
private final Executor threadPool;
|
||||
private final Clock clock;
|
||||
private final Map<String, Processor> tgCommandProcessors;
|
||||
|
||||
@Inject
|
||||
public Help(
|
||||
TemplateContentGenerator templateContentGenerator,
|
||||
@Named("eventThreadPool") Executor threadPool,
|
||||
Clock clock,
|
||||
@Named("commandProcessor") Map<String, Processor> tgCommandProcessors) {
|
||||
this.templateContentGenerator = templateContentGenerator;
|
||||
this.threadPool = threadPool;
|
||||
this.clock = clock;
|
||||
this.tgCommandProcessors = tgCommandProcessors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Set.of(TRIGGERING_STAGING_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(TgChat chat, Update update) {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
final String message =
|
||||
templateContentGenerator.mergeTemplate(
|
||||
velocityContext -> {
|
||||
velocityContext.put(
|
||||
"commands",
|
||||
tgCommandProcessors.values().stream()
|
||||
.distinct()
|
||||
.map(
|
||||
p ->
|
||||
Pair.of(
|
||||
p.getTriggerKeywords().stream()
|
||||
.sorted()
|
||||
.collect(Collectors.toList()),
|
||||
p.getLocaleDescriptionKeyword()))
|
||||
.collect(Collectors.toList()));
|
||||
},
|
||||
chat.getLocale(),
|
||||
"template/telegram/help.vm");
|
||||
|
||||
final ChatContext chatContext = chat.getChatContext();
|
||||
chatContext.setStage(TRIGGERING_STAGING_NAME);
|
||||
chatContext.setStep(0);
|
||||
chatContext.setPreviousMessageUnixTimestampInSeconds(clock.now());
|
||||
chat.setChatContext(chatContext);
|
||||
chat.setHasHelpBeenShown(true);
|
||||
chat.save();
|
||||
|
||||
// FIXME put all the buttons here
|
||||
|
||||
return Optional.of(new SendMessage(chat.getId(), message));
|
||||
},
|
||||
threadPool);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import com.pengrad.telegrambot.request.SendMessage;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class NotFound implements Processor {
|
||||
|
||||
private final String commandName;
|
||||
|
||||
@Inject
|
||||
public NotFound(@Assisted String commandName) {
|
||||
this.commandName = commandName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(TgChat chat, Update update) {
|
||||
// FIXME complete it with: localization, callbackQuery to show help message
|
||||
return CompletableFuture.completedFuture(update)
|
||||
.thenApply(
|
||||
ignored ->
|
||||
Optional.of(
|
||||
new SendMessage(chat.getId(), "Command " + commandName + " is not valid")));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
public interface NotFoundFactory {
|
||||
NotFound create(String commandName);
|
||||
}
|
|
@ -4,6 +4,7 @@ import com.github.polpetta.mezzotre.orm.model.TgChat;
|
|||
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;
|
||||
|
||||
/**
|
||||
|
@ -21,7 +22,7 @@ public interface Processor {
|
|||
*
|
||||
* @return a {@link String} providing the keyword to trigger the current {@link Processor}
|
||||
*/
|
||||
String getTriggerKeyword();
|
||||
Set<String> getTriggerKeywords();
|
||||
|
||||
/**
|
||||
* Process the current update
|
||||
|
@ -31,4 +32,17 @@ public interface Processor {
|
|||
* @return a {@link CompletableFuture} with the result of the computation
|
||||
*/
|
||||
CompletableFuture<Optional<BaseRequest<?, ?>>> process(TgChat chat, Update update);
|
||||
|
||||
/**
|
||||
* Provide the key to retrieve the current processor descriptor. This is useful for help messages
|
||||
* or to provide a user with a description of what this command does. Whilst a default
|
||||
* implementation is provided, we suggest to rename it accordingly since the name generation is
|
||||
* based on reflection and can be fragile and subject to code refactor changes.
|
||||
*
|
||||
* @return a {@link String} with the name of the localization key containing the command
|
||||
* description
|
||||
*/
|
||||
default String getLocaleDescriptionKeyword() {
|
||||
return this.getClass().getName().toLowerCase() + ".cmdDescription";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import com.google.inject.Inject;
|
|||
import com.google.inject.Singleton;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.inject.Named;
|
||||
|
@ -21,15 +21,18 @@ import javax.inject.Named;
|
|||
@Singleton
|
||||
public class Router {
|
||||
|
||||
private final Set<Processor> tgCommandProcessors;
|
||||
private final Map<String, Processor> tgCommandProcessors;
|
||||
private final Executor threadPool;
|
||||
private final NotFoundFactory notFoundFactory;
|
||||
|
||||
@Inject
|
||||
public Router(
|
||||
@Named("commandProcessor") Set<Processor> tgCommandProcessors,
|
||||
@Named("eventThreadPool") Executor threadPool) {
|
||||
@Named("commandProcessor") Map<String, Processor> tgCommandProcessors,
|
||||
@Named("eventThreadPool") Executor threadPool,
|
||||
NotFoundFactory notFoundFactory) {
|
||||
this.tgCommandProcessors = tgCommandProcessors;
|
||||
this.threadPool = threadPool;
|
||||
this.notFoundFactory = notFoundFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,15 +61,13 @@ public class Router {
|
|||
.map(list -> list[0])
|
||||
.filter(wannabeCommand -> wannabeCommand.startsWith("/"))
|
||||
.or(() -> Optional.ofNullable(chat.getChatContext().getStage()))
|
||||
.flatMap(
|
||||
.map(
|
||||
command ->
|
||||
tgCommandProcessors.stream()
|
||||
// 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))
|
||||
.orElse(CompletableFuture.failedFuture(new CommandNotFoundException())),
|
||||
tgCommandProcessors
|
||||
.getOrDefault(command, notFoundFactory.create(command))
|
||||
.process(chat, update))
|
||||
// This should never happen
|
||||
.orElse(CompletableFuture.failedFuture(new IllegalStateException())),
|
||||
threadPool);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory;
|
||||
import com.github.polpetta.mezzotre.i18n.TemplateContentGenerator;
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.github.polpetta.mezzotre.telegram.callbackquery.SelectLanguageTutorial;
|
||||
|
@ -15,17 +15,13 @@ 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;
|
||||
import io.vavr.control.Try;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
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 org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.tools.ToolManager;
|
||||
import org.apache.velocity.util.StringBuilderWriter;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
|
@ -37,29 +33,35 @@ import org.slf4j.Logger;
|
|||
@Singleton
|
||||
public class Start implements Processor {
|
||||
|
||||
private static final String TRIGGERING_STAGING_NAME = "/start";
|
||||
|
||||
private final Executor threadPool;
|
||||
private final Logger log;
|
||||
private final UUIDGenerator uuidGenerator;
|
||||
private final Clock clock;
|
||||
private final LocalizedMessageFactory localizedMessageFactory;
|
||||
private final String applicationName;
|
||||
|
||||
private final TemplateContentGenerator templateContentGenerator;
|
||||
|
||||
@Inject
|
||||
public Start(
|
||||
LocalizedMessageFactory localizedMessageFactory,
|
||||
TemplateContentGenerator templateContentGenerator,
|
||||
@Named("eventThreadPool") Executor threadPool,
|
||||
Logger log,
|
||||
UUIDGenerator uuidGenerator,
|
||||
Clock clock) {
|
||||
this.localizedMessageFactory = localizedMessageFactory;
|
||||
Clock clock,
|
||||
@Named("applicationName") String applicationName) {
|
||||
this.templateContentGenerator = templateContentGenerator;
|
||||
this.threadPool = threadPool;
|
||||
this.log = log;
|
||||
this.uuidGenerator = uuidGenerator;
|
||||
this.clock = clock;
|
||||
this.applicationName = applicationName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTriggerKeyword() {
|
||||
return "/start";
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Set.of(TRIGGERING_STAGING_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -73,36 +75,19 @@ public class Start implements Processor {
|
|||
3 - Reply to Telegram
|
||||
*/
|
||||
final String message =
|
||||
Try.of(
|
||||
() -> {
|
||||
final Locale locale = Locale.forLanguageTag(chat.getLocale());
|
||||
final ToolManager toolManager =
|
||||
localizedMessageFactory.createVelocityToolManager(locale);
|
||||
final VelocityContext context =
|
||||
new VelocityContext(toolManager.createContext());
|
||||
context.put("firstName", update.message().chat().firstName());
|
||||
context.put("programName", "_Mezzotre_");
|
||||
|
||||
final StringBuilder content = new StringBuilder();
|
||||
final StringBuilderWriter stringBuilderWriter =
|
||||
new StringBuilderWriter(content);
|
||||
|
||||
toolManager
|
||||
.getVelocityEngine()
|
||||
.mergeTemplate(
|
||||
"template/command/start.0.vm",
|
||||
StandardCharsets.UTF_8.name(),
|
||||
context,
|
||||
stringBuilderWriter);
|
||||
|
||||
stringBuilderWriter.close();
|
||||
return content.toString();
|
||||
})
|
||||
.get();
|
||||
templateContentGenerator.mergeTemplate(
|
||||
velocityContext -> {
|
||||
velocityContext.put("firstName", update.message().chat().firstName());
|
||||
// FIXME add some very cool markdown formatter instead of concatenating stuff
|
||||
// this way
|
||||
velocityContext.put("programName", "_" + applicationName + "_");
|
||||
},
|
||||
chat.getLocale(),
|
||||
"template/telegram/start.vm");
|
||||
log.trace("Start command - message to send back: " + message);
|
||||
|
||||
final ChatContext chatContext = chat.getChatContext();
|
||||
chatContext.setStage(getTriggerKeyword());
|
||||
chatContext.setStage(TRIGGERING_STAGING_NAME);
|
||||
chatContext.setStep(0);
|
||||
chatContext.setPreviousMessageUnixTimestampInSeconds(clock.now());
|
||||
chat.setChatContext(chatContext);
|
||||
|
@ -135,13 +120,9 @@ public class Start implements Processor {
|
|||
.build());
|
||||
|
||||
final String englishButton =
|
||||
localizedMessageFactory
|
||||
.createResourceBundle(Locale.US)
|
||||
.getString("changeLanguage.english");
|
||||
templateContentGenerator.getString(Locale.US, "changeLanguage.english");
|
||||
final String italianButton =
|
||||
localizedMessageFactory
|
||||
.createResourceBundle(Locale.ITALY)
|
||||
.getString("changeLanguage.italian");
|
||||
templateContentGenerator.getString(Locale.ITALY, "changeLanguage.italian");
|
||||
|
||||
final SendMessage messageToSend =
|
||||
new SendMessage(chat.getId(), message)
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command.di;
|
||||
|
||||
import com.github.polpetta.mezzotre.telegram.command.Processor;
|
||||
import com.github.polpetta.mezzotre.telegram.command.Start;
|
||||
import com.github.polpetta.mezzotre.telegram.command.*;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import java.util.Set;
|
||||
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.velocity.app.VelocityEngine;
|
||||
|
@ -12,11 +13,37 @@ import org.apache.velocity.runtime.RuntimeConstants;
|
|||
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
|
||||
|
||||
public class Command extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
super.configure();
|
||||
|
||||
install(
|
||||
new FactoryModuleBuilder()
|
||||
.implement(Processor.class, NotFound.class)
|
||||
.build(NotFoundFactory.class));
|
||||
}
|
||||
|
||||
private static Map<String, Processor> mapForProcessor(Processor processor) {
|
||||
final HashMap<String, Processor> commandMap = new HashMap<>();
|
||||
processor
|
||||
.getTriggerKeywords()
|
||||
.forEach(
|
||||
keyword -> {
|
||||
commandMap.put(keyword, processor);
|
||||
});
|
||||
return commandMap;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("commandProcessor")
|
||||
public Set<Processor> getCommandProcessor(Start start) {
|
||||
return Set.of(start);
|
||||
public Map<String, Processor> getCommandProcessor(Start start, Help help) {
|
||||
final HashMap<String, Processor> commandMap = new HashMap<>();
|
||||
commandMap.putAll(mapForProcessor(start));
|
||||
commandMap.putAll(mapForProcessor(help));
|
||||
|
||||
return commandMap;
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package com.github.polpetta.mezzotre.telegram.model;
|
||||
|
||||
import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory;
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class Help {
|
||||
|
||||
private final LocalizedMessageFactory localizedMessageFactory;
|
||||
|
||||
@Inject
|
||||
public Help(LocalizedMessageFactory localizedMessageFactory) {
|
||||
this.localizedMessageFactory = localizedMessageFactory;
|
||||
}
|
||||
|
||||
public String generateErrorMessage() {
|
||||
return "";
|
||||
}
|
||||
}
|
|
@ -1,11 +1,16 @@
|
|||
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
|
||||
start.cmdDescription=Trigger this very bot
|
||||
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
|
||||
changeLanguage.cmdDescription=Select the new language I will use to speak to you
|
||||
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.
|
||||
help.description=Here is a list of what I can do
|
||||
help.buttonsToo=You can do the same operations you''d do with the commands aforementioned by selecting the corresponding button below \ud83d\udc47
|
||||
help.cmdDescription=Print the help message
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
${i18n.help.description}:
|
||||
|
||||
#foreach(${command} in ${commands})
|
||||
*#foreach(${key} in ${command.left}) ${key}#end: ${i18n.get(${command.right})}
|
||||
#end
|
||||
|
||||
${i18n.help.buttonsToo}
|
|
@ -6,6 +6,7 @@ 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.i18n.TemplateContentGenerator;
|
||||
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;
|
||||
|
@ -67,7 +68,8 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
selectLanguageTutorial =
|
||||
new SelectLanguageTutorial(
|
||||
Executors.newSingleThreadExecutor(),
|
||||
new LocalizedMessageFactory(Loader.defaultVelocityEngine()),
|
||||
new TemplateContentGenerator(
|
||||
new LocalizedMessageFactory(Loader.defaultVelocityEngine())),
|
||||
LoggerFactory.getLogger(SelectLanguageTutorial.class),
|
||||
fakeUUIDGenerator);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
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.i18n.TemplateContentGenerator;
|
||||
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.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.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import org.apache.velocity.app.VelocityEngine;
|
||||
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.slf4j.Logger;
|
||||
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 HelpIntegrationTest {
|
||||
private static Gson gson;
|
||||
|
||||
@Container
|
||||
private final PostgreSQLContainer<?> postgresServer =
|
||||
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||
|
||||
private Database database;
|
||||
private VelocityEngine velocityEngine;
|
||||
private Clock fakeClock;
|
||||
private Help help;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
database =
|
||||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||
velocityEngine = Loader.defaultVelocityEngine();
|
||||
|
||||
final Logger log = LoggerFactory.getLogger(Start.class);
|
||||
fakeClock = mock(Clock.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProvideMessageWithButtons() throws Exception {
|
||||
when(fakeClock.now()).thenReturn(42L);
|
||||
|
||||
final TgChat tgChat = new TgChat(1111111L, new ChatContext(), "en-US", false);
|
||||
tgChat.save();
|
||||
|
||||
final HashMap<String, Processor> commands = new HashMap<>();
|
||||
final Processor dummy1 =
|
||||
new Processor() {
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Set.of("/a", "/b");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
TgChat chat, Update update) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocaleDescriptionKeyword() {
|
||||
return "start.cmdDescription";
|
||||
}
|
||||
};
|
||||
final Processor dummy2 =
|
||||
new Processor() {
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Set.of("/different");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
TgChat chat, Update update) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocaleDescriptionKeyword() {
|
||||
return "help.cmdDescription";
|
||||
}
|
||||
};
|
||||
commands.put("/a", dummy1);
|
||||
commands.put("/b", dummy1);
|
||||
commands.put("/different", dummy2);
|
||||
|
||||
help =
|
||||
new Help(
|
||||
new TemplateContentGenerator(new LocalizedMessageFactory(velocityEngine)),
|
||||
Executors.newSingleThreadExecutor(),
|
||||
fakeClock,
|
||||
commands);
|
||||
|
||||
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\":\"/help\"\n"
|
||||
+ "}\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotFuture = help.process(tgChat, update);
|
||||
final Optional<BaseRequest<?, ?>> gotResponseOpt = gotFuture.get();
|
||||
final BaseRequest<?, ?> gotResponse = gotResponseOpt.get();
|
||||
assertInstanceOf(SendMessage.class, gotResponse);
|
||||
final String message = (String) gotResponse.getParameters().get("text");
|
||||
assertEquals(
|
||||
"Here is a list of what I can do:\n"
|
||||
+ "\n"
|
||||
+ "* /a /b: Trigger this very bot\n"
|
||||
+ "* /different: Print the help message\n"
|
||||
+ "\n"
|
||||
+ "You can do the same operations you'd do with the commands aforementioned by"
|
||||
+ " selecting the corresponding button below \uD83D\uDC47",
|
||||
message);
|
||||
|
||||
// TODO InputKeyboard assertions
|
||||
fail("Add inputkeyboard assertions too");
|
||||
|
||||
final TgChat gotChat = new QTgChat().id.eq(1111111L).findOne();
|
||||
assertNotNull(gotChat);
|
||||
assertTrue(gotChat.getHasHelpBeenShown());
|
||||
final ChatContext gotChatChatContext = gotChat.getChatContext();
|
||||
assertEquals(0, gotChatChatContext.getStep());
|
||||
assertEquals("/help", gotChatChatContext.getStage());
|
||||
assertEquals(42, gotChatChatContext.getPreviousMessageUnixTimestampInSeconds());
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ 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.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
@ -32,12 +33,12 @@ class RouterTest {
|
|||
gson = new Gson();
|
||||
|
||||
dummyEmptyExampleProcessor = mock(Processor.class);
|
||||
when(dummyEmptyExampleProcessor.getTriggerKeyword()).thenReturn("/example");
|
||||
when(dummyEmptyExampleProcessor.getTriggerKeywords()).thenReturn(Set.of("/example"));
|
||||
when(dummyEmptyExampleProcessor.process(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
|
||||
|
||||
anotherKeyWithResultProcessor = mock(Processor.class);
|
||||
when(anotherKeyWithResultProcessor.getTriggerKeyword()).thenReturn("/anotherExample");
|
||||
when(anotherKeyWithResultProcessor.getTriggerKeywords()).thenReturn(Set.of("/anotherExample"));
|
||||
when(anotherKeyWithResultProcessor.process(any(), any()))
|
||||
.thenReturn(
|
||||
CompletableFuture.completedFuture(Optional.of(new SendMessage(1234L, "hello world"))));
|
||||
|
@ -46,7 +47,10 @@ class RouterTest {
|
|||
@Test
|
||||
void shouldMessageExampleMessageAndGetEmptyOptional() throws Exception {
|
||||
final Router router =
|
||||
new Router(Set.of(dummyEmptyExampleProcessor), Executors.newSingleThreadExecutor());
|
||||
new Router(
|
||||
Map.of("/example", dummyEmptyExampleProcessor),
|
||||
Executors.newSingleThreadExecutor(),
|
||||
mock(NotFoundFactory.class));
|
||||
final TgChat fakeChat = mock(TgChat.class);
|
||||
when(fakeChat.getChatContext()).thenReturn(new ChatContext());
|
||||
final Update update =
|
||||
|
@ -85,8 +89,13 @@ class RouterTest {
|
|||
void shouldSelectRightExecutorAndReturnResult() throws Exception {
|
||||
final Router router =
|
||||
new Router(
|
||||
Set.of(dummyEmptyExampleProcessor, anotherKeyWithResultProcessor),
|
||||
Executors.newSingleThreadExecutor());
|
||||
Map.of(
|
||||
"/example",
|
||||
dummyEmptyExampleProcessor,
|
||||
"/anotherExample",
|
||||
anotherKeyWithResultProcessor),
|
||||
Executors.newSingleThreadExecutor(),
|
||||
mock(NotFoundFactory.class));
|
||||
final TgChat fakeChat = mock(TgChat.class);
|
||||
when(fakeChat.getChatContext()).thenReturn(new ChatContext());
|
||||
final Update update =
|
||||
|
|
|
@ -6,6 +6,7 @@ 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.i18n.TemplateContentGenerator;
|
||||
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;
|
||||
|
@ -22,8 +23,6 @@ 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;
|
||||
|
@ -62,12 +61,7 @@ class StartIntegrationTest {
|
|||
void setUp() throws Exception {
|
||||
database =
|
||||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||
velocityEngine = new VelocityEngine();
|
||||
velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "classpath");
|
||||
velocityEngine.setProperty(
|
||||
"resource.loader.classpath.class", ClasspathResourceLoader.class.getName());
|
||||
velocityEngine.init();
|
||||
localizedMessageFactory = new LocalizedMessageFactory(velocityEngine);
|
||||
velocityEngine = Loader.defaultVelocityEngine();
|
||||
|
||||
final Logger log = LoggerFactory.getLogger(Start.class);
|
||||
|
||||
|
@ -76,11 +70,12 @@ class StartIntegrationTest {
|
|||
|
||||
start =
|
||||
new Start(
|
||||
localizedMessageFactory,
|
||||
new TemplateContentGenerator(new LocalizedMessageFactory(velocityEngine)),
|
||||
Executors.newSingleThreadExecutor(),
|
||||
log,
|
||||
fakeUUIDGenerator,
|
||||
fakeClock);
|
||||
fakeClock,
|
||||
"Mezzotre");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue