#1 initial sending media files support
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
<actions> </actions>
|
||||
<screens> </screens>
|
||||
<commands> </commands>
|
||||
<media> </media>
|
||||
</flow>
|
||||
```
|
||||
|
||||
@@ -51,6 +52,8 @@ Screen - то, что бот ответит пользователю, обычн
|
||||
</screen>
|
||||
```
|
||||
|
||||
`mediaRef` - ссылка на элемент медиа, который будет оправлен ботом
|
||||
|
||||
`text` - выводимый текст
|
||||
|
||||
`keyboard` - описание клавиатуры
|
||||
@@ -65,7 +68,7 @@ Screen - то, что бот ответит пользователю, обычн
|
||||
|
||||
#### Button
|
||||
|
||||
`if` - видомость кнопки, значение `true`, `false` или имя метода из контекста экрана (для программной обработки)
|
||||
`if` - видимость кнопки, значение `true`, `false` или имя метода из контекста экрана (для программной обработки)
|
||||
|
||||
`actionRef` - `id` или `name` action, описанный в соответсвующей секции
|
||||
|
||||
@@ -89,4 +92,21 @@ Screen - то, что бот ответит пользователю, обычн
|
||||
```
|
||||
|
||||
`actionRef` - ссылка на action, может использоваться id или name
|
||||
`screenRef` - ссылка на screen, может использоваться id или name
|
||||
|
||||
`screenRef` - ссылка на screen, может использоваться id или name
|
||||
|
||||
`edit` = `true|false`- исходное сообщение будет отредактировано (актуально для callback)
|
||||
|
||||
`replace` = `true|false`- исходное сообщение будет удалено, и отправлено новое (актуально для callback,
|
||||
если меняется тип сообщения т.е. сообщение с фото, видео должно быть заменено на текстовое и наоборот)
|
||||
|
||||
### Media
|
||||
|
||||
Описывает медиа-ресурсы, доступные для отправки ботом
|
||||
|
||||
```xml
|
||||
<media>
|
||||
<video id="40001" url="https://example.com/video.mp4"/>
|
||||
<photo id="40002" url="https://example.com//photo.png"/>
|
||||
</media>
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.penkrat.stbf.templates;
|
||||
|
||||
import ru.penkrat.stbf.api.Media;
|
||||
|
||||
public interface MediaResolver {
|
||||
|
||||
Media getMedia(String name);
|
||||
|
||||
}
|
||||
@@ -24,4 +24,7 @@ public class CommandItem extends NamedItem {
|
||||
@JacksonXmlProperty(isAttribute = true)
|
||||
private boolean edit;
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true)
|
||||
private boolean replace;
|
||||
|
||||
}
|
||||
|
||||
@@ -62,19 +62,22 @@ public class FlowCommandResolverDelegate implements CommandResolver {
|
||||
screenFactory = screenResolver.getScreenFactory(commandItem.getScreenRef());
|
||||
}
|
||||
if (actionMatcher != null && screen != null) {
|
||||
return simpleCommand(actionMatcher, screen, commandItem.isEdit(), commandItem.getId(), commandItem.getName());
|
||||
return simpleCommand(actionMatcher, screen, commandItem.isEdit(), commandItem.isReplace(), commandItem.getId(), commandItem.getName());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Command simpleCommand(RequestMatcher matcher, Screen screen, boolean edit, String id, String name) {
|
||||
private Command simpleCommand(RequestMatcher matcher, Screen screen, boolean edit, boolean replace, String id, String name) {
|
||||
return new Command() {
|
||||
|
||||
@Override
|
||||
public void process(BotRequest botRequest, BotResponse botResponse, CommandChain chain) {
|
||||
if (matcher.match(botRequest)) {
|
||||
if (edit) {
|
||||
if (replace) {
|
||||
botResponse.deleteMessage();
|
||||
}
|
||||
if (edit && !replace) {
|
||||
botResponse.edit(screen);
|
||||
} else {
|
||||
botResponse.send(screen);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package ru.penkrat.stbf.templates.xml;
|
||||
|
||||
import ru.penkrat.stbf.api.Media;
|
||||
import ru.penkrat.stbf.api.MediaType;
|
||||
import ru.penkrat.stbf.templates.MediaResolver;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
class FlowMediaResolverDelegate implements MediaResolver {
|
||||
|
||||
private final NamedItemResolver<MediaItem> videosResolver;
|
||||
private final NamedItemResolver<MediaItem> photosResolver;
|
||||
|
||||
FlowMediaResolverDelegate(FlowRoot src) {
|
||||
videosResolver = new NamedItemResolver<>(Traversal.traverse(src, FlowMediaResolverDelegate::videos));
|
||||
photosResolver = new NamedItemResolver<>(Traversal.traverse(src, FlowMediaResolverDelegate::photos));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Media getMedia(String name) {
|
||||
Optional<Media> media = videosResolver.getOpt(name)
|
||||
.map(FlowMediaResolverDelegate::toVideo);
|
||||
if(media.isPresent()){
|
||||
return media.get();
|
||||
}
|
||||
|
||||
media = photosResolver.getOpt(name)
|
||||
.map(FlowMediaResolverDelegate::toPhoto);
|
||||
if(media.isPresent()){
|
||||
return media.get();
|
||||
}
|
||||
|
||||
throw new IllegalStateException("MediaElement not found by 'id' or 'name' " + name);
|
||||
}
|
||||
|
||||
private static Media toVideo(MediaItem item) {
|
||||
return Media.builder()
|
||||
.mediaType(MediaType.VIDEO)
|
||||
.url(item.getUrl())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Media toPhoto(MediaItem item) {
|
||||
return Media.builder()
|
||||
.mediaType(MediaType.PHOTO)
|
||||
.url(item.getUrl())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Collection<MediaItem> videos(FlowRoot root) {
|
||||
return root.getMedia().getVideos();
|
||||
}
|
||||
|
||||
private static Collection<MediaItem> photos(FlowRoot root) {
|
||||
return root.getMedia().getPhotos();
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,10 @@ class FlowRoot {
|
||||
@JsonProperty("command")
|
||||
private List<CommandItem> commands = new ArrayList<>();
|
||||
|
||||
@Getter
|
||||
@JsonProperty("media")
|
||||
private MediaRoot media = new MediaRoot();
|
||||
|
||||
@Getter
|
||||
private transient List<FlowRoot> included = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -6,10 +6,13 @@ import lombok.val;
|
||||
import ru.penkrat.stbf.api.Action;
|
||||
import ru.penkrat.stbf.api.Keyboard;
|
||||
import ru.penkrat.stbf.api.KeyboardBuilder;
|
||||
import ru.penkrat.stbf.api.Media;
|
||||
import ru.penkrat.stbf.api.Screen;
|
||||
import ru.penkrat.stbf.common.screen.MediaScreen;
|
||||
import ru.penkrat.stbf.common.screen.TextScreen;
|
||||
import ru.penkrat.stbf.templates.ActionResolver;
|
||||
import ru.penkrat.stbf.templates.KeyboardProvider;
|
||||
import ru.penkrat.stbf.templates.MediaResolver;
|
||||
import ru.penkrat.stbf.templates.ScreenResolver;
|
||||
import ru.penkrat.stbf.templates.TemplateRenderer;
|
||||
import ru.penkrat.stbf.templates.utils.ReflectionUtils;
|
||||
@@ -25,11 +28,14 @@ class FlowScreenResolverDelegate implements ScreenResolver {
|
||||
|
||||
private final ActionResolver actionResolver;
|
||||
|
||||
private final MediaResolver mediaResolver;
|
||||
|
||||
@Setter
|
||||
private TemplateRenderer templateRenderer = new NoopTemplateRenderer();
|
||||
|
||||
FlowScreenResolverDelegate(FlowRoot src, ActionResolver actionResolver) {
|
||||
FlowScreenResolverDelegate(FlowRoot src, ActionResolver actionResolver, MediaResolver mediaResolver) {
|
||||
this.actionResolver = actionResolver;
|
||||
this.mediaResolver = mediaResolver;
|
||||
resolver = new NamedItemResolver<>(Traversal.traverse(src, FlowRoot::getScreens));
|
||||
}
|
||||
|
||||
@@ -37,6 +43,11 @@ class FlowScreenResolverDelegate implements ScreenResolver {
|
||||
public Screen getScreen(String name) {
|
||||
ScreenItem item = resolver.get(name);
|
||||
|
||||
if (StringUtils.isNotEmpty(item.getMediaRef())) {
|
||||
final Media media = mediaResolver.getMedia(item.getMediaRef());
|
||||
return new MediaScreen(item.getText(), media, buildKeyboard(item.getKeyboard(), null));
|
||||
}
|
||||
|
||||
return new TextScreen(item.getText(), buildKeyboard(item.getKeyboard(), null));
|
||||
}
|
||||
|
||||
@@ -44,6 +55,12 @@ class FlowScreenResolverDelegate implements ScreenResolver {
|
||||
public Screen getScreen(String name, Object context) {
|
||||
ScreenItem item = resolver.get(name);
|
||||
|
||||
if (StringUtils.isNotEmpty(item.getMediaRef())) {
|
||||
final Media media = mediaResolver.getMedia(item.getMediaRef());
|
||||
return new MediaScreen(templateRenderer.render(item.getText(), context), media,
|
||||
resolveKeyboard(item.getKeyboard(), context));
|
||||
}
|
||||
|
||||
return new TextScreen(templateRenderer.render(item.getText(), context),
|
||||
resolveKeyboard(item.getKeyboard(), context));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package ru.penkrat.stbf.templates.xml;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class MediaItem extends NamedItem {
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true)
|
||||
private String url;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package ru.penkrat.stbf.templates.xml;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MediaRoot {
|
||||
|
||||
@Getter
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
@JsonProperty("video")
|
||||
private List<MediaItem> videos = new ArrayList<>();
|
||||
|
||||
@Getter
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
@JsonProperty("photo")
|
||||
private List<MediaItem> photos = new ArrayList<>();
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import ru.penkrat.stbf.templates.utils.StringUtils;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class NamedItemResolver<T extends NamedItem> {
|
||||
@@ -39,4 +40,22 @@ class NamedItemResolver<T extends NamedItem> {
|
||||
throw new IllegalStateException("Element not found by 'id' or 'name' " + key);
|
||||
}
|
||||
|
||||
Optional<T> getOpt(String key) {
|
||||
List<T> list = byId.get(key);
|
||||
if (list != null) {
|
||||
if (list.size() > 1) {
|
||||
throw new IllegalStateException("Non unique 'id' " + key);
|
||||
}
|
||||
return Optional.of(list.get(0));
|
||||
}
|
||||
list = byName.get(key);
|
||||
if (list != null) {
|
||||
if (list.size() > 1) {
|
||||
throw new IllegalStateException("Non unique 'name' " + key);
|
||||
}
|
||||
return Optional.of(list.get(0));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ru.penkrat.stbf.templates.xml;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -9,6 +10,9 @@ class ScreenItem extends NamedItem {
|
||||
|
||||
private String text;
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true)
|
||||
private String mediaRef;
|
||||
|
||||
private KeyboardWrapper keyboard = new KeyboardWrapper();
|
||||
|
||||
}
|
||||
|
||||
@@ -18,18 +18,21 @@ public class XmlFlowResolver implements FlowResolver {
|
||||
private final FlowActionResolverDelegate actionDelegate;
|
||||
private final FlowScreenResolverDelegate screenDelegate;
|
||||
private final FlowCommandResolverDelegate commandResolver;
|
||||
private final FlowMediaResolverDelegate mediaResolver;
|
||||
|
||||
public XmlFlowResolver(String filename) {
|
||||
FlowRoot flow = reader.read(filename);
|
||||
actionDelegate = new FlowActionResolverDelegate(flow);
|
||||
screenDelegate = new FlowScreenResolverDelegate(flow, this);
|
||||
mediaResolver = new FlowMediaResolverDelegate(flow);
|
||||
screenDelegate = new FlowScreenResolverDelegate(flow, this, mediaResolver);
|
||||
commandResolver = new FlowCommandResolverDelegate(flow, actionDelegate, screenDelegate);
|
||||
}
|
||||
|
||||
public XmlFlowResolver(InputStream is) {
|
||||
FlowRoot flow = reader.read(is);
|
||||
actionDelegate = new FlowActionResolverDelegate(flow);
|
||||
screenDelegate = new FlowScreenResolverDelegate(flow, this);
|
||||
mediaResolver = new FlowMediaResolverDelegate(flow);
|
||||
screenDelegate = new FlowScreenResolverDelegate(flow, this, mediaResolver);
|
||||
commandResolver = new FlowCommandResolverDelegate(flow, actionDelegate, screenDelegate);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package ru.penkrat.stbf.templates.xml;
|
||||
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import lombok.val;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import ru.penkrat.stbf.api.Command;
|
||||
@@ -42,6 +43,7 @@ public class XmlWriterTest {
|
||||
item2.setId("2");
|
||||
item2.setName("screen-2");
|
||||
item2.setText("Hello, {{ name }}");
|
||||
item2.setMediaRef("12");
|
||||
|
||||
item2.getKeyboard().setFactoryMethod("getKeyboard");
|
||||
|
||||
@@ -58,6 +60,16 @@ public class XmlWriterTest {
|
||||
commandItem.setClazz(Command.class.getCanonicalName());
|
||||
root.getCommands().add(commandItem);
|
||||
|
||||
|
||||
MediaItem video1 = new MediaItem();
|
||||
video1.setId("11");
|
||||
video1.setUrl("https://example.com/test.mpg");
|
||||
MediaItem photo1 = new MediaItem();
|
||||
photo1.setId("12");
|
||||
photo1.setUrl("https://example.com/test.jpg");
|
||||
root.getMedia().getVideos().add(video1);
|
||||
root.getMedia().getPhotos().add(photo1);
|
||||
|
||||
String xml = mapper.writeValueAsString(root);
|
||||
|
||||
System.out.println(xml);
|
||||
|
||||
Reference in New Issue
Block a user