Skip to content

Commit

Permalink
Feature/add scenario config from file (#29)
Browse files Browse the repository at this point in the history
* [FEATURE]: add parsing scenario from config file
  • Loading branch information
Drednote authored Jan 21, 2025
1 parent f626f92 commit 9f9af42
Show file tree
Hide file tree
Showing 39 changed files with 871 additions and 47 deletions.
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,6 @@ handle this, there is a component called **Response Processing**, which follows
- You can create any implementation of `TelegramResponse` for sending response
- Any custom code can be written in `TelegramResponse`, but I strongly recommend using this
interface only for sending a response to **Telegram**
- If you pass {@link BotApiMethod} or {@link SendMediaBotMethod} in the constructor of this class,
the 'chatId' property will be automatically set (only if it is null). If you manually
set 'chatId', nothing happens

### Exception Handling

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ public TelegramAutoConfiguration(TelegramProperties properties) {
@AutoConfiguration
public static class BotConfig {

private static final String TELEGRAM_BOT = "TelegramBot";

/**
* Configures a bean for the Telegram bot instance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.github.drednote.telegram.filter.FilterProperties;
import io.github.drednote.telegram.handler.UpdateHandlerProperties;
import io.github.drednote.telegram.handler.scenario.property.ScenarioProperties;
import io.github.drednote.telegram.menu.MenuProperties;
import io.github.drednote.telegram.session.SessionProperties;
import lombok.Getter;
Expand All @@ -16,7 +17,7 @@
@ConfigurationProperties("drednote.telegram")
@EnableConfigurationProperties({
SessionProperties.class, UpdateHandlerProperties.class,
FilterProperties.class, MenuProperties.class
FilterProperties.class, MenuProperties.class, ScenarioProperties.class
})
@Getter
@Setter
Expand Down Expand Up @@ -69,4 +70,9 @@ public class TelegramProperties {
*/
@NonNull
private MenuProperties menu = new MenuProperties();
/**
* Scenario properties
*/
@NonNull
private ScenarioProperties scenario = new ScenarioProperties();
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,22 @@
*/
public class InvocableHandlerMethod extends HandlerMethod {

private final String beanType;

/**
* Creates a new instance of the {@code InvocableHandlerMethod} class with the given handler
* method.
*
* @param handlerMethod the handler method to invoke, not null
*/
public InvocableHandlerMethod(HandlerMethod handlerMethod, String beanType) {
super(handlerMethod);
this.beanType = beanType;
}

public InvocableHandlerMethod(HandlerMethod handlerMethod) {
super(handlerMethod);
this.beanType = "TelegramController";
}

/**
Expand Down Expand Up @@ -62,8 +70,8 @@ protected void assertTargetBean(Method method, Object targetBean, Object[] args)
Class<?> targetBeanClass = targetBean.getClass();
if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
String text = "The mapped handler method class '" + methodDeclaringClass.getName() +
"' is not an instance of the actual TelegramController bean class '" +
targetBeanClass.getName() + "'. If the TelegramController requires proxying " +
"' is not an instance of the actual " + beanType + " bean class '" +
targetBeanClass.getName() + "'. If the " + beanType + " requires proxying " +
"(e.g. due to @Transactional), please use class-based proxying.";
throw new IllegalStateException(formatInvokeError(text, args));
}
Expand All @@ -72,6 +80,6 @@ protected void assertTargetBean(Method method, Object targetBean, Object[] args)
@NonNull
@Override
protected String formatInvokeError(String text, Object[] args) {
return super.formatInvokeError(text, args).replace("Controller", "TelegramController");
return super.formatInvokeError(text, args).replace("Controller", beanType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.github.drednote.telegram.handler.scenario.persist.SimpleScenarioContext;
import io.github.drednote.telegram.handler.scenario.persist.SimpleStateContext;
import io.github.drednote.telegram.handler.scenario.persist.StateContext;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import org.springframework.lang.NonNull;
Expand Down Expand Up @@ -56,6 +57,7 @@ private void writeState(Kryo kryo, Output output, StateContext<S> state) {
kryo.writeClassAndObject(output, mapping.getRequestType());
kryo.writeClassAndObject(output, new HashSet<>(mapping.getMessageTypes()));
});
kryo.writeClassAndObject(output, new HashMap<>(state.props()));
}

@Override
Expand All @@ -77,7 +79,8 @@ public ScenarioContext<S> read(Kryo kryo, Input input, Class<ScenarioContext<S>>
Set<MessageType> messageTypes = (Set<MessageType>) kryo.readClassAndObject(input);
mappings.add(new UpdateRequestMapping(pattern, requestType, messageTypes));
}
return new SimpleStateContext<>(state, mappings, responseMessageProcessing);
HashMap<String, Object> props = (HashMap<String, Object>) kryo.readClassAndObject(input);
return new SimpleStateContext<>(state, mappings, responseMessageProcessing, props);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.github.drednote.telegram.handler.controller.TelegramControllerContainer;
import io.github.drednote.telegram.handler.scenario.ScenarioConfig;
import io.github.drednote.telegram.handler.scenario.ScenarioIdResolver;
import io.github.drednote.telegram.handler.scenario.property.ScenarioProperties;
import io.github.drednote.telegram.handler.scenario.ScenarioUpdateHandler;
import io.github.drednote.telegram.handler.scenario.SimpleScenarioConfig;
import io.github.drednote.telegram.handler.scenario.SimpleScenarioIdResolver;
Expand All @@ -25,6 +26,10 @@
import io.github.drednote.telegram.handler.scenario.persist.ScenarioFactory;
import io.github.drednote.telegram.handler.scenario.persist.SimpleScenarioFactory;
import io.github.drednote.telegram.handler.scenario.persist.SimpleScenarioPersister;
import io.github.drednote.telegram.handler.scenario.property.ScenarioFactoryBeanPostProcessor;
import io.github.drednote.telegram.handler.scenario.property.ScenarioFactoryContainer;
import io.github.drednote.telegram.handler.scenario.property.ScenarioFactoryResolver;
import io.github.drednote.telegram.handler.scenario.property.ScenarioPropertiesConfigurer;
import io.github.drednote.telegram.session.SessionProperties;
import io.github.drednote.telegram.utils.FieldProvider;
import org.springframework.beans.factory.BeanCreationException;
Expand All @@ -38,7 +43,7 @@
import org.springframework.context.annotation.Bean;

@AutoConfiguration
@EnableConfigurationProperties(UpdateHandlerProperties.class)
@EnableConfigurationProperties({UpdateHandlerProperties.class, ScenarioProperties.class})
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class UpdateHandlerAutoConfiguration {

Expand All @@ -57,7 +62,7 @@ public UpdateHandlerAutoConfiguration(
that it implies sequential processing within one user.
Consider disable the scenario handling, \
or set drednote.telegram.session.MaxThreadsPerUser to 1.
You can disable this warning by setting drednote.telegram.update-handler.enabledWarningForScenario to false
""";
Expand All @@ -82,16 +87,30 @@ public ScenarioUpdateHandler scenarioUpdateHandler(
return new ScenarioUpdateHandler();
}

@Bean
@ConditionalOnMissingBean
public ScenarioFactoryBeanPostProcessor scenarioFactoryBeanPostProcessor(ScenarioFactoryContainer container) {
return new ScenarioFactoryBeanPostProcessor(container);
}

@Bean
@ConditionalOnMissingBean
public ScenarioFactoryContainer scenarioFactoryContainer() {
return new ScenarioFactoryContainer();
}

@Bean
@ConditionalOnMissingBean
public <S> ScenarioUpdateHandlerPopular<S> scenarioUpdateHandlerPopular(
ScenarioConfigurerAdapter<S> adapter,
@Autowired(required = false) ScenarioIdRepositoryAdapter scenarioIdAdapter
ScenarioConfigurerAdapter<S> adapter, ScenarioProperties scenarioProperties,
@Autowired(required = false) ScenarioIdRepositoryAdapter scenarioIdAdapter,
ScenarioFactoryResolver scenarioFactoryResolver
) {
ScenarioBuilder<S> builder = new ScenarioBuilder<>();
adapter.onConfigure(new SimpleScenarioStateConfigurer<>(builder));
adapter.onConfigure(new SimpleScenarioConfigConfigurer<>(builder));
adapter.onConfigure(new SimpleScenarioTransitionConfigurer<>(builder));
new ScenarioPropertiesConfigurer(scenarioProperties, scenarioFactoryResolver).configure(builder);
ScenarioData<S> data = builder.build();

ScenarioIdResolver resolver = data.resolver() == null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

@Configuration
@ConfigurationProperties("drednote.telegram.update-handler")
Expand Down Expand Up @@ -78,6 +79,7 @@ public enum ParseMode {
MARKDOWN_V2("MarkdownV2"),
HTML("html");

@Nullable
private final String value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public TelegramControllerBeanPostProcessor(ControllerRegistrar registrar) {
* identifies annotated methods and registers them using the {@link ControllerRegistrar}.
*/
@Override
public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName)
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName)
throws BeansException {
Class<?> targetClass = AopUtils.getTargetClass(bean);
TelegramController telegramController = AnnotationUtils.findAnnotation(targetClass,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ public interface Action<S> {
* @return an optional result of the action execution, or null if there is no result
*/
@Nullable
Object execute(ActionContext<S> context);
Object execute(ActionContext<S> context) throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ public interface ActionContext<S> {
* @return a map of template variable names to their corresponding values
*/
Map<String, String> getTemplateVariables();

Map<String, Object> getProps();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class SimpleActionContext<S> implements ActionContext<S> {

private final UpdateRequest updateRequest;
private final Transition<S> transition;
private final Map<String, Object> props;

/**
* Retrieves the template variables extracted from the {@code UpdateRequest} based on the
Expand All @@ -40,6 +41,11 @@ public Map<String, String> getTemplateVariables() {
return res;
}

@Override
public Map<String, Object> getProps() {
return props;
}

/**
* Retrieves the transition associated with this context.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import io.github.drednote.telegram.handler.scenario.persist.ScenarioContext;
import io.github.drednote.telegram.handler.scenario.persist.ScenarioPersister;
import io.github.drednote.telegram.handler.scenario.persist.StateContext;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -52,8 +54,9 @@ public ScenarioEventResult sendEvent(UpdateRequest request) {
}
Transition<S> transition = optionalSTransition.get();
State<S> target = transition.getTarget();
Map<String, Object> props = target.getProps();

var context = new SimpleActionContext<>(request, transition);
var context = new SimpleActionContext<>(request, transition, new HashMap<>(props));
try {
Object response = target.execute(context);
ResponseSetter.setResponse(request, response);
Expand Down Expand Up @@ -101,7 +104,7 @@ public void resetScenario(ScenarioContext<S> context) {

private @NonNull SimpleState<S> convertToState(StateContext<S> stateContext) {
Set<? extends UpdateRequestMappingAccessor> mappings = stateContext.updateRequestMappings();
SimpleState<S> simpleState = new SimpleState<>(stateContext.id(), convert(mappings));
SimpleState<S> simpleState = new SimpleState<>(stateContext.id(), convert(mappings), stateContext.props());
simpleState.setResponseMessageProcessing(stateContext.responseMessageProcessing());
return simpleState;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;
import org.springframework.lang.Nullable;

public class ScenarioBuilder<S> {

@Nullable
@Setter
@Getter
private S initial;
@Nullable
@Setter
Expand Down Expand Up @@ -92,6 +94,7 @@ private static <S> void initTarget(
target.setActions(transition.getActions());
target.setMappings(mappings);
target.setResponseMessageProcessing(transition.isResponseMessageProcessing());
target.setProps(transition.getProps());
}

public record ScenarioData<S>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import io.github.drednote.telegram.core.request.TelegramRequest;
import io.github.drednote.telegram.core.request.UpdateRequestMapping;
import io.github.drednote.telegram.handler.scenario.Action;
import io.github.drednote.telegram.handler.scenario.ActionContext;
import java.util.Map;

/**
* Interface for configuring scenario base transitions.
Expand Down Expand Up @@ -38,15 +40,23 @@ public interface ScenarioBaseTransitionConfigurer<C extends ScenarioBaseTransiti
C action(Action<S> action);

/**
* Sets a condition that must be met for a given transition to be called. The matching is
* executing by {@link UpdateRequestMapping}
* Sets a condition that must be met for a given transition to be called. The matching is executing by
* {@link UpdateRequestMapping}
*
* @param telegramRequest the TelegramRequest to set
* @return the current instance of the configurer
* @see UpdateRequestMapping
*/
C telegramRequest(TelegramRequest telegramRequest);

/**
* Sets the additional props to be used during the transition.
*
* @param props additional props to pass to {@link Action} in {@link ActionContext}
* @return the current instance of the configurer
*/
C props(Map<String, Object> props);

/**
* Finalizes the transition configuration and returns a ScenarioTransitionConfigurer.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import io.github.drednote.telegram.core.request.TelegramRequest;
import io.github.drednote.telegram.handler.scenario.Action;
import io.github.drednote.telegram.handler.scenario.ActionContext;
import java.util.Map;

/**
* Interface for configuring rollback transitions in scenarios.
* <p>
* During this configurer will be created a new one transition with a reverse direction. For
* example, you create A → B transition, there will be created B → A transition with personal
* telegramRequest matching and action (you should specify it).
* During this configurer will be created a new one transition with a reverse direction. For example, you create A → B
* transition, there will be created B → A transition with personal telegramRequest matching and action (you should
* specify it).
*
* @param <S> the type of the scenario
* @author Ivan Galushko
Expand All @@ -32,4 +34,12 @@ public interface ScenarioRollbackTransitionConfigurer<S> extends
*/
ScenarioRollbackTransitionConfigurer<S> rollbackTelegramRequest(
TelegramRequest telegramRequest);

/**
* Sets the additional props to be used during the rollback.
*
* @param props additional props to pass to {@link Action} in {@link ActionContext}
* @return the current instance of {@link ScenarioRollbackTransitionConfigurer}
*/
ScenarioRollbackTransitionConfigurer<S> rollbackProps(Map<String, Object> props);
}
Loading

0 comments on commit 9f9af42

Please sign in to comment.