From 3bd362e478e0a29aa0faa96aa673634652ad8d4b Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Mon, 1 Apr 2019 01:09:59 +0200 Subject: [PATCH 01/27] fixed encoding in RSS feed reader (again!) --- .../server/assist/tools/RssFeedReader.java | 11 +++++--- .../assist/tools/Test_RssRomeTools.java | 26 ++++++++++++++----- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/b07z/sepia/server/assist/tools/RssFeedReader.java b/src/main/java/net/b07z/sepia/server/assist/tools/RssFeedReader.java index 8c3bfef5..995f047e 100644 --- a/src/main/java/net/b07z/sepia/server/assist/tools/RssFeedReader.java +++ b/src/main/java/net/b07z/sepia/server/assist/tools/RssFeedReader.java @@ -2,6 +2,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -122,12 +123,16 @@ public JSONObject getFeed(String url, String feedName, int maxEntries, boolean c //Get content as String then create a stream (its safer as the previous method) HttpClientResult httpRes = Connectors.apacheHttpGET(url, null); statusLine = httpRes.statusLine; - String content = httpRes.content; - if (content == null || content.isEmpty()){ + if (httpRes.content == null || httpRes.content.isEmpty()){ throw new RuntimeException("Feed content not found."); } //System.out.println(content.substring(0, 50)); - InputStream stream = new ByteArrayInputStream(content.getBytes()); + InputStream stream; + if (httpRes.encoding != null){ + stream = new ByteArrayInputStream(httpRes.content.getBytes(httpRes.encoding)); + }else{ + stream = new ByteArrayInputStream(httpRes.content.getBytes(StandardCharsets.UTF_8)); + } SyndFeedInput input = new SyndFeedInput(); SyndFeed feed = input.build(new XmlReader(stream, true, "UTF-8")); diff --git a/src/test/java/net/b07z/sepia/server/assist/tools/Test_RssRomeTools.java b/src/test/java/net/b07z/sepia/server/assist/tools/Test_RssRomeTools.java index 05685e7a..cb6cc16f 100644 --- a/src/test/java/net/b07z/sepia/server/assist/tools/Test_RssRomeTools.java +++ b/src/test/java/net/b07z/sepia/server/assist/tools/Test_RssRomeTools.java @@ -2,6 +2,8 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.nio.charset.StandardCharsets; + import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; @@ -12,6 +14,7 @@ import com.rometools.rome.io.XmlReader; import net.b07z.sepia.server.core.tools.Connectors; +import net.b07z.sepia.server.core.tools.Connectors.HttpClientResult; import net.b07z.sepia.server.core.tools.Debugger; import net.b07z.sepia.server.core.tools.Is; @@ -49,7 +52,9 @@ public static void main(String[] args) { } SyndFeedInput input = new SyndFeedInput(); SyndFeed feed = input.build(new XmlReader(stream, true, "UTF-8")); - System.out.println("Title: " + feed.getTitle()); //DEBUG + System.out.println("Feed name: " + feed.getTitle()); //DEBUG + System.out.println("First title: " + feed.getEntries().get(0).getTitle()); //DEBUG + System.out.println("First desc.: " + feed.getEntries().get(0).getContents().get(0).getValue()); //DEBUG System.out.println("Took: " + Debugger.toc(tic)); //DEBUG } } @@ -63,14 +68,21 @@ public static void main(String[] args) { tic = Debugger.tic(); try { //FEED STUFF - String content = Connectors.apacheHttpGET(url, null).content; - if (Is.notNullOrEmpty(content)){ - System.out.println(content.substring(0, 50)); - InputStream newStream = new ByteArrayInputStream(content.getBytes()); - + HttpClientResult httpClientRes = Connectors.apacheHttpGET(url, null); + //System.out.println(httpClientRes.content); + if (Is.notNullOrEmpty(httpClientRes.content)){ + System.out.println(httpClientRes.content.substring(0, 50)); + InputStream newStream; + if (httpClientRes.encoding != null){ + newStream = new ByteArrayInputStream(httpClientRes.content.getBytes(httpClientRes.encoding)); + }else{ + newStream = new ByteArrayInputStream(httpClientRes.content.getBytes(StandardCharsets.UTF_8)); + } SyndFeedInput input = new SyndFeedInput(); SyndFeed feed = input.build(new XmlReader(newStream, true, "UTF-8")); - System.out.println("Title: " + feed.getTitle()); //DEBUG + System.out.println("Feed name: " + feed.getTitle()); //DEBUG + System.out.println("First title: " + feed.getEntries().get(0).getTitle()); //DEBUG + System.out.println("First desc.: " + feed.getEntries().get(0).getContents().get(0).getValue()); //DEBUG } System.out.println("Took: " + Debugger.toc(tic)); //DEBUG From 4c61cbebca6cbd39f3d5d0b2e4f168d47c65652d Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Mon, 1 Apr 2019 01:11:35 +0200 Subject: [PATCH 02/27] added follow-up messages for assistant duplex connection - new setting - new NluInput data - opened Clients to send follow-ups --- Xtensions/assist.custom.properties | 1 + Xtensions/assist.properties | 1 + .../assist/endpoints/AssistEndpoint.java | 9 +++ .../server/assist/interpreters/NluInput.java | 8 ++ .../messages/AssistantSocketClient.java | 79 +++++++++++++++++-- .../sepia/server/assist/messages/Clients.java | 21 ++++- .../sepia/server/assist/server/Config.java | 6 ++ .../assist/services/ServiceBuilder.java | 29 +++++++ 8 files changed, 144 insertions(+), 10 deletions(-) diff --git a/Xtensions/assist.custom.properties b/Xtensions/assist.custom.properties index 2bb6f5c7..7efac3b5 100644 --- a/Xtensions/assist.custom.properties +++ b/Xtensions/assist.custom.properties @@ -3,6 +3,7 @@ assistant_name=Sepia assistant_id=uid1005 assistant_email=assistant@sepia.localhost assistant_pwd=4be708b703c518d10a97e4db421f0f75b66f9ff8c0ae65b6c5c13684a01f804d +assistant_allow_follow_ups=true module_account=elasticsearch #module_account=dynamo_db module_answers=file diff --git a/Xtensions/assist.properties b/Xtensions/assist.properties index 27076cd0..bda46277 100644 --- a/Xtensions/assist.properties +++ b/Xtensions/assist.properties @@ -3,6 +3,7 @@ assistant_name=Sepia assistant_id=uid1005 assistant_email=assistant@sepia.localhost assistant_pwd=4be708b703c518d10a97e4db421f0f75b66f9ff8c0ae65b6c5c13684a01f804d +assistant_allow_follow_ups=true module_account=elasticsearch #module_account=dynamo_db module_answers=file diff --git a/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java b/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java index bc9e5516..c2ab92a5 100644 --- a/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java +++ b/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java @@ -56,6 +56,9 @@ public static enum InputParameters { input_miss, dialog_stage, user_location, + connection, //http or ws (WebSocket), defaults to http, check also: NluInput.isDuplexConnection() + msg_id, //duplex connections might want to track the msg id + duplex_data, //more duplex data in JSON format demomode } @@ -265,6 +268,9 @@ public static NluInput getInput(RequestParameters params){ String time_str = params.getString(InputParameters.time.name()); //system time - time stamp String time_local = params.getString(InputParameters.time_local.name()); //local time date String client_info = params.getString(InputParameters.client.name()); + String connection = params.getString(InputParameters.connection.name()); //http or ws + String msg_id = params.getString(InputParameters.msg_id.name()); //msg ID + String duplex_data = params.getString(InputParameters.duplex_data.name()); //duplex data long time = -1; //-answer params: String last_cmd = params.getString(InputParameters.last_cmd.name()); @@ -283,6 +289,9 @@ public static NluInput getInput(RequestParameters params){ if (language!=null) input.language = language; if (context!=null) input.context = context; if (client_info!=null) input.clientInfo = client_info; + if (connection!=null) input.connection = connection; + if (msg_id!=null) input.msgId = msg_id; + if (duplex_data!=null) input.duplexData = duplex_data; if (env!=null) input.environment = env; else input.environment = "all"; //input.client_info.replaceFirst("_v\\d.*", "").trim(); if (user_location!=null) input.userLocation = user_location; if (time_local!=null) input.userTimeLocal = time_local; diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java index 0e20dc5d..ac74c33e 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java @@ -48,6 +48,9 @@ public class NluInput { public String userTimeLocal = ""; //time_local: system date and time at locally at user location, default format 2016.12.31_22:44:11 public User user; //user: holds all info about the user, can reload from account //... more to come + public String msgId = null; //msg_id: an ID to identify request, especially helpful in duplex scenarios + public String duplexData = null; //duplex_data: data helpful to trace back a duplex call and answer or follow-up. Format is JSON, parse when required + public String connection = "http"; //connection: http request or WebSocket connection - has influence on delayed replies public boolean demoMode = false; //demomode: true/false if you want to use the demomode //Stuff to cache during all processes from NLU to service result: @@ -219,6 +222,11 @@ public ServiceAnswers getCachedServiceAnswers(String serviceCommand){ //-------helper methods-------- + //connection type + public boolean isDuplexConnection(){ + return (connection.equals("ws")); //more types can be added here when available + } + //question/answer handling /** * Check if this input is an answer to a question asked by the assistant. diff --git a/src/main/java/net/b07z/sepia/server/assist/messages/AssistantSocketClient.java b/src/main/java/net/b07z/sepia/server/assist/messages/AssistantSocketClient.java index cbd1f90d..e1c170b1 100644 --- a/src/main/java/net/b07z/sepia/server/assist/messages/AssistantSocketClient.java +++ b/src/main/java/net/b07z/sepia/server/assist/messages/AssistantSocketClient.java @@ -9,11 +9,14 @@ import net.b07z.sepia.server.assist.endpoints.AssistEndpoint; import net.b07z.sepia.server.assist.endpoints.AssistEndpoint.InputParameters; +import net.b07z.sepia.server.assist.interpreters.NluInput; import net.b07z.sepia.server.assist.server.Start; +import net.b07z.sepia.server.assist.services.ServiceResult; import net.b07z.sepia.server.core.server.FakeRequest; import net.b07z.sepia.server.core.server.FakeResponse; import net.b07z.sepia.server.core.tools.Converters; import net.b07z.sepia.server.core.tools.Debugger; +import net.b07z.sepia.server.core.tools.Is; import net.b07z.sepia.server.core.tools.JSON; import net.b07z.sepia.websockets.client.SepiaSocketClient; import net.b07z.sepia.websockets.common.SocketConfig; @@ -54,6 +57,27 @@ public void replyToMessage(SocketMessage msg){ SocketMessage reply = getReply(msg, msg.sender, msg.sender); sendMessage(reply, 3000); } + + /** + * WebSockets support duplex communication which means you can send an answer first and after a few seconds send a follow-up + * message to add more info/data to the previous reply.
+ * Since this is called by {@link Clients} it is assumed that nluInput.isDuplexConnection() was checked before! + * @param nluInput - initial {@link NluInput} to follow-up + * @param serviceResult - {@link ServiceResult} as produced by services to send as follow-up + */ + public void sendFollowUpMessage(NluInput nluInput, ServiceResult serviceResult){ + //use duplex data for channel? + //String channelId = JSON.getString(JSON.parseString(nluInput.duplexData), "channelId"); + //or use receiver id? + //String channelId = nluInput.user.getUserID() + //We use the user ID to post directly into user channel + String channelId = ""; //Note: "" will find the receiver active channel if the sender is "omnipresent" (assistant is) + String receiver = nluInput.user.getUserID(); + String receiverOnError = receiver; + SocketMessage msg = buildFollowUp(serviceResult.getResultJSONObject(), channelId, receiver, receiverOnError); + if (Is.notNullOrEmpty(nluInput.msgId)) msg.setMessageId(nluInput.msgId); //add old ID as reference + sendMessage(msg, 3000); + } //Triggered when this client reads an arbitrary chat message @Override @@ -139,16 +163,27 @@ public void welcomeToChannel(String channelId){ public FakeRequest buildAssistEndpointRequest(SocketMessage msg){ JSONObject data = msg.data; //System.out.println(data); //DEBUG - String text = msg.text; - return buildAssistEndpointRequest(data, text); + return buildAssistEndpointRequest(data, msg.text, msg.msgId, getDuplexData(msg)); } /** * Convert "data" and "text" of {@link SocketMessage} to {@link Request} for {@link AssistEndpoint} call. */ - public FakeRequest buildAssistEndpointRequest(JSONObject data, String text){ + public FakeRequest buildAssistEndpointRequest(JSONObject data, String text, String msgId, JSONObject duplexData){ JSONObject credentials = (JSONObject) data.get("credentials"); JSONObject parameters = (JSONObject) data.get("parameters"); + //add proper connection type and msg ID if not given (to identify Websocket origin and help with duplex connections) + if (!parameters.containsKey(AssistEndpoint.InputParameters.connection.name())){ + JSON.put(parameters, AssistEndpoint.InputParameters.connection.name(), "ws"); + } + if (!parameters.containsKey(AssistEndpoint.InputParameters.msg_id.name()) && Is.notNullOrEmpty(msgId)){ + JSON.put(parameters, AssistEndpoint.InputParameters.msg_id.name(), msgId); + } + //add some duplex connection data + if (duplexData != null){ + JSON.put(parameters, AssistEndpoint.InputParameters.duplex_data.name(), duplexData); + } + //build map with parameters Map keyTextAndParameters = new HashMap<>(); keyTextAndParameters.put("KEY", credentials.get("userId") + ";" + credentials.get("pwd")); //KEY @@ -160,18 +195,22 @@ public FakeRequest buildAssistEndpointRequest(JSONObject data, String text){ } /** * Build a custom request for {@link AssistEndpoint} call. - * @param data - data of {@link SocketMessage} - * @param text - text or direct command + * @param msg - {@link SocketMessage} * @param overwriteParameters - parameters to overwrite, see {@link AssistEndpoint.InputParameters} * @return */ - public FakeRequest buildAssistEndpointCustomCommandRequest(JSONObject data, String text, Map overwriteParameters){ - FakeRequest frq = buildAssistEndpointRequest(data, text); + public FakeRequest buildAssistEndpointCustomCommandRequest(SocketMessage msg, Map overwriteParameters){ + FakeRequest frq = buildAssistEndpointRequest(msg.data, msg.text, msg.msgId, getDuplexData(msg)); overwriteParameters.forEach((String k, String v) -> { frq.overwriteParameter(k, v); }); return frq; } + private JSONObject getDuplexData(SocketMessage msg) { + return JSON.make( + "channelId", msg.channelId + ); + } /** * Build {@link SocketMessage} reply from {@link AssistEndpoint} answer. @@ -199,6 +238,30 @@ public SocketMessage buildReply(JSONObject answer, String channelId, String rece } return reply; } + /** + * Build {@link SocketMessage} assistant follow-up message from {@link ServiceResult} answer. + */ + public SocketMessage buildFollowUp(JSONObject answer, String channelId, String receiver, String receiverOnError){ + SocketMessage reply; + if (!JSON.getString(answer, "result").equals("fail")){ + String answerText = (String) answer.get("answer"); + if (answerText == null){ + reply = new SocketMessage(channelId, getUserId(), receiverOnError, "Error?", TextType.status.name()); + }else{ + //The 'real' message: + JSONObject data = new JSONObject(); + JSON.add(data, "dataType", DataType.assistFollowUp.name()); + JSON.add(data, "assistAnswer", answer); //NOTE: we keep the name 'assistAnswer' here for client ... should have called it 'assistMsg' + reply = new SocketMessage(channelId, getUserId(), receiver, data); + } + + //no login or error + }else{ + reply = new SocketMessage(channelId, getUserId(), receiverOnError, "Login? Error?", TextType.status.name()); + } + reply.setSenderType(SenderType.assistant.name()); + return reply; + } /** * Get a reply and send it to receiver. @@ -235,7 +298,7 @@ public SocketMessage getReply(SocketMessage msg, String receiver, String receive JSON.put(newParams, InputParameters.client.name(), JSON.getString(parameters, InputParameters.client.name())); //REQUIRED! Need more? JSON.put(msg.data, "parameters", newParams); String text = "chat;;reply="; - Request request = buildAssistEndpointRequest(msg.data, text); + Request request = buildAssistEndpointRequest(msg.data, text, msgId, getDuplexData(msg)); JSONObject answer = JSON.parseString(AssistEndpoint.answerAPI(request, new FakeResponse())); reply = buildReply(answer, channelId, receiver, receiverOnError); diff --git a/src/main/java/net/b07z/sepia/server/assist/messages/Clients.java b/src/main/java/net/b07z/sepia/server/assist/messages/Clients.java index 647ee751..2d0b01b3 100644 --- a/src/main/java/net/b07z/sepia/server/assist/messages/Clients.java +++ b/src/main/java/net/b07z/sepia/server/assist/messages/Clients.java @@ -4,12 +4,13 @@ import org.json.simple.JSONObject; +import net.b07z.sepia.server.assist.interpreters.NluInput; import net.b07z.sepia.server.assist.server.Config; +import net.b07z.sepia.server.assist.services.ServiceResult; import net.b07z.sepia.server.core.tools.Connectors; import net.b07z.sepia.server.core.tools.Debugger; import net.b07z.sepia.server.core.tools.FilesAndStreams; import net.b07z.sepia.server.core.tools.JSON; -import net.b07z.sepia.websockets.client.SocketClient; import net.b07z.sepia.websockets.client.SocketClientHandler; import net.b07z.sepia.websockets.common.SocketConfig; @@ -27,7 +28,7 @@ public class Clients { public static SocketClientHandler webSocketMessenger; public static Thread webSocketMessengerThread; - private static SocketClient assistantSocket; + private static AssistantSocketClient assistantSocket; /** * Setup and run webSocket messenger. Creates a new thread to maintain the connection. @@ -74,6 +75,22 @@ public static String getAssistantSocketClientStats(){ } } + /** + * WebSockets support duplex communication which means you can send an answer first and after a few seconds send a follow-up + * message to add more info/data to the previous reply. + * @param nluInput - initial {@link NluInput} to follow-up + * @param serviceResult - {@link ServiceResult} as produced by services to send as follow-up + * @return true if sent, false if not + */ + public static boolean sendAssistantFollowUpMessage(NluInput nluInput, ServiceResult serviceResult){ + if (assistantSocket != null && Config.assistantAllowFollowUps && nluInput.isDuplexConnection()){ + assistantSocket.sendFollowUpMessage(nluInput, serviceResult); + return true; + }else{ + return false; + } + } + /** * Ping webSocket messenger to see if we can connect. * @return true if server answered diff --git a/src/main/java/net/b07z/sepia/server/assist/server/Config.java b/src/main/java/net/b07z/sepia/server/assist/server/Config.java index e16286f3..5fc9d237 100644 --- a/src/main/java/net/b07z/sepia/server/assist/server/Config.java +++ b/src/main/java/net/b07z/sepia/server/assist/server/Config.java @@ -88,6 +88,7 @@ public class Config { //General and assistant settings public static String assistantName = "Sepia"; //**Name - this might become user-specific once ... + public static boolean assistantAllowFollowUps = true; //**allow the assistant to send follow-up messages to requests (plz don't spam the user!) public static String defaultClientInfo = ConfigDefaults.defaultClientInfo; //in case the client does not submit the info use this. public static String userIdPrefix = "uid"; //**prefix used when generating for example user ids or checking them @@ -455,6 +456,10 @@ public static void loadSettings(String confFile){ assistantId = settings.getProperty("assistant_id"); assistantEmail = settings.getProperty("assistant_email"); assistantPwd = settings.getProperty("assistant_pwd"); + String assistantAllowFollowUpsString = settings.getProperty("assistant_allow_follow_ups"); + if (assistantAllowFollowUpsString != null && !assistantAllowFollowUpsString.isEmpty()){ + assistantAllowFollowUps = Boolean.valueOf(assistantAllowFollowUpsString); + } //credentials userIdPrefix = settings.getProperty("user_id_prefix"); guidOffset = Long.valueOf(settings.getProperty("guid_offset")); @@ -530,6 +535,7 @@ public static void saveSettings(String confFile){ config.setProperty("assistant_id", assistantId); config.setProperty("assistant_email", assistantEmail); config.setProperty("assistant_pwd", ""); + config.setProperty("assistant_allow_follow_ups", String.valueOf(assistantAllowFollowUps)); //credentials config.setProperty("user_id_prefix", userIdPrefix); config.setProperty("guid_offset", String.valueOf(guidOffset)); diff --git a/src/main/java/net/b07z/sepia/server/assist/services/ServiceBuilder.java b/src/main/java/net/b07z/sepia/server/assist/services/ServiceBuilder.java index 6e5de033..c66160e5 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/ServiceBuilder.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/ServiceBuilder.java @@ -1,5 +1,8 @@ package net.b07z.sepia.server.assist.services; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + import org.json.simple.JSONArray; import org.json.simple.JSONObject; @@ -7,8 +10,10 @@ import net.b07z.sepia.server.assist.assistant.Assistant; import net.b07z.sepia.server.assist.data.Card; import net.b07z.sepia.server.assist.data.Parameter; +import net.b07z.sepia.server.assist.interpreters.NluInput; import net.b07z.sepia.server.assist.interpreters.NluResult; import net.b07z.sepia.server.assist.interviews.InterviewData; +import net.b07z.sepia.server.assist.messages.Clients; import net.b07z.sepia.server.assist.parameters.Confirm; import net.b07z.sepia.server.assist.services.ServiceInfo.Content; import net.b07z.sepia.server.assist.services.ServiceInfo.Type; @@ -409,6 +414,30 @@ public void makeThisAQuestion(String missing_input_param){ //System.out.println("N=" + nlu_result.input.last_cmd_N + ", DS=" + dialog_stage); //debug } + /** + * Run a task in the background, optionally with a delay. + * @param delayMs - start after this many ms + * @param task - use it like this: () -> { my code... } + */ + public void runInBackground(long delayMs, Runnable task){ + int corePoolSize = 1; + final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(corePoolSize); + executor.schedule(task, delayMs, TimeUnit.MILLISECONDS); + //other option (but does not support lambda expression): + //Timer timer = new Timer(); + //timer.schedule(task, delayMs); + } + + /** + * WebSockets support duplex communication which means you can send an answer first and after a few seconds send a follow-up + * message to add more info/data to the previous reply. Test for nluInput.isDuplexConnection() first! + * @param nluInput - initial {@link NluInput} to follow-up + * @param serviceResult - {@link ServiceResult} as produced by services to send as follow-up + */ + public boolean sendFollowUpMessage(NluInput nluInput, ServiceResult serviceResult){ + return Clients.sendAssistantFollowUpMessage(nluInput, serviceResult); + } + /** * Build {@link ServiceResult} from the info in this class. Handles some specific procedures like context management to generate all necessary info. * From 1b0bd458c9e0a7debbdfc825f4e6952acc309518 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Sat, 6 Apr 2019 23:05:55 +0200 Subject: [PATCH 03/27] added 'infos.app_settings' to ACCOUNT data --- src/main/java/net/b07z/sepia/server/assist/users/ACCOUNT.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/b07z/sepia/server/assist/users/ACCOUNT.java b/src/main/java/net/b07z/sepia/server/assist/users/ACCOUNT.java index a6de620b..859ecf0a 100644 --- a/src/main/java/net/b07z/sepia/server/assist/users/ACCOUNT.java +++ b/src/main/java/net/b07z/sepia/server/assist/users/ACCOUNT.java @@ -56,6 +56,8 @@ public class ACCOUNT { public static final String USER_GENDER = INFOS + ".gender"; //gender - not (yet?) in basics //infos - bot public static final String BOT_CHARACTER = INFOS + ".bot_char"; //preset assistant character + //infos - app settings + public static final String APP_SETTINGS = INFOS + ".app_settings"; //settings for user clients, sorted by device ID //special tokens public static final String ACCESS_LVL_TOKEN = "incAccLvl"; //token (in tokens) generated to increase access level @@ -81,7 +83,7 @@ public class ACCOUNT { //------Collections to handle write access------- - //allow flexible access for things like contacts and favorites + //allow flexible access for things like email, name, address, info (language, settings, etc.) public static boolean allowFlexAccess(String key){ //TODO: Rework this? String flexKey = NluTools.stringFindFirst(key, From 27d2cb0e4eb1deba31a060c60447369747303212 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Wed, 10 Apr 2019 00:15:01 +0200 Subject: [PATCH 04/27] moved basic elasticsearch interface to core tools --- .../b07z/sepia/server/assist/database/DB.java | 2 +- .../server/assist/database/Elasticsearch.java | 448 +----------------- .../server/assist/server/ConfigServices.java | 4 +- 3 files changed, 11 insertions(+), 443 deletions(-) diff --git a/src/main/java/net/b07z/sepia/server/assist/database/DB.java b/src/main/java/net/b07z/sepia/server/assist/database/DB.java index cb2cc870..f8ebe702 100644 --- a/src/main/java/net/b07z/sepia/server/assist/database/DB.java +++ b/src/main/java/net/b07z/sepia/server/assist/database/DB.java @@ -61,7 +61,7 @@ public class DB { public static final String USERS = "users"; //essential user data like account public static final String TICKETS = "tickets"; //tickets with unique IDs that can be a short info like reg. token or some event public static final String STORAGE = "storage"; //unsorted data for later processing - public static final String KNOWLEDGE = "knowledge"; //processed and sorted data for queries + public static final String KNOWLEDGE = "knowledge"; //processed and sorted data for queries - TODO: unused? public static final String USERDATA = "userdata"; //all kinds of user data entries like cmd-mapping, lists, alarms, ... - Type: services, alarms, lists, ..., ID: userID public static final String COMMANDS = "commands"; //system and personal commands - Type: Command.COMMANDS_TYPE public static final String ANSWERS = Answer.ANSWERS_INDEX; //system and personal answers - Type: Answer.ANSWERS_TYPE; diff --git a/src/main/java/net/b07z/sepia/server/assist/database/Elasticsearch.java b/src/main/java/net/b07z/sepia/server/assist/database/Elasticsearch.java index 063346c8..361ee6b3 100644 --- a/src/main/java/net/b07z/sepia/server/assist/database/Elasticsearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/database/Elasticsearch.java @@ -1,15 +1,7 @@ package net.b07z.sepia.server.assist.database; -import java.net.URLEncoder; -import java.util.HashMap; - -import org.json.simple.JSONObject; - import net.b07z.sepia.server.assist.server.Config; import net.b07z.sepia.server.core.database.DatabaseInterface; -import net.b07z.sepia.server.core.tools.Connectors; -import net.b07z.sepia.server.core.tools.Debugger; -import net.b07z.sepia.server.core.tools.JSON; /** * Class to handle an Elasticsearch node. @@ -17,445 +9,19 @@ * @author Florian Quirin * */ -public class Elasticsearch implements DatabaseInterface { - - //ElasticSearch address - final String server = ConfigElasticSearch.getEndpoint(Config.defaultRegion); +public class Elasticsearch extends net.b07z.sepia.server.core.database.Elasticsearch implements DatabaseInterface { /** * Create Elasticsearch class with server defined during Start.loadSettings(). */ public Elasticsearch(){ - //changing server is not supported right now - } - - //-------INTERFACE IMPLEMENTATIONS--------- - - //SET - public int setItemData(String index, String type, String item_id, JSONObject data) { - return writeDocument(index, type, item_id, data); - } - public JSONObject setAnyItemData(String index, String type, JSONObject data) { - return writeDocument(index, type, data); - } - //GET - public JSONObject getItem(String index, String type, String item_id) { - return getDocument(index, type, item_id); - } - public JSONObject getItemFiltered(String index, String type, String item_id, String[] filters) { - //convert filters to sources-string - String sources = ""; - for (String f : filters){ - sources += f.trim() + ","; - } - sources = sources.replaceFirst(",$", "").trim(); - return getDocument(index, type, item_id, sources); - } - //UPDATE - public int updateItemData(String index, String type, String item_id, JSONObject data) { - return updateDocument(index, type, item_id, data); - } - //SEARCH SIMPLE - public JSONObject searchSimple(String path, String search_term){ - //Build URL - if (!path.endsWith("/")) { path = path + "/"; } - try{ - String url = server + "/" + path + "_search?q=" + URLEncoder.encode(search_term, "UTF-8"); - - JSONObject result = Connectors.httpGET(url); - //System.out.println(result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return result; - } - //error - else{ - return result; - } - //error - }catch (Exception e){ - JSONObject res = new JSONObject(); - JSON.add(res, "error", "request failed! - e: " + e.getMessage()); - JSON.add(res, "code", -1); - return res; - } - } - //SEARCH COMPLEX - public JSONObject searchByJson(String path, String jsonQuery) { - if (!path.endsWith("/")) { path = path + "/"; } - try{ - String url = server + "/" + path + "_search"; - //System.out.println("url: " + url); //debug - //System.out.println("query: " + jsonQuery); //debug - JSONObject result = Connectors.httpPOST(url, jsonQuery, null); - //System.out.println(result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return result; - } - //error - else{ - return result; - } - //error - }catch (Exception e){ - JSONObject res = new JSONObject(); - JSON.add(res, "error", "request failed! - e: " + e.getMessage()); - JSON.add(res, "code", -1); - return res; - } - } - //DELETE - public int deleteItem(String index, String type, String item_id) { - return deleteDocument(index, type, item_id); - } - public int deleteAnything(String path) { - return deleteAny(path); + //overwrite server value + this.server = ConfigElasticSearch.getEndpoint(Config.defaultRegion); //override value of base class } - //DELETE COMPLEX - delete item that matches query - public JSONObject deleteByJson(String path, String jsonQuery) { - if (!path.endsWith("/")) { path = path + "/"; } - try{ - String url = server + "/" + path + "_delete_by_query"; - //System.out.println("url: " + url); //debug - //System.out.println("query: " + jsonQuery); //debug - JSONObject result = Connectors.httpPOST(url, jsonQuery, null); - //System.out.println(result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return result; - } - //error - else{ - return result; - } - //error - }catch (Exception e){ - JSONObject res = new JSONObject(); - JSON.add(res, "error", "request failed! - e: " + e.getMessage()); - JSON.add(res, "code", -1); - return res; - } + public Elasticsearch(String server){ + //the constructor is not supported in this explicit implementation + throw new RuntimeException("Not allowed in this explicit implementation. Use 'core.database.Elasticsearch' instead."); } - //--------ELASTICSEARCH METHODS--------- - - /** - * Add a mapping to an index. You can use JSON.readJsonFromFile to get "data" form a JSON file. - * @return JSON response with error code. - */ - public JSONObject writeMapping(String index, JSONObject data){ - //Build URL - String url = server + "/" + index; - - String dataStr = data.toJSONString(); - - //Headers - HashMap headers = new HashMap(); - headers.put("Content-Type", "application/json"); - headers.put("Content-Length", Integer.toString(dataStr.getBytes().length)); - - JSONObject result = Connectors.httpPUT(url, dataStr, headers); - //System.out.println(result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return JSON.make("code", 0); - } - //error - else{ - Debugger.println("putMapping - ElasticSearch - error in '" + index + "': " + result.toJSONString(), 1); - return JSON.make("code", 1); - } - } - - /** - * Get all mappings. - * @return JSONObject with mappings as keys or null - */ - public JSONObject getMappings(){ - //Build URL - String url = server + "/" + "_mappings"; - - JSONObject result = Connectors.httpGET(url); - //System.out.println(result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return result; - } - //error - else{ - Debugger.println("getMappings - ElasticSearch - found no DB or no mappings", 1); - return null; - } - } - - /** - * Write document at "id" of "type" in "index". - * @param index - index name, e.g. "account" - * @param type - type name, e.g. "user" - * @param id - id name/number, e.g. user_id - * @param data - JSON data to put inside id - * @return error code (0 - no error, 1 - no connection or fail) - */ - public int writeDocument(String index, String type, String id, JSONObject data){ - //Build URL - String url = server + "/" + index + "/" + type + "/" + id; - - String dataStr = data.toJSONString(); - - //headers - HashMap headers = new HashMap(); - headers.put("Content-Type", "application/json"); - headers.put("Content-Length", Integer.toString(dataStr.getBytes().length)); - - JSONObject result = Connectors.httpPUT(url, dataStr, headers); - //System.out.println(result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return 0; - } - //error - else{ - Debugger.println("writeDocument - ElasticSearch - error in '" + index + "/" + type + "': " + result.toJSONString(), 1); - return 1; - } - } - /** - * Write document at random id of "type" in "index". - * @param index - index name, e.g. "account" - * @param type - type name, e.g. "user" - * @param data - JSON data to put inside id - * @return JSON with error "code" (0 - no error, 1 - no connection or fail) and "_id" if created - */ - public JSONObject writeDocument(String index, String type, JSONObject data){ - //Build URL - String url = server + "/" + index + "/" + type; - //System.out.println("writeDocument URL: " + url); //debug - - String dataStr = data.toJSONString(); - - //headers - HashMap headers = new HashMap(); - headers.put("Content-Type", "application/json"); - headers.put("Content-Length", Integer.toString(dataStr.getBytes().length)); - - JSONObject result = Connectors.httpPOST(url, dataStr, headers); - //System.out.println("writeDocument Result: " + result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return JSON.make("code", 0, "_id", result.get("_id")); - } - //error - else{ - Debugger.println("writeDocument - ElasticSearch - error in '" + index + "/" + type + "': " + result.toJSONString(), 1); - return JSON.make("code", 1); - } - } - - /** - * Update or create document at "id" of "type" in "index". - * @param index - index name, e.g. "account" - * @param type - type name, e.g. "user" - * @param id - id name/number, e.g. user_id - * @param data - JSON data to put inside id (can also include a script, upsert is added automatically) - * @return error code (0 - no error, 1 - no connection or fail) - */ - public int updateDocument(String index, String type, String id, JSONObject data){ - return updateDocument(index, type, id, data, 0); - } - /** - * Update or create document at "id" of "type" in "index". - * @param index - index name, e.g. "account" - * @param type - type name, e.g. "user" - * @param id - id name/number, e.g. user_id - * @param data - JSON data to put inside id (can also include a script, upsert is added automatically) - * @param retry - number of retries at conflict (default: 0, throws error) - * @return error code (0 - no error, 1 - no connection or fail) - */ - public int updateDocument(String index, String type, String id, JSONObject data, int retry){ - //Build URL - String url = server + "/" + index + "/" + type + "/" + id + "/_update"; - if (retry != 0){ - url += ("?retry_on_conflict=" + retry); - } - - //Check data for script and upsert to get update or create behavior - if (!data.containsKey("script") && !data.containsKey("doc_as_upsert")){ - JSONObject dataUpdate = new JSONObject(); - JSON.put(dataUpdate, "doc", data); - JSON.put(dataUpdate, "doc_as_upsert", new Boolean(true)); - data = dataUpdate; - } - String dataStr = data.toJSONString(); - - //headers - HashMap headers = new HashMap(); - headers.put("Content-Type", "application/json"); - headers.put("Content-Length", Integer.toString(dataStr.getBytes().length)); - - JSONObject result = Connectors.httpPOST(url, dataStr, headers); - //System.out.println(result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return 0; - } - //error - else{ - Debugger.println("updateDocument - ElasticSearch - error in '" + index + "/" + type + "': " + result.toJSONString(), 1); - return 1; - } - } - - /** - * Remove field of document at index/type/id. - * @param index - index name, e.g. "account" - * @param type - type name, e.g. "user" - * @param id - id name/number, e.g. user_id - * @param field - field in document to remove (can contain "." for fields of objects) - * @return error code: 0 (all good) or 1 (connection error) - */ - public int deleteFromDocument(String index, String type, String id, String field){ - //Build URL - String url = server + "/" + index + "/" + type + "/" + id + "/_update"; - - JSONObject data; - if (field.contains(".")){ - String[] paths = field.split("\\."); - String path = ""; - String pathField = ""; - if (paths.length == 2){ - path = "." + paths[0]; - pathField = paths[1]; - }else{ - for (int i=0; i headers = new HashMap(); - headers.put("Content-Type", "application/json"); - headers.put("Content-Length", Integer.toString(dataStr.getBytes().length)); - - JSONObject result = Connectors.httpPOST(url, dataStr, headers); - //System.out.println(result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return 0; - } - //error - else{ - Debugger.println("deleteFromDocument - ElasticSearch - error in '" + index + "/" + type + "': " + result.toJSONString(), 1); - return 1; - } - } - - /** - * Get document at path "index/type/id". - * @param index - index name, e.g. "account" - * @param type - type name, e.g. "user" - * @param id - id name/number, e.g. user_id - * @return JSONObject with document data or error - */ - public JSONObject getDocument(String index, String type, String id){ - //Build URL - String url = server + "/" + index + "/" + type + "/" + id; - - JSONObject result = Connectors.httpGET(url); - //System.out.println(result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return result; - } - //error - else{ - Debugger.println("getDocument - ElasticSearch - error in '" + index + "/" + type + "': " + result.toJSONString(), 1); - return result; - } - } - /** - * Get document at path "index/type/id" with filtered entries. - * @param index - index name, e.g. "account" - * @param type - type name, e.g. "user" - * @param id - id name/number, e.g. user_id - * @param sources - entries in the document you want to retrieve, e.g. "name,address,email", separated by a simple ",". All empty space is removed. - * @return JSONObject with document data or null. If sources are missing they are ignored. - */ - public JSONObject getDocument(String index, String type, String id, String sources){ - return getDocument(index, type, id + "?_source=" + sources.replaceAll("\\s+", "").trim()); - } - - /** - * Delete document at "index/type/id". - * @param index - index name, e.g. "account" - * @param type - type name, e.g. "user" - * @param id - id name/number, e.g. user_id - * @return error code (0 - no error, 1 - no connection or fail) - */ - public int deleteDocument(String index, String type, String id){ - //Build URL - String url = server + "/" + index + "/" + type + "/" + id; - - JSONObject result = Connectors.httpDELETE(url); - //System.out.println(result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return 0; - } - //error - else{ - Debugger.println("deleteDocument - ElasticSearch - error in '" + index + "/" + type + "': " + result.toJSONString(), 1); - return 1; - } - } - /** - * Delete anything. - * @param path - e.g. index/type/id - * @return error code (0 - no error, 1 - no connection or fail) - */ - public int deleteAny(String path){ - //Build URL - String url = server + "/" + path; - - JSONObject result = Connectors.httpDELETE(url); - //System.out.println(result.toJSONString()); //debug - - //success? - if (Connectors.httpSuccess(result)){ - return 0; - } - //error - else{ - Debugger.println("deleteAny - ElasticSearch - error in '" + path + "': " + result.toJSONString(), 1); - return 1; - } - } + //everything here moved to: net.b07z.sepia.server.core.database.Elasticsearch } diff --git a/src/main/java/net/b07z/sepia/server/assist/server/ConfigServices.java b/src/main/java/net/b07z/sepia/server/assist/server/ConfigServices.java index efe245d9..d2db1fdd 100644 --- a/src/main/java/net/b07z/sepia/server/assist/server/ConfigServices.java +++ b/src/main/java/net/b07z/sepia/server/assist/server/ConfigServices.java @@ -21,6 +21,7 @@ import net.b07z.sepia.server.assist.users.UserDataInterface; import net.b07z.sepia.server.core.assistant.CMD; import net.b07z.sepia.server.core.data.CmdMap; +import net.b07z.sepia.server.core.database.DatabaseInterface; import net.b07z.sepia.server.core.java.MaxSizeMap; import net.b07z.sepia.server.core.tools.ClassBuilder; import net.b07z.sepia.server.core.tools.Debugger; @@ -146,7 +147,8 @@ public static void setupSandbox(){ //Framework stuff: blackList.add(Config.class.getPackage().getName()); //server.* blackList.add(AuthEndpoint.class.getPackage().getName()); //endpoints.* - blackList.add(DB.class.getPackage().getName()); //database.* + blackList.add(DB.class.getPackage().getName()); //database.* + blackList.add(DatabaseInterface.class.getPackage().getName()); //database.* from core-tools blackList.add(Interview.class.getPackage().getName()); //interviews.* blackList.add(SendEmail.class.getPackage().getName()); //email.* //TODO: complete blacklist From f587c2c4961c884d21c706b0a3a59793368868f4 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Wed, 10 Apr 2019 00:16:00 +0200 Subject: [PATCH 05/27] improved link-card icons and handling --- .../WebContent/files/images/cards/link.png | Bin 1835 -> 2051 bytes .../images/cards/location_img_default.png | Bin 2336 -> 1590 bytes .../WebContent/files/images/cards/search.png | Bin 1947 -> 1993 bytes .../assist/services/DirectionsGoogleMaps.java | 24 +++++++++++++++--- .../assist/services/LocationSearchBasic.java | 12 +++++++-- .../assist/services/OpenCustomLink.java | 16 +++++++++--- .../assist/services/WebsearchBasic.java | 12 +++++++-- 7 files changed, 52 insertions(+), 12 deletions(-) diff --git a/Xtensions/WebContent/files/images/cards/link.png b/Xtensions/WebContent/files/images/cards/link.png index 70b463098f4e823df60a5baf330bb506fb075644..cd75ca0d02b7622a5c80dabeaedaa0c37d3bf43f 100644 GIT binary patch delta 1894 zcmV-s2buV*4ucSoGJgQ%P)t-s|Ns9G6c`W`7!VW~5EK{?6c`W`7!VW~5EK{?6c`W` z7!VW~5EK{?6d3>i|M&Oz@9*&H>+9y`=icAn)z;S1)6~q&%*4gUzrew}yuG=)yR@~p zuCTGJt*)l0siLH%pP{3kpP`wVnU9f^j*pRxjgN(giGziQe}934e13p>e13I!dU11g zZ*g;LZE$90W?f)nT3ujST3S?BT2NC~QBqS(Pf|-vPDx5jL`X|QMMywFLp(o2Iz2%+ zIy^KtIx{mfDlRc7EHEc3EFvZ;AR{LqA|)FiA{ZMV6&M^678(!~7&>x*ivR!sOmtFC zQveYnGel;0hyt0alV1Ts1mdn91e3S{NPh+`Nkl} zv$Lb)x(gg{|JlU8oRj3_q!%nmINziJe-G`xA{dS{TIJVJ|L$NxIBB=+h1Y0S)TL%SU>woR^ z)KKjrI$OP)6}wkAFZv*5)AH)%a(I@uWz}yfW9x!3n^uR4{%+;N?xkXMt(TwcdA-cx ztdXO2 zuC|<;Yy8^g-RZ#B_^*}aa;4Jo+<(Y{{g_@J53nZJuMS&562ofhanjPp16qH+GFAHG zxZP9c>R$$&jy0xIulNRjV9gD6#Z(hCb6uG`KLWXf!B?gtoEa$R8}q1B4aVocPvQ+t z*F1!{O;zrUa8IsZOv~DSXiyCT#nLS|DFDGgZ7}D+No!J>lCokQF*)w51=% z1Ev<09Ud-?6M}J(EX?U67XJ77D5FDGBRf5M=BG+>-VK50IRRykxteFVf zHvmsyuvg9DY40wP>utj5GTyQU*w;#f*xvj4T4dm%J zt^oDxni4lz)6mmrJH9AP`;;;OmrtA-ZAPKNH$YiaX)w9F5B+tuTz~7vRzf$CqmK-r zFdR_XaxMGqd8Q#v7rLmGcPMHNenO>T?v;tFKWLO@YtU*7r#>g#SPGPND&9V6oaVrk zNVvyKzHqO4w(#+B&vSio$9wl)7z`54jBY6~SXA`&YP|OWdzLa3Xy*HwMY|Qa*;koQ0f&-_E z)P(y;jSuS?J;e`psklM3&d9at0{58$Ta#4WU?At>`jb@P?M3(pv3Gcv@WyoDh#Rg7 zTyqn(^WNL5@c9;Vk3{1)8<^#8{}>K0qS)`+$PK$mYHlhp%YXHP)$b`cJ(wA|X}}y8 zWfm9A?6ARHVVq>Z3B!r!dZ;BYsM%pn`m-c%5cLxS2-Kf3;xsbNz8zt1QhyGV7`ec4 zG{ubJ*)PNPGm_Qc(S}PJdqc2nk|5kCYIS`>FoV(9SG4ORi_dYj<$yJ$u(-`U zk($y(K*aU+3xC{F8_l8DKx)WRA9IS^7f7q_Cp`E3B`r7Hj}VmEIGKFp?A^<`I-#)> zD1jD9mfpS!{fuP%{6c!w_~O= zgRJaq%s@@?qRm116u6I+TQ%`Ab;qPMpqzKZOkBJH!hhQ@Q0zjUhE`le_I_OfFJa`u z3v(cZIA|=%TG*N=|29~7D+1VjL4Eo`Vvdbc+m*PANaWOwA5eDq(q}OkbG3D5ZnS63 z`x=3lLNMV@vjmP%v6C;ZUnB672pn;F@E;y7~{ z!kfGi3{%ptR6>>q951p#1g$?=vN_9TFMr_0AMu%ZkK-|#fm|J9mHIPy#K-3Y(Wb}3 zupP&-w|cavr_`^&Lroy}T4|Byi>F!imns)L#(xF!+@&UvB3E#54V*r$K-gO1_vPi~*5?1$=Jn9!`QP8) z%;NaY-|)rV_Q~7r*4Ea*+VsEK^u^fg#MkP(*7Cj5=C#xC%zwbwe3M{! zj$U<)R&9h;Z8L*WXnjv*drf0`NndnGU2;WQa6(vYK~-x#Qf9}PH+=vA09I2-`*vf(LxNRi43 z3cB0*f^lrV5J9Vq8f1#@V9MbC|39wK^qnRt4fTHD`;+$dS(0zkB(5CCafy{R9>f;> z+Dd|(MKVU!h}1{ZJ>Sa+@x*#&?_4jnE`QM3^_Wx`nJd62xD^0mc6AwmB)0~>#qECz z!Z)zW@nAcCcn|O#0CDA&BmSJh@w?MTejj*!2`}L#{QrfeLQU25s2?hE!i8$%PJ^M= zkArQtaH*kr;$$lZSUK~u)2NDzhD&!SSJN8E`ep|oquFU&7ZwQzyPDQU$gE(e+iujV z)sq`%nf3}XXV?6Q4w98Op5++GN5yY&tCnSxd5+yY?R zJ@eJ5V)n5!_At)X%WR(Xh<6H?lX*UAabi@5GT>KKI1XIB?6<9bdo1M-x z;hPf{v-!WvpL1Da1B-PHHDJyM%U`-Lqw!=i8NWSu%QDur78j++aPEFMamMg^lfw1A zM8d*y=l8@Lqw6%uZEtwbg@k`U|Maobcq!stUqy$7Pm%1jwf{j}ZM(<*lcC_tAXksm zaBe8L=}xMFJITCd#=Bu)hx@C0ke6jSUw(MA54zi#;D&&0cht;UN0r;LdH50SR?v^5 zhHdxRy$mY8n00RtB_h$KhHba!?X)tK2i6G+mln43x#u6A<$q^qx?z7@O4#8_h|{_L zw293EQ@M1o?fx)b$yMRvm*#=_vqZ{gdbsH;Z@Q+oFVI_O(gl@E1N&X$CEG^Hl;x#? zul;$oV?6oB|0)(~T{4Vtt+jZ|E2pXQiVAKbTxkO7`Yi)qcav zl{URgfG?4*V@E|#v6Fw2BBy1gRCDa1l3hF;P`SbI<{RGCbGzF1yS0FsEDdVF$_RzWQ_D2(DE^Y}^bUNOM>S7mdyTiq~_+@`;u8(G48B9L$3v;nEMz}ri z|LI^4>ka+j{ToD|&0L$j-ITk09GK;P?RR#Wu#{Vhxzkzy9o+BEF}aEYFCv!k5?;cL z;n=@OnSQRobNqXJps_uJ_u_|-KS#NYS&ua%GTTS+(=v|xn2P&jI>Y6-L~=F7$Mq>6pIS{$KOg)Lp~p4eLS;~a00000 LNkvXXu0mjfLq&|7 diff --git a/Xtensions/WebContent/files/images/cards/location_img_default.png b/Xtensions/WebContent/files/images/cards/location_img_default.png index 5f021911978a62c90776f090e9e861db497ca990..d8a86af1e862d3d76f9aeec253d6f4ec5f7581ad 100644 GIT binary patch delta 1538 zcmbW1`#Tc~7{`~Cxs^-NEO&-RC}Jowm+cuNw=I{sEMzPqB94`mOE}~@RBB~4X2aYj zF(E4|l@Xddla5PtIFudz2j@B8=Y7B5=Y5{{m(MTX3b{rpQidzqQCdPt0ssI=BN29P zyBYs`fqQrT0J~>mH}=NZB0YdW;OL~w?{nDI$-`bK6v`Yr@?U26b7Ny;d3pKk;-Wwx z;Pc0MydfTsKQQo-(@E-T@91Q=wly`g>l>Pwbq#bzJ*}GgltzC{t*W3<%kMuTlgWSP z6=h`S-_Fdtosn}ZEi3tE=C$i-2}voJWB-VVP7Ec+1ruU$_y~*--qRcJ;u+xN;g59n zL7>mWk?t_Ko8?)QskO6_1=1MiaO{+Wo{7Dlp(Vrsrg{7fK||kCUDs3@VglAN2dNwF z0@=qMx8J2iTyaCY17tN$+J#(AC(~N{zAWvM{7>B<&mGM(LXbqGkalN1;>!oi5`0E% zWO`uHvIsyni>GpI#QTLU$N<6Be^}6gm2Z+9OTV*t8^r`-7GcP2z{;36Q+sE*BY$0`^Kko8zJh9Hw zT{7zuR$<_wU^c+-9J>`A2MRk8Acivxj^7~MoT+QkZAm)rP?K^ew`x*k#%(?Z>tKOl zWR}(5AN<;!^<s5>(h%+SY{Mx=x|2N&doNle6)D zevGqwLtgBEr))gbc+a{*`~`QSa{Uu(>IIsm)3?9L+0^Ee8998^FRJ3DyiXdxgvuHU zKS|<^2EG!fPsUDd?pLxo**6kXOEeEWlNKut8j0-7T`LKWA%d0W?#Vxcx4Tc6O>F_m zH1}&t1f$`Z3dTeGB#`}j8fAc~`fKrU5ET2mw;bnK^nvrKjI+qyBiv9qQQNe}&iL6Y z#@`8$wZhsdfM(i%G7n|~dXf?qw{;|6kXqZ-c_vB5_o8{!Nk{pm0Z>rCzpHC?ckIOn z;9k?A3!(e4Yk0%dH|EpuozJihij2|Arl1*cDzs?i`>q-jRijPH?{G;x<+w1W+05aS8WHB^Z7yQt(?uSgdZ|@SVPj^ZaEYVe{wby2m=}e| z$F~j^Us%%SIEft;G`q0!%SDA03h>)XFtvCX=Cgb&B(RtitI6~2veMyT z*dq_lyRZ3TtQ0Mx#PtgP(SWc|`N}L$l-;Gi&(nZSuOIbxXBBpB!aY`o;~DRblh~cCtZNw@PMR<*xevR@)p$6YT(Ycr$bvnQv$IX7Op_vfu`9Xp^ zQ}~t(RVcuy=rPn-5V~k1gdxD-$T7~|fmfYzdfQbjjqdHvO^L$lMG4a|MH4GlIaIGk z%dc})Yknk82dPL;LbiA}=076*47Q+ey}6?ORaO0quR8M}&WyQRZ2+V_+OEbnAmv|n Cy7J!u delta 2291 zcmYk8c|6qH8^^z6ib*q;B1D$5)u^FTWRRf@LYc8wD9hL{qFlyTwi%*nQL@FfU~I`A zYA}SXrR>}2R}?ech?%k7>E2%V_50(T=XIXf^ZC4<^Zaq%ry8ESOEdAjl_^4auP^`r z2s5mS4L?TzS}+0re(vH*7C%5kFlKld3^vNNp1MzfU_sbDGZQ2HjVxAn#Ce>(d|PjK z%7y8@iaX3C64CZ|lPJ7`-cUrS?H~Rn0oTK-*>$XIl@^0NY^xy8(a`*3^B8Bnl78x^ zYMcVm(zpJc@1PHotb`V@NxGSf7JxWmuzv{bu*U|kQm#3i-7d;hr89yJLN-5-O>#GS z(kC|_gKhdZ9hXM!#kPxRO@|W{^0fC8g!1%`L!-r)$RdF9m65G7viLs8d4eEdtPMz? z+9{CW18Cx*(bT;F{1f0hds$4AU;XRU4Y(HYtE8?p_QgwU;^S^o_ru|mZmz$j8IZ>F z2q3}pUp3P0A1TOp67Oe|JPvt8KRQDTGIV^S9(Ez=#6hDXyt;e2E=(z{h(5?91o#K~ zu{i@{pN4d&ej2SMAu5h9IT94umiJVrnaRt={&fWf?GbkDxq5CZ*CPH*Xvz3R>Bvmw zt`GZ6L{Gr~^Z3-EEYgUMe>SDCR;^!~?)Io-NIcO5nTB#qlXt#+$xsvf zMy1J(0eo80~?Vz9mYh)OWg|xwu{RB)u5B*Mrmc2AXRx-%J^j`I}cX5Qe5TB?~ zdAzsR;O*qA3$hIcQ;TQFre!LjMtD44BClQ*$C!X}C#Cfw4WcT=b0~BN@9_{t`kRSl zZ}TH*sO0-MNhkquXDODE%^CHpj_swh0)~%LvxDB@Qr3LTWEPHwx35YYAO_!cYbNEk=YMUX*V)k_C}}3 z4wS*0wc3voHb!i9PJkS$EZ-^gbWv+kzoVA<^8icq?WU5sT@P-I?mD~&d4scvWcq%! z%j&_v_C;-V@6&=9 zn~7^k&BZw&uCMi;&pHGq_7rc>fDAdB5@qYItQ5vs0^-2)j{bUWK^30e!p^tk+P?W} zD7T0h?!=&jBO2m!azwp`a=`HStiNJ}Zew*sVW||l#?BcaGPnnj56%^%LSWQ{hZA+D zaDVcMZiJ%Jo@*sEH8_F^&_$#VwpnlKp|$9td1C0HyO=n^ZRSB9z9AMC1rd{1ype?j zh-la|@XiwmdYTvoHjOHf{seAkbNc31V}&fT1h;X_CvTL|bX8PzO;$29GElR!iNZ3Q zXxHNghB@Foes-R|%#%^jnmi+PdNF3%P!2?+FL)y2`Id z;iv3j`ozX9vk8%U#N(NY9AZaQ|AAafBSmmdQlioCoBmRhTJuQc7Q?!GPjR4aGrWkb za)fVMh481i2i!4#9^(+ABR32pYqd@Kx}S-|oX9ChN+IkVl!DZu_OUcdy`auwF58Kp zAGhm~%rq}ewR*GqViqxzbZ&~7Z0iv2r&H|`6Kz^hIIuozEK}=(7)MarmN?+C@D2It zQXxv?yP4-kYnrN1wvwrJe!vnh_pn?Cv8uepYH{o%^gO0JMLH^sRis%gd-b|V z4k$D!o#OXMHvRG`&$K*hkGQxNb%w{|$s4B5tlSAjZCVY@hTozRYlY+kYM*o8_OP5) zmp@XoF5-3%sMVE~{do0E6a+BsADHCiMU-=ro{LWC*QYqI*vO!GnGf2oaqBUd3Yj_2 zKqApN%E+x?LtNO-;N<`4B8o7B9D4B+&)y^1Ci`XbGe%hx*Qdz$8}xHtx<1Ws2NdH1 zRzkWh2P@u6#^!JFFqY5(OI3{PQ*E3lAciTiTRSIS^JCNF`o>%)sK%d@%=-A;j;6D{ zm$tDOEd|A|UZO3FUVpatH%ELSg;@WH+ZQ;t`-Ic6R>9~QZs&9D(a@p;2ewvsKeY30 zTc`*T(*5AIalQGhWfkN!EU2a-;YZ=P9w^~nGrEzfCIU`E!AW)45J8*0I%dLb_@IL_0vC-@&2!0e2bNh!uP=5L@` BPEG&- diff --git a/Xtensions/WebContent/files/images/cards/search.png b/Xtensions/WebContent/files/images/cards/search.png index 84e6203b186af69f8125416098cddeb45ec78d08..61bbc607bdf00ecad66a580b98febf5e4d25008e 100644 GIT binary patch delta 1700 zcmV;V23z@?56KUZGcV^*OjJex|NjjV84VH{4H6j*5*ZB=84VH{4H6j*5*ZB=84VH{ z4H6j*5*ZB=8Iff_k*7#UD=;`IFEu7AF(W829wRFoASW3fBo`YZ5f>g2790%{843>< z2n`bk3K0Sa4*&%W0RadA001;${AU0F08MmKPE!C8A~QsjE&)ac|Nr=pY6p{e0ZD)F zNkl6}Txw5g zR*qHrFqEsWR7pMPO8T1WclV$oY`cGlr*71%!r0n_ve-a_UC{@~sm-gu(}0(m+pN^? z*4VbdXR$ika=g2{VFPW`>DJ-yT-!b@9nO>;W;ZFdWI(B4n7PUfce8ZJfYd_=@Ma~` z8|x5Bu9}M_SI@tU$l>ledkmm(Ku25`PX=*qqwX7zRD zl|dgBaXT>A)XsC*Kp&}N;77cZ=N>>7O$q&2hz<0Ij-P8apL;@iET#Vlv4Or(^mzA6 zSe9v-*C2N|{Io!mv-Sn-7hHcsg7tMmEhKn=?Xc<64CHM5aQ}ESN`E-AvE+1@#fy@a z>hI|jWo+en?us$vF$o_zNOEeKIuJfa%|zo@Q?`sOm{?0TIg7u%ct_fH(y)cWlG50& zj(;#G-8S*qBqrZ8961>{g+5GTKf0lAzG3Pm{j z4i~lgSc{yPdc46OF4z!x;}6bJUFNPVvT65A*xYXtN1>Dt_5=kE?YbrUp7DOn97#)b z8a{ccqsDrZ_K7k8laGJ&sWdW}i(j}$85nQ}bSC$2?n+B}Q_p;GN;s4#*J3;-A(nZI zo7XdHk~g`R=-X(D5iyv~xgFv>8C;4v_?*cbGU>_Y;SmnphQfyvg2#v{wwF9&<3orr zt<}S(9=uXuv{?(24-UTH1GopJdH;n?J;L#Fqg6TZ00O&VQ#XGrL`&|t)Ep0>?1NkN z2xZk__H}}@Y87x}n;n<%;~5_wWM&V7`REz)#5Imw$W#*oyRY23q>fBXzJktBwgrPt z9zjOZv5s>jtpacytiQ`Vnevgo#;wN^u=^A?cgkRsAtv9S07kALiQ_GP0M1wlJ1^VeH5fWJb$;hfeim*L2#dz$ z0k@N|H;V*x+GFq)%Fos9;zko(OpX+)10@1MtMpsc>ZM8YXkyWe=!usV(up^T0C=EB z=%=V%-&d%a{*uzf{@C9-33ql`wV`BhExJgYH(X5@jun5xO=_9i+wpL?6&Xx#Lo!XC zbo~1AHaC{OTsoV48Rmo}AAt#dD9gzl@SNQG8lUBo(YiK!=1b9s zr$dI+C0?Qmo`k zkvQpt?U~5Jl830`W8*S%(|y(iUX{8JspR&SI9h*-tb0Y|^nI@MmJLU?)3&`RN)a=+ zAzKws<;iJ(9xd~^Q06kEqAvU7f0N{w{1zm4y|{JZR+gOX^EkJ)&RuxHSA3<&GXPw0 z#V5@N&}2Kd?bqy37hGz}a<39(hhJ44XE%F=*SJ;LR~$W0wX8b{O4=%`#UR%E^LYIF zp5=cED>AApPM{uRxqY6nIHA6BjM@rN>MOaaugIvrvZ~q&$?7YetFKtFzVgWW3PKyT zmB2RYEBdXk%(+opVRxgxQg&aRU5fV$^DC_CdR?JW+Y1_NbUm-61ghyJoVDtC?P#r< uUbNb%mRHLnwUti#WxCG(U#6>2KLLa~I|UFHPq*U$0000` delta 1654 zcmV-+28sE}51S8=Gcx2*OjJex|NkB%D;^^&9wRFrBP$*wD;^^&9wRFrBP$*wD;^^& z9wRFrBP$*wD;^^&kzzjsn4P7Op-4Y1GCC_TI3_DHBPcK)BP$#sC>If|5g7^( z7X=Ct0tXKO1q=WH00007{G-Ws0000?bW%=J01+ZHL{*b80Y(HZt|hXQd;v*+#Ysd# zRCt{2n`>9&HV}rfL&z1<4dL2l$!mmrQ?>~>wiMe@{{R138(XqvThcfYN9XkP z{lXzJdeCTQG`e_a=YPp%S$4DC>GfTvey`JR+Lra6P`lCU9k`V`=(QU5yTGl+UbmW= zsk_(E&9>E4%Cf zV_VdIYh!GquWnwZeq$r7)!GgltF=K`+r90%uG_0U(z{!XZeIH}gL+R7Hr8GpZd)JM zb=!uxaI)3W%XQrj%(=C5BeDTICjwaeUAO?U#yH}oh^pQ9;<~Ohlh~Sn@>P}gW!V#! zpAp6w)hGAFT=jf}140OLoEIGZ3~JJa6LdrIhbSbAr;9>DiJp7NNg|*jV2Y6x}Uz zcg1xq#urlVVDNbXBp;N2(}=jkfv3PmIz)KGE{i)HpaSH0e0y_uIpRMYEn(zRmxUe1 zTz`-55h3d^4Az8rcX{~8hmi|iZiqUJaIW$58Chk6iC1KLoPBw^21WD%pG8Eou!l0g z8vY=~kPXQ+Bi|4Rn4g)2G3;e=bcuqB@59`G_D=`_bFnO({xipa`cn|AILHzyEU0oN zfhBS+%<|Pe7L6eCujxY*Q1^a`@oIW;#bUEi+6!odKPvnx|KKxy@D)O4wzdS84!)+%{i+BQ4^d-JLAIt~QRP8sKjtc@L9_J9 zGXOWMLWz^fq>Cr^6&@*ZpvmRoGhaR3kW<<&VoXps8S7 z2kgqg%&SKruTHD4Lml8@jX0*`a;XDMt>>u=*DQ0v7LQVJ@I<{!9mtr@pmPvc4rUod zJ;+&&dAIjP&>zHE|+<$s|~{_YEhnJ~NDLib^MvS~39ZF6wF ztbV}DxR4qtQAbt?ya|10t)5k`o-jIPPdq{@hBvjPrB6cs%-W4&rO>75_uTq8S~>wY zLekPlRO}^fFLL`0=d*>6Q#e8xJI}+xU=y+}EpKUmukt$`A0D4yyj7$C&8J@nCBevP zc`GXDQ(?cb!E-p7Os>YqWh$7Q7WAs!V(gz*Ln`$O2r0UysoE~a>uWWT$yJwVbkQas zc#VGax79H&t<-y+k64oIzhY6{DDqIAMfXm%TgKUtu2eH z+;&rq^EJ;yOkD0>zCy|QGdCRvUA_w=^Tz`^jxv3IV|y;}F#dQGpPa1vfD(`D?aSb7+bpVkGg^b$@iL$4hrY$GpPQMid$%l;es2_f8MMMxZ=qW}N^07*qoM6N<$g8G;V A9RL6T diff --git a/src/main/java/net/b07z/sepia/server/assist/services/DirectionsGoogleMaps.java b/src/main/java/net/b07z/sepia/server/assist/services/DirectionsGoogleMaps.java index 281188fb..16ff397f 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/DirectionsGoogleMaps.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/DirectionsGoogleMaps.java @@ -439,7 +439,11 @@ public ServiceResult getResult(NluResult nluResult){ Card googleCard = new Card(Card.TYPE_SINGLE); //JSONObject linkCard1 = googleCard.addElement(ElementType.link, - JSON.make("title", "Google Maps", "desc", description), + JSON.make( + "title", "Google Maps", + "desc", description, + "type", "maps" + ), null, null, "", googleMapsURL, Config.urlWebImages + "/brands/google-maps.png", @@ -452,7 +456,11 @@ public ServiceResult getResult(NluResult nluResult){ Card appleCard = new Card(Card.TYPE_SINGLE); //JSONObject linkCard2 = appleCard.addElement(ElementType.link, - JSON.make("title", "Apple Maps", "desc", description), + JSON.make( + "title", "Apple Maps", + "desc", description, + "type", "maps" + ), null, null, "", appleMapsURL, Config.urlWebImages + "/brands/apple-maps.png", @@ -556,7 +564,11 @@ public static ArrayList buildEndAlternativeCards(JSONArray options, String String locStreet = JSON.getStringOrDefault(locJSON, InterviewData.LOCATION_STREET, ""); String locAddr = JSON.getStringOrDefault(locJSON, InterviewData.LOCATION_ADDRESS_TEXT, ""); String locName = JSON.getStringOrDefault(locJSON, InterviewData.LOCATION_NAME, ""); - String locImage = JSON.getStringOrDefault(locJSON, InterviewData.LOCATION_IMAGE, Config.urlWebImages + "cards/location_img_default.png"); + String locImage = JSON.getStringOrDefault(locJSON, InterviewData.LOCATION_IMAGE, ""); + boolean isDefaultImage = locImage.isEmpty(); + if (isDefaultImage){ + locImage = Config.urlWebImages + "cards/location_img_default.png"; + } String mapUrl = ""; try{ mapUrl = makeGoogleMapsURL(start, loc, wp, googleTravelType, googleViews); @@ -571,7 +583,11 @@ public static ArrayList buildEndAlternativeCards(JSONArray options, String Card card = new Card(Card.TYPE_SINGLE); //JSONObject linkCard = card.addElement(ElementType.link, - JSON.make("title", "", "desc", (locName.isEmpty()? "" : ("" + locName + "
")) + desc.trim()), //we dont use title because names are so loooong most of the time :( + JSON.make( + "title", "", //we dont use title because names are so loooong most of the time :( + "desc", (locName.isEmpty()? "" : ("" + locName + "
")) + desc.trim(), + "type", ((isDefaultImage)? "locationDefault" : "locationCustom") + ), null, null, "", mapUrl, locImage, diff --git a/src/main/java/net/b07z/sepia/server/assist/services/LocationSearchBasic.java b/src/main/java/net/b07z/sepia/server/assist/services/LocationSearchBasic.java index 60a9a18a..c70dc002 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/LocationSearchBasic.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/LocationSearchBasic.java @@ -183,7 +183,11 @@ public ServiceResult getResult(NluResult nluResult){ Card googleCard = new Card(Card.TYPE_SINGLE); //JSONObject linkCard1 = googleCard.addElement(ElementType.link, - JSON.make("title", "Google Maps", "desc", description), + JSON.make( + "title", "Google Maps", + "desc", description, + "type", "maps" + ), null, null, "", googleMapsURL, Config.urlWebImages + "/brands/google-maps.png", @@ -196,7 +200,11 @@ public ServiceResult getResult(NluResult nluResult){ Card appleCard = new Card(Card.TYPE_SINGLE); //JSONObject linkCard2 = appleCard.addElement(ElementType.link, - JSON.make("title", "Apple Maps", "desc", description), + JSON.make( + "title", "Apple Maps", + "desc", description, + "type", "maps" + ), null, null, "", appleMapsURL, Config.urlWebImages + "/brands/apple-maps.png", diff --git a/src/main/java/net/b07z/sepia/server/assist/services/OpenCustomLink.java b/src/main/java/net/b07z/sepia/server/assist/services/OpenCustomLink.java index 8a164a41..bc6db8d5 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/OpenCustomLink.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/OpenCustomLink.java @@ -53,7 +53,8 @@ public static ServiceResult get(NluResult nluResult){ if (description.isEmpty()) description = ActionBuilder.getDefaultButtonText(api.language); String iconUrl = nluResult.getParameter("icon_url"); //icon URL to be used for link-card - if (iconUrl.isEmpty()) iconUrl = Config.urlWebImages + "/cards/link.png"; + boolean isCustomIcon = !iconUrl.isEmpty(); + if (!isCustomIcon) iconUrl = Config.urlWebImages + "/cards/link.png"; //handle parameters/answers/questions //p @@ -139,7 +140,7 @@ public static ServiceResult get(NluResult nluResult){ callURL = ""; //e.printStackTrace(); } - api.addAction(ACTIONS.OPEN_URL); + api.addAction(ACTIONS.OPEN_IN_APP_BROWSER); api.putActionInfo("url", callURL); /* api.actionInfo_add_action(ACTIONS.BUTTON_URL); @@ -149,13 +150,20 @@ public static ServiceResult get(NluResult nluResult){ //card Card card = new Card(Card.TYPE_SINGLE); + String linkType = (isCustomIcon)? "custom" : "default"; JSONObject linkCard = card.addElement(ElementType.link, - JSON.make("title", title, "desc", description), + JSON.make( + "title", title, + "desc", description, + "type", linkType + ), null, null, "", callURL, iconUrl, null, null); - JSON.put(linkCard, "imageBackground", "transparent"); //use any CSS background option you wish + if (isCustomIcon){ + JSON.put(linkCard, "imageBackground", "transparent"); //use any CSS background option you wish + } api.addCard(card.getJSON()); api.hasAction = true; diff --git a/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java b/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java index b50c0aea..16e12b65 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java @@ -138,7 +138,11 @@ public ServiceResult getResult(NluResult nluResult){ Card card = new Card(Card.TYPE_SINGLE); /*JSONObject linkCard = */ card.addElement(ElementType.link, - JSON.make("title", title1 + ":", "desc", "\"" + search + "\""), + JSON.make( + "title", title1 + ":", + "desc", "\"" + search + "\"", + "type", "websearch" + ), null, null, "", search_url, engineIconUrl, @@ -157,7 +161,11 @@ public ServiceResult getResult(NluResult nluResult){ Card card2 = new Card(Card.TYPE_SINGLE); /*JSONObject linkCard2 = */ card2.addElement(ElementType.link, - JSON.make("title", title2 + ":", "desc", "\"" + search + "\""), + JSON.make( + "title", title2 + ":", + "desc", "\"" + search + "\"", + "type", "websearch" + ), null, null, "", search_url_rnd, engineIconUrlRnd, From 02a2e53420dfe4d4c4e406b1b4e161f09aba7746 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Wed, 10 Apr 2019 00:16:39 +0200 Subject: [PATCH 06/27] improved number and action parameters --- Xtensions/Assistant/answers/answers_de.txt | 2 +- Xtensions/Assistant/answers/answers_en.txt | 2 +- .../interpreters/RegexParameterSearch.java | 77 ++++++++++++++++--- .../server/assist/parameters/Action.java | 6 +- .../server/assist/parameters/Number.java | 9 ++- 5 files changed, 79 insertions(+), 17 deletions(-) diff --git a/Xtensions/Assistant/answers/answers_de.txt b/Xtensions/Assistant/answers/answers_de.txt index 46265986..9a6007c5 100644 --- a/Xtensions/Assistant/answers/answers_de.txt +++ b/Xtensions/Assistant/answers/answers_de.txt @@ -25,7 +25,7 @@ ok_0c;; rep=0|mood=5;; Ok, ist erledigt. ;;char=neutral default_ask_parameter_0a;; rep=0|mood=5;; Hmm, die Anfrage ist scheinbar unvollständig, was ist der fehlende Parameter? ;;char=polite,neutral default_ask_parameter_0a;; rep=1|mood=5;; Und der nächste fehlende Parameter? ;;char=cool,rude,neutral default_ask_parameter_0a;; rep=2|mood=5;; Und noch einen Parameter bitte! ;;char=cool,rude -default_ask_parameter_0b;; rep=0|mood=5;; Hmm, die Anfrage ist scheinbar unvollständig, was ist der fehlende Parameter? ;;char=neutral +default_ask_parameter_0b;; rep=0|mood=5;; Hmm, die Anfrage ist scheinbar unvollständig, wie lautet der Wert des fehlende Parameters? ;;char=neutral default_ask_parameter_0b;; rep=1|mood=5;; Ne, hat nicht funktioniert, versuch noch mal bitte. ;;char=cool default_ask_parameter_0b;; rep=2|mood=5;; Nein, auch das hat nicht geklappt. Einmal noch bitte. ;;char=neutral default_ask_action_0a;; rep=0|mood=5;; Welche Aktion soll ich ausführen? ;;char=neutral diff --git a/Xtensions/Assistant/answers/answers_en.txt b/Xtensions/Assistant/answers/answers_en.txt index 84729568..9518042a 100644 --- a/Xtensions/Assistant/answers/answers_en.txt +++ b/Xtensions/Assistant/answers/answers_en.txt @@ -25,7 +25,7 @@ ok_0c;; rep=0|mood=5;; Ok, it's done. ;;char=neutral default_ask_parameter_0a;; rep=0|mood=5;; Hmm the request seems incomplete, what is the missing parameter? ;;char=neutral default_ask_parameter_0a;; rep=1|mood=5;; And the next missing parameter? ;;char=neutral default_ask_parameter_0a;; rep=2|mood=5;; And another missing parameter? ;;char=neutral -default_ask_parameter_0b;; rep=0|mood=5;; Hmm the request seems incomplete, what is the missing parameter? ;;char=neutral +default_ask_parameter_0b;; rep=0|mood=5;; Hmm the request seems incomplete, what is the value of the missing parameter? ;;char=neutral default_ask_parameter_0b;; rep=1|mood=5;; No that did not work, try again please. ;;char=neutral default_ask_parameter_0b;; rep=2|mood=5;; No, still not working, try once more please. ;;char=neutral default_ask_action_0a;; rep=0|mood=5;; Which action should I perform? ;;char=neutral diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java index e09020ad..9e881d7c 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java @@ -117,13 +117,13 @@ public static String get_number(String input){ * @param language - language code * @return original input with replaced number if there was any */ - public static String replace_text_number(String input, String followedBy, String language){ + public static String replace_text_number_followed_by(String input, String followedBy, String language){ followedBy = "(" + followedBy + ")"; //German if (language.matches("de")){ input = input .replaceAll("\\b(null) " + followedBy, "0 $2") - .replaceAll("\\b(einer|eine|einem) " + followedBy, "1 $2") + .replaceAll("\\b(einer|eine|einem|eins|ein) " + followedBy, "1 $2") .replaceAll("\\b(zwei) " + followedBy, "2 $2") .replaceAll("\\b(drei) " + followedBy, "3 $2") .replaceAll("\\b(vier) " + followedBy, "4 $2") @@ -158,7 +158,8 @@ public static String replace_text_number(String input, String followedBy, String //English - and missing language support ... }else{ - input = input.replaceAll("\\b(null) " + followedBy, "0 $2") + input = input + .replaceAll("\\b(null) " + followedBy, "0 $2") .replaceAll("\\b(zero) " + followedBy, "0 $2") .replaceAll("\\b(one) " + followedBy, "1 $2") .replaceAll("\\b(two) " + followedBy, "2 $2") @@ -184,7 +185,63 @@ public static String replace_text_number(String input, String followedBy, String .replaceAll("\\b(twenty(-| )three) " + followedBy, "23 $2") .replaceAll("\\b(twenty(-| )four) " + followedBy, "24 $2") .replaceAll("\\b(twenty) " + followedBy, "20 $2") - .replaceAll("\\b(thirty) " + followedBy, "20 $2") + .replaceAll("\\b(thirty) " + followedBy, "30 $2") + ; + return input; + } + } + /** + * Replace a textual number like "one" by real number. Works with numbers from "null/zero" to "fifteen". + * @param input - text input + * @param language - language code + * @return original input with replaced number if there was any + */ + public static String replace_text_number(String input, String language){ + //German + if (language.matches("de")){ + input = input + .replaceAll("\\b(null)\\b", "0") + .replaceAll("\\b(eins)\\b", "1") + .replaceAll("\\b(zwei)\\b", "2") + .replaceAll("\\b(drei)\\b", "3") + .replaceAll("\\b(vier)\\b", "4") + .replaceAll("\\b(fuenf)\\b", "5") + .replaceAll("\\b(sechs)\\b", "6") + .replaceAll("\\b(sieben)\\b", "7") + .replaceAll("\\b(acht)\\b", "8") + .replaceAll("\\b(neun)\\b", "9") + .replaceAll("\\b(zehn)\\b", "10") + .replaceAll("\\b(elf)\\b", "11") + .replaceAll("\\b(zwoelf)\\b", "12") + .replaceAll("\\b(dreizehn)\\b", "13") + .replaceAll("\\b(vierzehn)\\b", "14") + .replaceAll("\\b(fuenfzehn)\\b", "15") + .replaceAll("\\b(zwanzig)\\b", "20") + .replaceAll("\\b(dreissig)\\b", "30") + ; + return input; + + //English - and missing language support ... + }else{ + input = input + .replaceAll("\\b(null|zero)\\b", "0") + .replaceAll("\\b(one)\\b", "1") + .replaceAll("\\b(two)\\b", "2") + .replaceAll("\\b(three)\\b", "3") + .replaceAll("\\b(four)\\b", "4") + .replaceAll("\\b(five)\\b", "5") + .replaceAll("\\b(six)\\b", "6") + .replaceAll("\\b(seven)\\b", "7") + .replaceAll("\\b(eight)\\b", "8") + .replaceAll("\\b(nine)\\b", "9") + .replaceAll("\\b(ten)\\b", "10") + .replaceAll("\\b(eleven)\\b", "11") + .replaceAll("\\b(twelve)\\b", "12") + .replaceAll("\\b(thirteen)\\b", "13") + .replaceAll("\\b(fourteen)\\b", "14") + .replaceAll("\\b(fifteen)\\b", "15") + .replaceAll("\\b(twenty)\\b", "20") + .replaceAll("\\b(thirty)\\b", "30") ; return input; } @@ -566,7 +623,7 @@ public static HashMap get_age(String input, String language){ //German if (language.matches("de")){ //years (from 1999, 50 years ...) - input = replace_text_number(input, "(jahr(en|e|))", language); + input = replace_text_number_followed_by(input, "(jahr(en|e|))", language); tag_y = NluTools.stringFindFirst(input, "(jahr(en|e|))"); //age_y = NLU_Tools.stringFindFirst(input, "(<|>|)(\\d+ " + tag_y + "|1\\d\\d\\d|2\\d\\d\\d)"); String age_y_regex; @@ -601,7 +658,7 @@ public static HashMap get_age(String input, String language){ //English - and missing language support ... }else{ //years (from 1999, 50 years ...) - input = replace_text_number(input, "(years|year)", language); + input = replace_text_number_followed_by(input, "(years|year)", language); tag_y = NluTools.stringFindFirst(input, "(years|year)"); //age_y = NLU_Tools.stringFindFirst(input, "(<|>|)(\\d+ " + tag_y + "|1\\d\\d\\d|2\\d\\d\\d)"); String age_y_regex; @@ -1040,7 +1097,7 @@ public static HashMap get_date(String text, String language){ //German if (language.matches(LANGUAGES.DE)){ //note: make sure you use the same replacements in "remove_date(..)" too! - text = replace_text_number(text, DateAndTime.TIME_TAGS_DE, language); + text = replace_text_number_followed_by(text, DateAndTime.TIME_TAGS_DE, language); time_tag = NluTools.stringFindFirst(text, DateAndTime.TIME_UNSPECIFIC_TAGS_DE); if (!time_tag.isEmpty()) text = NluTools.stringRemoveFirst(text, time_tag); @@ -1067,7 +1124,7 @@ public static HashMap get_date(String text, String language){ //English and non-supported language }else{ //note: make sure you use the same replacements in "remove_date(..)" too! - text = replace_text_number(text, DateAndTime.TIME_TAGS_EN, language); + text = replace_text_number_followed_by(text, DateAndTime.TIME_TAGS_EN, language); time_tag = NluTools.stringFindFirst(text, DateAndTime.TIME_UNSPECIFIC_TAGS_EN); if (!time_tag.isEmpty()) text = NluTools.stringRemoveFirst(text, time_tag); @@ -1145,7 +1202,7 @@ public static String remove_date(String text, String date, String language){ //English if (language.equals(LANGUAGES.EN)){ - text = replace_text_number(text, DateAndTime.TIME_TAGS_EN, language); + text = replace_text_number_followed_by(text, DateAndTime.TIME_TAGS_EN, language); text = text.replaceFirst("\\b(from the |from |(starting|arrival) at (the |))(" + Pattern.quote(date.trim()) + ")(\\s|$)", "").trim(); text = text.replaceFirst("\\b(until the |until |till the |till |(departure) at (the |)|to the |to )(" + Pattern.quote(date.trim()) + ")(\\s|$)", "").trim(); text = text.replaceFirst("\\b(for the |for |in the |during the |at the |at |the |this |in |on |)(" + Pattern.quote(date.trim()) + ")(\\s|$)", "").trim(); @@ -1153,7 +1210,7 @@ public static String remove_date(String text, String date, String language){ //German }else if (language.equals(LANGUAGES.DE)){ - text = replace_text_number(text, DateAndTime.TIME_TAGS_DE, language); + text = replace_text_number_followed_by(text, DateAndTime.TIME_TAGS_DE, language); text = text.replaceFirst("\\b(von dem |vom |ab dem |ab |von |anreise (am|an dem) )(" + Pattern.quote(date.trim()) + ")(\\s|$)", "").trim(); text = text.replaceFirst("\\b(bis zu dem |bis zum |bis |abreise (am|an dem) )(" + Pattern.quote(date.trim()) + ")(\\s|$)", "").trim(); text = text.replaceFirst("\\b(fuer den |fuer |in dem |in den |waehrend der |an dem |der |die |dem |diesem |diesen |dieser |in |am |)(" + Pattern.quote(date.trim()) + ")(\\s|$)", "").trim(); diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/Action.java b/src/main/java/net/b07z/sepia/server/assist/parameters/Action.java index 94ee7dd7..a5b5050a 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/Action.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/Action.java @@ -143,7 +143,8 @@ public String extract(String input) { decrease = "(mach|dreh) .*\\b(runter|aus)|" + "runterdrehen|runter|kleiner|niedriger|erniedrigen|erniedrige|abschwaechen|schwaech(er|en|e)|senk(en|e|)|dunkler|dimmen|dimme|(? Date: Wed, 10 Apr 2019 00:18:46 +0200 Subject: [PATCH 07/27] some clean-ups and new version number v2.2.2 --- pom.xml | 2 +- .../sepia/server/assist/chats/HowAreYou.java | 17 ++++++++++++++ .../server/assist/events/EventsManager.java | 23 ++++++++++++++----- .../sepia/server/assist/server/Config.java | 2 +- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 4aea8ee0..132971b3 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ net.b07z.sepia.server.assist sepia-assist-API - 2.2.1 + 2.2.2 SEPIA Assist API Core server of the SEPIA Framework for personal digital assistants. Responsible for NLU, conversation, smart-service integration, user-accounts and more. https://sepia-framework.github.io diff --git a/src/main/java/net/b07z/sepia/server/assist/chats/HowAreYou.java b/src/main/java/net/b07z/sepia/server/assist/chats/HowAreYou.java index 42c03dd4..d74807e0 100644 --- a/src/main/java/net/b07z/sepia/server/assist/chats/HowAreYou.java +++ b/src/main/java/net/b07z/sepia/server/assist/chats/HowAreYou.java @@ -40,6 +40,23 @@ public static ServiceResult get(NluResult nluResult){ //anything else? api.context = CMD.CHAT; //how do we handle chat contexts? Just like that and do the rest with cmd_summary? + //DEBUG + /* + if (nluResult.input.isDuplexConnection()){ + System.out.println(nluResult.input.connection); + System.out.println(nluResult.input.msgId); + System.out.println(nluResult.input.duplexData); + api.runInBackground(3000, () -> { + //initialize follow-up result + ServiceBuilder service = new ServiceBuilder(nluResult); + service.answer = Answers.getAnswerString(nluResult, "Ach was ich noch sagen wollte. Habs vergessen."); + service.status = "success"; + boolean wasSent = service.sendFollowUpMessage(nluResult.input, service.buildResult()); + return; + }); + } + */ + //finally build the API_Result ServiceResult result = api.buildResult(); diff --git a/src/main/java/net/b07z/sepia/server/assist/events/EventsManager.java b/src/main/java/net/b07z/sepia/server/assist/events/EventsManager.java index fca3f00b..747e621f 100644 --- a/src/main/java/net/b07z/sepia/server/assist/events/EventsManager.java +++ b/src/main/java/net/b07z/sepia/server/assist/events/EventsManager.java @@ -113,13 +113,13 @@ public static JSONObject buildCommonEvents(NluInput input){ //random messages to entertain the user if (localTimeIsKnown){ - addScheduledMessage(actionBuilder, EventDialogs.Type.randomMotivationMorning.name(), ((10l - hod) * 60l - (30l - moh)) * 60l * 1000l, + addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.randomMotivationMorning.name(), ((10l - hod) * 60l - (30l - moh)) * 60l * 1000l, EventDialogs.getMessage(EventDialogs.Type.randomMotivationMorning, input.language)); - addScheduledMessage(actionBuilder, EventDialogs.Type.haveLunch.name(), ((13l - hod) * 60l - moh) * 60l * 1000l, + addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.haveLunch.name(), ((13l - hod) * 60l - moh) * 60l * 1000l, EventDialogs.getMessage(EventDialogs.Type.haveLunch, input.language)); - addScheduledMessage(actionBuilder, EventDialogs.Type.makeCoffebreak.name(), ((16l - hod) * 60l - moh) * 60l * 1000l, + addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.makeCoffebreak.name(), ((16l - hod) * 60l - moh) * 60l * 1000l, EventDialogs.getMessage(EventDialogs.Type.makeCoffebreak, input.language)); - addScheduledMessage(actionBuilder, EventDialogs.Type.beActive.name(), ((18l - hod) * 60l - moh) * 60l * 1000l, + addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.beActive.name(), ((18l - hod) * 60l - moh) * 60l * 1000l, EventDialogs.getMessage(EventDialogs.Type.beActive, input.language)); } @@ -277,9 +277,9 @@ private static void addCommandButton(ServiceBuilder actionBuilder, String title, )); } /** - * Add a scheduled message + * Action: add a scheduled message to entertain the user. */ - private static void addScheduledMessage(ServiceBuilder actionBuilder, String eventId, long triggerDelayMS, String message){ + public static void addScheduledEntertainMessage(ServiceBuilder actionBuilder, String eventId, long triggerDelayMS, String message){ actionBuilder.addAction(ACTIONS.SCHEDULE_MSG); actionBuilder.putActionInfo("info", "entertainWhileIdle"); //TODO: one could distinguish messages that are only triggered when the app is not in foreground vs. important notes etc ... actionBuilder.putActionInfo("eventId", eventId); @@ -287,5 +287,16 @@ private static void addScheduledMessage(ServiceBuilder actionBuilder, String eve actionBuilder.putActionInfo("text", message); //actionBuilder.actionInfo_put_info("options", "inputHidden"); } + /** + * Action: add a scheduled message to pro-actively approach the user. + */ + public static void addScheduledProActiveMessage(ServiceBuilder actionBuilder, String eventId, long triggerDelayMS, String message){ + actionBuilder.addAction(ACTIONS.SCHEDULE_MSG); + actionBuilder.putActionInfo("info", "proActiveNote"); //TODO: one could distinguish messages that are only triggered when the app is not in foreground vs. important notes etc ... + actionBuilder.putActionInfo("eventId", eventId); + actionBuilder.putActionInfo("triggerIn", triggerDelayMS); + actionBuilder.putActionInfo("text", message); + //actionBuilder.actionInfo_put_info("options", "inputHidden"); + } } diff --git a/src/main/java/net/b07z/sepia/server/assist/server/Config.java b/src/main/java/net/b07z/sepia/server/assist/server/Config.java index 5fc9d237..ed126176 100644 --- a/src/main/java/net/b07z/sepia/server/assist/server/Config.java +++ b/src/main/java/net/b07z/sepia/server/assist/server/Config.java @@ -52,7 +52,7 @@ */ public class Config { public static final String SERVERNAME = "SEPIA-Assist-API"; //public server name - public static final String apiVersion = "v2.2.1"; //API version + public static final String apiVersion = "v2.2.2"; //API version public static String privacyPolicyLink = ""; //Link to privacy policy //helper for dynamic class creation (e.g. from strings in config-file) - TODO: reduce dependencies further From c2c9b7b772076dafb8fe784fe05e9abdf2b21c42 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Sun, 14 Apr 2019 22:34:40 +0200 Subject: [PATCH 08/27] added deviceId to NluInput; added new platform controls service; clean-ups --- .../assist/endpoints/AssistEndpoint.java | 3 + .../server/assist/interpreters/NluInput.java | 1 + .../interviews/InterviewServicesMap.java | 5 + .../assist/parameters/ClientFunction.java | 3 +- .../assist/parameters/ParameterConfig.java | 71 ++++---- .../assist/services/ClientControls.java | 16 ++ .../assist/services/PlatformControls.java | 171 ++++++++++++++++++ 7 files changed, 236 insertions(+), 34 deletions(-) create mode 100644 src/main/java/net/b07z/sepia/server/assist/services/PlatformControls.java diff --git a/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java b/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java index c2ab92a5..861b3a0a 100644 --- a/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java +++ b/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java @@ -46,6 +46,7 @@ public static enum InputParameters { context, mood, env, + device_id, //TODO: add something like "is_home_network" ? time, time_local, @@ -265,6 +266,7 @@ public static NluInput getInput(RequestParameters params){ String mood_str = params.getString(InputParameters.mood.name()); int mood = -1; String env = params.getString(InputParameters.env.name()); + String deviceId = params.getString(InputParameters.device_id.name()); String time_str = params.getString(InputParameters.time.name()); //system time - time stamp String time_local = params.getString(InputParameters.time_local.name()); //local time date String client_info = params.getString(InputParameters.client.name()); @@ -293,6 +295,7 @@ public static NluInput getInput(RequestParameters params){ if (msg_id!=null) input.msgId = msg_id; if (duplex_data!=null) input.duplexData = duplex_data; if (env!=null) input.environment = env; else input.environment = "all"; //input.client_info.replaceFirst("_v\\d.*", "").trim(); + if (deviceId!=null) input.deviceId = deviceId; if (user_location!=null) input.userLocation = user_location; if (time_local!=null) input.userTimeLocal = time_local; // diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java index ac74c33e..62017772 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java @@ -48,6 +48,7 @@ public class NluInput { public String userTimeLocal = ""; //time_local: system date and time at locally at user location, default format 2016.12.31_22:44:11 public User user; //user: holds all info about the user, can reload from account //... more to come + public String deviceId = ""; //device_id: an ID defined by the user to identify a certain device public String msgId = null; //msg_id: an ID to identify request, especially helpful in duplex scenarios public String duplexData = null; //duplex_data: data helpful to trace back a duplex call and answer or follow-up. Format is JSON, parse when required public String connection = "http"; //connection: http request or WebSocket connection - has influence on delayed replies diff --git a/src/main/java/net/b07z/sepia/server/assist/interviews/InterviewServicesMap.java b/src/main/java/net/b07z/sepia/server/assist/interviews/InterviewServicesMap.java index 388c04fb..11f00e35 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interviews/InterviewServicesMap.java +++ b/src/main/java/net/b07z/sepia/server/assist/interviews/InterviewServicesMap.java @@ -21,6 +21,7 @@ import net.b07z.sepia.server.assist.services.MeshNodeConnector; import net.b07z.sepia.server.assist.services.MusicRadioMixed; import net.b07z.sepia.server.assist.services.NewsRssFeeds; +import net.b07z.sepia.server.assist.services.PlatformControls; import net.b07z.sepia.server.assist.services.SentenceConnect; import net.b07z.sepia.server.assist.services.ServiceInfo; import net.b07z.sepia.server.assist.services.ServiceInterface; @@ -228,6 +229,10 @@ public static void load(){ ArrayList client_controls = new ArrayList(); client_controls.add(ClientControls.class.getCanonicalName()); systemInterviewServicesMap.put(CMD.CLIENT_CONTROLS, client_controls); + //PLATFORM CONTROLS (of CLIENT) + ArrayList platform_controls = new ArrayList(); + platform_controls.add(PlatformControls.class.getCanonicalName()); + systemInterviewServicesMap.put(CMD.PLATFORM_CONTROLS, platform_controls); //PARROT (REPEAT USER) ArrayList parrot = new ArrayList(); parrot.add(RepeatMe.class.getCanonicalName()); diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java b/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java index 120dfe7f..c37d696d 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java @@ -29,7 +29,8 @@ public static enum Type { volume, alwaysOn, meshNode, - clexi + clexi, + platformFunction //this is handled by PlatformControls service (we use it just for the action) } //-------data------- diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java b/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java index 3a3ff2c5..64ee98c5 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java @@ -4,7 +4,6 @@ import java.util.Map.Entry; import net.b07z.sepia.server.assist.data.Parameter; -import net.b07z.sepia.server.assist.server.Config; import net.b07z.sepia.server.core.assistant.PARAMETERS; import net.b07z.sepia.server.core.tools.Debugger; @@ -42,45 +41,51 @@ public static boolean test(){ */ public static void setup(){ //NOTE: If you ever rename any of the handlers this breaks! (use the test method!) - handlerToParameter.put(PARAMETERS.YES_NO, Config.parentPackage + ".parameters.YesNo"); - handlerToParameter.put(PARAMETERS.CONFIRMATION, Config.parentPackage + ".parameters.Confirm"); + handlerToParameter.put(PARAMETERS.YES_NO, YesNo.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.CONFIRMATION, Confirm.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.NUMBER, net.b07z.sepia.server.assist.parameters.Number.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.ACTION, Action.class.getCanonicalName()); - handlerToParameter.put(PARAMETERS.ALARM_NAME, Config.parentPackage + ".parameters.AlarmName"); - handlerToParameter.put(PARAMETERS.LOCATION_START, Config.parentPackage + ".parameters.LocationStart"); - handlerToParameter.put(PARAMETERS.LOCATION_END, Config.parentPackage + ".parameters.LocationEnd"); - handlerToParameter.put(PARAMETERS.LOCATION_WAYPOINT, Config.parentPackage + ".parameters.LocationWaypoint"); - handlerToParameter.put(PARAMETERS.LIST_ITEM, Config.parentPackage + ".parameters.ListItem"); - handlerToParameter.put(PARAMETERS.LIST_TYPE, Config.parentPackage + ".parameters.ListType"); - handlerToParameter.put(PARAMETERS.LIST_SUBTYPE, Config.parentPackage + ".parameters.ListSubType"); - handlerToParameter.put(PARAMETERS.TRAVEL_TYPE, Config.parentPackage + ".parameters.TravelType"); - handlerToParameter.put(PARAMETERS.TRAVEL_REQUEST_INFO, Config.parentPackage + ".parameters.TravelRequestInfo"); - handlerToParameter.put(PARAMETERS.PLACE, Config.parentPackage + ".parameters.Place"); - handlerToParameter.put(PARAMETERS.TIME, Config.parentPackage + ".parameters.DateAndTime"); - handlerToParameter.put(PARAMETERS.CLOCK, Config.parentPackage + ".parameters.DateClock"); - handlerToParameter.put(PARAMETERS.ALARM_TYPE, Config.parentPackage + ".parameters.AlarmType"); - handlerToParameter.put(PARAMETERS.FASHION_ITEM, Config.parentPackage + ".parameters.FashionItem"); - handlerToParameter.put(PARAMETERS.FASHION_SIZE, Config.parentPackage + ".parameters.FashionSize"); - handlerToParameter.put(PARAMETERS.FASHION_BRAND, Config.parentPackage + ".parameters.FashionBrand"); - handlerToParameter.put(PARAMETERS.COLOR, Config.parentPackage + ".parameters.Color"); - handlerToParameter.put(PARAMETERS.GENDER, Config.parentPackage + ".parameters.Gender"); - handlerToParameter.put(PARAMETERS.NEWS_SECTION, Config.parentPackage + ".parameters.NewsSection"); - handlerToParameter.put(PARAMETERS.NEWS_TYPE, Config.parentPackage + ".parameters.NewsType"); - handlerToParameter.put(PARAMETERS.SPORTS_TEAM, Config.parentPackage + ".parameters.SportsTeam"); - handlerToParameter.put(PARAMETERS.SPORTS_LEAGUE, Config.parentPackage + ".parameters.SportsLeague"); - handlerToParameter.put(PARAMETERS.FOOD_ITEM, Config.parentPackage + ".parameters.FoodItem"); - handlerToParameter.put(PARAMETERS.FOOD_CLASS, Config.parentPackage + ".parameters.FoodClass"); - handlerToParameter.put(PARAMETERS.RADIO_STATION, Config.parentPackage + ".parameters.RadioStation"); - handlerToParameter.put(PARAMETERS.MUSIC_GENRE, Config.parentPackage + ".parameters.MusicGenre"); + handlerToParameter.put(PARAMETERS.PLACE, Place.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.TIME, DateAndTime.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.CLOCK, DateClock.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.ALARM_NAME, AlarmName.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.ALARM_TYPE, AlarmType.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.LOCATION_START, LocationStart.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.LOCATION_END, LocationEnd.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.LOCATION_WAYPOINT, LocationWaypoint.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.LIST_ITEM, ListItem.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.LIST_TYPE, ListType.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.LIST_SUBTYPE, ListSubType.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.TRAVEL_TYPE, TravelType.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.TRAVEL_REQUEST_INFO, TravelRequestInfo.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.FASHION_ITEM, FashionItem.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.FASHION_SIZE, FashionSize.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.FASHION_BRAND, FashionBrand.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.COLOR, Color.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.GENDER, Gender.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.NEWS_SECTION, NewsSection.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.NEWS_TYPE, NewsType.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.SPORTS_TEAM, SportsTeam.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.SPORTS_LEAGUE, SportsLeague.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.FOOD_ITEM, FoodItem.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.FOOD_CLASS, FoodClass.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.RADIO_STATION, RadioStation.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.MUSIC_GENRE, MusicGenre.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.SMART_DEVICE, SmartDevice.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.SMART_DEVICE_VALUE, SmartDeviceValue.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.ROOM, Room.class.getCanonicalName()); - handlerToParameter.put(PARAMETERS.WEBSEARCH_REQUEST, Config.parentPackage + ".parameters.WebSearchRequest"); - handlerToParameter.put(PARAMETERS.WEBSEARCH_ENGINE, Config.parentPackage + ".parameters.WebSearchEngine"); - handlerToParameter.put(PARAMETERS.SEARCH_SECTION, Config.parentPackage + ".parameters.SearchSection"); + handlerToParameter.put(PARAMETERS.WEBSEARCH_REQUEST, WebSearchRequest.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.WEBSEARCH_ENGINE, WebSearchEngine.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.SEARCH_SECTION, SearchSection.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.CLIENT_FUN, ClientFunction.class.getCanonicalName()); + //more client/platform helpers (simple generic parameters) + handlerToParameter.put(PARAMETERS.ANDROID_FUN, GenericEmptyParameter.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.IOS_FUN, GenericEmptyParameter.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.BROWSER_FUN, GenericEmptyParameter.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.WINDOWS_FUN, GenericEmptyParameter.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.DEVICE_FUN, GenericEmptyParameter.class.getCanonicalName()); //Generics / Exceptions / Specials - handlerToParameter.put(PARAMETERS.SENTENCES, Config.parentPackage + ".parameters.Sentences"); + handlerToParameter.put(PARAMETERS.SENTENCES, Sentences.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.DATA, GenericEmptyParameter.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.REPLY, GenericParameter.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.REPLY_SUCCESS, GenericParameter.class.getCanonicalName()); diff --git a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java index 5f345606..ad1cf976 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java @@ -162,6 +162,22 @@ public ServiceResult getResult(NluResult nluResult) { //TODO: implement, ask or fail? } }else if (isVolume){ + //check data for volume + if (num.isEmpty() && !Is.nullOrEmpty(data)){ + if (data.startsWith("{")){ + //JSON with number + Object numO = JSON.parseString(data).get("number"); + if (numO != null){ + long numL = Converters.obj2LongOrDefault(numO, -1l); + if (numL > -1){ + num = String.valueOf(numL); + } + } + }else if (data.matches("\\d+")){ + //number as string + num = data; + } + } //volume support if (!num.isEmpty() && (isActionEdit || isActionIncrease || isActionDecrease)){ long vol = Converters.obj2LongOrDefault(num, -1l); diff --git a/src/main/java/net/b07z/sepia/server/assist/services/PlatformControls.java b/src/main/java/net/b07z/sepia/server/assist/services/PlatformControls.java new file mode 100644 index 00000000..30445e60 --- /dev/null +++ b/src/main/java/net/b07z/sepia/server/assist/services/PlatformControls.java @@ -0,0 +1,171 @@ +package net.b07z.sepia.server.assist.services; + +import java.util.TreeSet; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.data.Parameter; +import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.parameters.ClientFunction; +import net.b07z.sepia.server.assist.services.ServiceBuilder; +import net.b07z.sepia.server.assist.services.ServiceInfo; +import net.b07z.sepia.server.assist.services.ServiceInterface; +import net.b07z.sepia.server.assist.services.ServiceResult; +import net.b07z.sepia.server.assist.services.ServiceInfo.Content; +import net.b07z.sepia.server.assist.services.ServiceInfo.Type; +import net.b07z.sepia.server.core.assistant.ACTIONS; +import net.b07z.sepia.server.core.assistant.CLIENTS; +import net.b07z.sepia.server.core.assistant.CLIENTS.Platform; +import net.b07z.sepia.server.core.assistant.CMD; +import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.data.Language; +import net.b07z.sepia.server.core.tools.Is; +import net.b07z.sepia.server.core.tools.JSON; + +/** + * A service to trigger client control actions, usually called via direct commands + * defined in the teach-UI or by pre-defined system/SDK sentences. + * Compared to {@link ClientControls} this class supports different actions for each + * client platform so that you can use e.g. the same command to either call and Android Intent + * or a browser link. Example: 'open camera'. + * + * @author Florian Quirin + * + */ +public class PlatformControls implements ServiceInterface{ + + public static enum FunctionTypes { + androidIntent, + iosIntent, + windowsIntent, + browserIntent, + url + } + + //Define some sentences for testing: + + @Override + public TreeSet getSampleSentences(String lang){ + TreeSet samples = new TreeSet<>(); + //GERMAN + if (lang.equals(Language.DE.toValue())){ + samples.add("Kamera öffnen."); + + //OTHER + }else{ + samples.add("Open camera."); + } + return samples; + } + + //Basic service setup: + + @Override + public ServiceInfo getInfo(String language) { + ServiceInfo info = new ServiceInfo(Type.plain, Content.data, false); + + //Command + String CMD_NAME = CMD.PLATFORM_CONTROLS; //parameters: android_fun, ios_fun, browser_fun, device_fun + info.setIntendedCommand(CMD_NAME); + + //NOTE: check ClientControls for examples if you want to add some pre-defined triggers + + //Parameters: + + //all optional (for now, because we trigger this with custom functions) + Parameter p1 = new Parameter(PARAMETERS.ANDROID_FUN); + Parameter p2 = new Parameter(PARAMETERS.IOS_FUN); + Parameter p3 = new Parameter(PARAMETERS.BROWSER_FUN); + Parameter p4 = new Parameter(PARAMETERS.DEVICE_FUN); + Parameter p5 = new Parameter(PARAMETERS.WINDOWS_FUN); + + info.addParameter(p1).addParameter(p2).addParameter(p3).addParameter(p4).addParameter(p5); + + //Default answers + info.addSuccessAnswer("ok_0b") + .addFailAnswer("error_0a") + .addOkayAnswer("default_not_possible_0a"); + + return info; + } + + @Override + public ServiceResult getResult(NluResult nluResult) { + //initialize result + ServiceBuilder api = new ServiceBuilder(nluResult, + getInfoFreshOrCache(nluResult.input, this.getClass().getCanonicalName())); + + //platform data + Platform platform = CLIENTS.getPlatform(nluResult.input.clientInfo); + String userDeviceId = nluResult.input.deviceId; + + //get parameters + Parameter androidFunP = nluResult.getOptionalParameter(PARAMETERS.ANDROID_FUN, ""); + String androidFun = androidFunP.getValueAsString(); + + Parameter iosFunP = nluResult.getOptionalParameter(PARAMETERS.IOS_FUN, ""); + String iosFun = iosFunP.getValueAsString(); + + Parameter browserFunP = nluResult.getOptionalParameter(PARAMETERS.BROWSER_FUN, ""); + String browserFun = browserFunP.getValueAsString(); + + Parameter windowsFunP = nluResult.getOptionalParameter(PARAMETERS.WINDOWS_FUN, ""); + String windowsFun = windowsFunP.getValueAsString(); + + Parameter deviceFunP = nluResult.getOptionalParameter(PARAMETERS.DEVICE_FUN, ""); + String deviceFun = deviceFunP.getValueAsString(); + JSONObject deviceFunJson = null; + if (Is.notNullOrEmpty(userDeviceId) && !deviceFun.isEmpty()){ + deviceFunJson = JSON.parseString(deviceFun); + } + + //Find best match for function type + String foundFunString = null; + if (Is.notNullOrEmpty(deviceFunJson)){ + foundFunString = JSON.getString(deviceFunJson, userDeviceId); + } + if (Is.nullOrEmpty(foundFunString)){ + if (platform.equals(Platform.browser)){ + foundFunString = browserFun; + }else if (platform.equals(Platform.android)){ + foundFunString = androidFun; + }else if (platform.equals(Platform.ios)){ + foundFunString = iosFun; + }else if (platform.equals(Platform.windows)){ + foundFunString = windowsFun; + } + } + /* DEBUG + System.out.println("DeviceId: " + userDeviceId); + System.out.println("Platform: " + platform); + System.out.println("Fun: " + foundFunString); + */ + + //This service fails when no device function or platform is found that fits + if (Is.nullOrEmpty(foundFunString)){ + api.setStatusOkay(); + return api.buildResult(); + + }else{ + //Tell client to perform this platform action); + String controlFun = ClientFunction.Type.platformFunction.name(); + JSONObject controlData = JSON.make( + "platform", platform.toString(), + "data", foundFunString + ); + api.addAction(ACTIONS.CLIENT_CONTROL_FUN); + api.putActionInfo("fun", controlFun); + api.putActionInfo("controlData", controlData); + + //action button + api.addAction(ACTIONS.BUTTON_CUSTOM_FUN); + api.putActionInfo("fun", "controlFun;;" + controlFun + ";;" + controlData.toJSONString()); + api.putActionInfo("title", "Button"); + + //build the API_Result - cannot fail anymore at this point + api.setStatusSuccess(); + ServiceResult result = api.buildResult(); + return result; + } + } +} From 6167f0d380e14087623fac7008fa9bfad7c5c09b Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Fri, 19 Apr 2019 09:05:25 +0200 Subject: [PATCH 09/27] improved RSS backup restore; updated news outlets --- Xtensions/ServiceProperties/news-outlets.json | 4 +-- .../server/assist/tools/RssFeedReader.java | 29 ++++++++++++++----- .../server/assist/workers/RssFeedWorker.java | 13 +++++++-- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Xtensions/ServiceProperties/news-outlets.json b/Xtensions/ServiceProperties/news-outlets.json index a47f158d..58974dac 100644 --- a/Xtensions/ServiceProperties/news-outlets.json +++ b/Xtensions/ServiceProperties/news-outlets.json @@ -1,6 +1,6 @@ { "outlets":[ - { "name": "SPIEGEL ONLINE", "url": "http://www.spiegel.de/schlagzeilen/tops/index.rss", "name_html": "SPIEGEL ONLINE" }, + { "name": "SPIEGEL ONLINE", "url": "https://www.spiegel.de/schlagzeilen/tops/index.rss", "name_html": "SPIEGEL ONLINE" }, { "name": "bento", "url": "https://www.bento.de/rss/nachrichten/", "name_html": "bento" }, { "name": "Tagesschau", "url": "http://www.tagesschau.de/xml/rss2", "name_html": "tagesschau.de" }, { "name": "Süddeutsche", "url": "http://rss.sueddeutsche.de/rss/Topthemen", "name_html": "Süddeutsche Zeitung" }, @@ -27,7 +27,7 @@ { "name": "Filmstarts.de - aktuell", "url": "http://rss.filmstarts.de/fs/kinos/aktuelle?format=xml", "name_html": "Filmstarts.de - aktuell" }, { "name": "Filmstarts.de - bald", "url": "http://rss.filmstarts.de/fs/kinos/bald?format=xml", "name_html": "Filmstarts.de - bald" }, { "name": "Filmstarts.de - Serien", "url": "http://rss.filmstarts.de/fs/news/serien?format=xml", "name_html": "Filmstarts.de - Serien" }, - { "name": "SPIEGEL Kino", "url": "http://www.spiegel.de/kultur/kino/index.rss", "name_html": "SPIEGEL Kino" }, + { "name": "SPIEGEL Kino", "url": "https://www.spiegel.de/kultur/kino/index.rss", "name_html": "SPIEGEL Kino" }, { "name": "Kino.de - Film-News", "url": "https://www.kino.de/rss/movienews", "name_html": "Kino.de - Film-News" }, { "name": "deutsche startups", "url": "https://www.deutsche-startups.de/feed/", "name_html": "deutsche startups" } ], diff --git a/src/main/java/net/b07z/sepia/server/assist/tools/RssFeedReader.java b/src/main/java/net/b07z/sepia/server/assist/tools/RssFeedReader.java index 995f047e..96061892 100644 --- a/src/main/java/net/b07z/sepia/server/assist/tools/RssFeedReader.java +++ b/src/main/java/net/b07z/sepia/server/assist/tools/RssFeedReader.java @@ -1,8 +1,10 @@ package net.b07z.sepia.server.assist.tools; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -47,19 +49,32 @@ public RssFeedReader(){ feedCacheTS = new JSONObject(); } - public JSONObject getCache(){ - return feedCache; - } - public boolean loadBackup(){ + /** + * Load the backup and return the last-modified timestamp of it or -1 if there was none. + * @return timestamp or -1 + */ + public long loadBackup(){ + //check file + File file = new File(Workers.rssFeedsData_BackupFile); + if (!file.exists()){ + Debugger.println("RssFeedReader - no backup file found! This is ok if you start for the first time or cleaned the backup.", 1); + return -1l; + } JSONObject backup = JSON.readJsonFromFile(Workers.rssFeedsData_BackupFile); if (backup != null && !backup.isEmpty()){ + long lastMod = file.lastModified(); this.feedCache = backup; - Debugger.println("RssFeedReader - backup restored with " + feedCache.size() + " feeds.", 3); - return true; + Debugger.println("RssFeedReader - backup restored with " + feedCache.size() + " feeds. Last modified: " + (new SimpleDateFormat(Config.defaultSdf)).format(lastMod), 3); + return lastMod; }else{ - return false; + Debugger.println("RssFeedReader - backup was corrupted! Please check or remove the file at: " + Workers.rssFeedsData_BackupFile, 1); + return -1l; } } + + public JSONObject getCache(){ + return feedCache; + } public JSONObject getCacheTimestamps(){ return feedCacheTS; } diff --git a/src/main/java/net/b07z/sepia/server/assist/workers/RssFeedWorker.java b/src/main/java/net/b07z/sepia/server/assist/workers/RssFeedWorker.java index 3a1fd480..1cf437d4 100644 --- a/src/main/java/net/b07z/sepia/server/assist/workers/RssFeedWorker.java +++ b/src/main/java/net/b07z/sepia/server/assist/workers/RssFeedWorker.java @@ -38,6 +38,7 @@ public class RssFeedWorker implements WorkerInterface { Set refreshFeeds; long customWaitInterval = 2275; //custom wait time until the worker checks for an abort request and status changes public static long customRefreshInterval = (3*60*60*1000); //every 3h + public static long oldBackupStartDelay = (15*60*1000); //wait 15min if the backup was old //even more specific for service int maxHeadlinesPerFeed = 8; @@ -126,10 +127,16 @@ public void waitForWorker(){ @Override public void start(){ //load backup - if (Config.rssReader.loadBackup()){ + long lastModified = Config.rssReader.loadBackup(); + if (lastModified > 0){ workerStatus = 0; - //start with refresh delay - start(customRefreshInterval); + if ((lastModified + customRefreshInterval) < System.currentTimeMillis()){ + //too old, start soon + start(oldBackupStartDelay); + }else{ + //start with normal refresh delay + start(customRefreshInterval); + } }else{ workerStatus = 0; //start with normal delay From 440b87867d5d2ae96c1fed8f4e6a54fd43eaafd4 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Fri, 19 Apr 2019 09:54:21 +0200 Subject: [PATCH 10/27] fixed a bug in date parsing --- .../server/assist/interpreters/RegexParameterSearch.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java index 9e881d7c..094f460c 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java @@ -1481,7 +1481,7 @@ public static String[] get_month(String input, String language){ } /** * Replace all month names in a string with their corresponding numbers (January=01.). - * If the month is followed or preceded by a day like "first of January" or "January the first" it is converted to "01.01." + * If the month is followed or preceded by a day like "first of February" or "February the first" it is converted to "01.02." * @param input - string to search * @param language - language code * @return string with names replaced by numbers @@ -1544,6 +1544,10 @@ public static String replace_all_months_by_numbers(String input, String language input = input.replaceAll("\\b(\\d+)th(\\.)", "$1."); //...continue? } + + //check for a year at the end + input = input.replaceAll("\\b(\\d{1,2}\\.)(\\d{1,2}\\.)\\s(\\d{4})\\b", "$1$2$3"); + return input; } From 0867c864894356173a0dd0bdc1c05217c0bce2b3 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Mon, 22 Apr 2019 01:39:26 +0200 Subject: [PATCH 11/27] added Unix time recognizer to DateAndTime; fixed some timezone bugs --- .../server/assist/interpreters/NluInput.java | 8 +++--- .../interpreters/RegexParameterSearch.java | 8 ++++++ .../server/assist/parameters/DateAndTime.java | 16 ++++++++--- .../assist/tools/DateTimeConverters.java | 27 +++++++++++++++++-- .../server/assist/workers/OpenLigaWorker.java | 8 +++--- .../assist/parameters/Test_DateAndTime.java | 13 +++++++-- .../server/assist/services/Test_Alarms.java | 2 +- 7 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java index 62017772..36ff3d68 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java @@ -45,7 +45,7 @@ public class NluInput { //personal info public String userLocation = ""; //user_location: address, longitude, latitude coordinates of user public long userTime = -1; //time: system time at request sent - public String userTimeLocal = ""; //time_local: system date and time at locally at user location, default format 2016.12.31_22:44:11 + public String userTimeLocal = ""; //time_local: system date and time at user location, default format 2016.12.31_22:44:11 public User user; //user: holds all info about the user, can reload from account //... more to come public String deviceId = ""; //device_id: an ID defined by the user to identify a certain device @@ -102,11 +102,11 @@ public NluInput(String text, String language, String context, int mood, String e /** * Set user local time. - * @param timeString - Date in default format (Config.defaultSdf): "yyyy.MM.hh_HH:mm:ss" + * @param timeString - Date in default format (Config.defaultSdf): "yyyy.MM.dd_HH:mm:ss" */ - public void setTime(String timeString){ + public void setTimeGMT(String timeString){ this.userTimeLocal = timeString; - this.userTime = DateTimeConverters.getUnixTimeOfDate(userTimeLocal, Config.defaultSdf); + this.userTime = DateTimeConverters.getUnixTimeOfDateGMT(userTimeLocal, Config.defaultSdf); } //handle (session) STORAGEs: diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java index 094f460c..8a546ad3 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java @@ -1091,6 +1091,14 @@ public static HashMap get_date(String text, String language){ String date_tag =""; String time_tag =""; + //Check UNIX time first (we are more tolerant than required here ^^) + String unixTimeString = NluTools.stringFindFirst(text, "\\b\\d{12,14}\\b"); + if (!unixTimeString.isEmpty()){ + pv.put("date_tag", unixTimeString); + pv.put("time_tag", ""); + return pv; + } + //Common text = replace_all_months_by_numbers(text, language); diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/DateAndTime.java b/src/main/java/net/b07z/sepia/server/assist/parameters/DateAndTime.java index c11c6348..c2790ea3 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/DateAndTime.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/DateAndTime.java @@ -54,14 +54,14 @@ public class DateAndTime implements ParameterHandler{ public static final String TIME_TAGS_SHORT = "( |)(sec|sek|s|min|std|h|hr|d|wk|mo|j|a|yr|y)(\\.|)($|\\s)"; - public static final String TIME_UNSPECIFIC_TAGS_DE = "(am |)(am morgen|morgens|frueh|vormittag(s|)|mittag(s|)|nachmittag(s|)|abend(s|)|nacht(s|)|spaet)"; - public static final String TIME_UNSPECIFIC_TAGS_EN = "(in the |at the |at |by |)(morning|early|forenoon|midday|afternoon|noon|evening|night|late)"; - public static final String DAY_TAGS_DE = "(montag|dienstag|mittwoch|donnerstag|freitag|samstag|sonntag)"; - public static final String DAY_TAGS_RELATIVE_DE = "(heute|jetzt|uebermorgen|morgen|wochenende|naechste woche|die(se|) woche|naechsten tage)"; + public static final String DAY_TAGS_RELATIVE_DE = "(heute|jetzt|uebermorgen|(? dateEx, NluInput if (dateTag.isEmpty() && timeTag.isEmpty()){ return (new String[]{"", ""}); } + if (dateTag.matches("\\d{12,14}")){ + long eventUnixTime = Long.parseLong(dateTag); + //calculate difference to user time + long secondsDiffToUser = (eventUnixTime - nluInput.userTime)/1000l; + //TODO: this fails when the event is crossing summer/winter-time switch + String[] eventDateRelativeToUser = DateTimeConverters.getTodayPlusX_seconds("yyyy.MM.dd_HH:mm:ss", nluInput, secondsDiffToUser).split("_"); + return (new String[]{eventDateRelativeToUser[0], eventDateRelativeToUser[1]}); + } if (dateTag.matches("\\d\\d\\d\\d\\.\\d\\d\\.\\d\\d_\\d\\d:\\d\\d:\\d\\d")){ String[] dateTimeRes = dateTag.split("_"); String dateRes = dateTimeRes[0]; diff --git a/src/main/java/net/b07z/sepia/server/assist/tools/DateTimeConverters.java b/src/main/java/net/b07z/sepia/server/assist/tools/DateTimeConverters.java index 52ed8c0f..12aba25b 100644 --- a/src/main/java/net/b07z/sepia/server/assist/tools/DateTimeConverters.java +++ b/src/main/java/net/b07z/sepia/server/assist/tools/DateTimeConverters.java @@ -2,9 +2,11 @@ import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.ZoneId; import java.util.Calendar; import java.util.Date; import java.util.HashMap; +import java.util.TimeZone; import java.util.concurrent.TimeUnit; import net.b07z.sepia.server.assist.answers.AnswerStatics; @@ -365,14 +367,35 @@ public static String getDateForDayOfWeek(int day, String format, NluInput nluInp } /** - * Get the UNIX time (ms) of the given date in the given format. + * Get the UNIX time (ms) of the given GMT date in the given format. * @param dateString - any string containing a date that can be parsed * @param format - format to parse * @return UNIX time or Long.MIN_VALUE */ - public static long getUnixTimeOfDate(String dateString, String format){ + public static long getUnixTimeOfDateGMT(String dateString, String format){ //parse input SimpleDateFormat sdf = new SimpleDateFormat(format); + sdf.setTimeZone(TimeZone.getTimeZone("GMT")); + Date date; + try { + date = sdf.parse(dateString); + return date.getTime(); + + } catch (ParseException e) { + return Long.MIN_VALUE; + } + } + /** + * Get the UNIX time (ms) of the given date in the given format and timezone. + * @param dateString - any string containing a date that can be parsed + * @param format - format to parse + * @param timezone - {@link ZoneId} of TimeZone, either an abbreviation such as "PST", a full name such as "America/Los_Angeles", or a custom ID such as "GMT-8:00". + * @return UNIX time or Long.MIN_VALUE + */ + public static long getUnixTimeOfDateAtZone(String dateString, String format, String timezone){ + //parse input + SimpleDateFormat sdf = new SimpleDateFormat(format); + sdf.setTimeZone(TimeZone.getTimeZone(ZoneId.of(timezone))); Date date; try { date = sdf.parse(dateString); diff --git a/src/main/java/net/b07z/sepia/server/assist/workers/OpenLigaWorker.java b/src/main/java/net/b07z/sepia/server/assist/workers/OpenLigaWorker.java index 042a93d0..1d12f682 100644 --- a/src/main/java/net/b07z/sepia/server/assist/workers/OpenLigaWorker.java +++ b/src/main/java/net/b07z/sepia/server/assist/workers/OpenLigaWorker.java @@ -450,9 +450,9 @@ public static JSONArray getMatchDayData(String leagueTag, String matchDay){ for (Object o : apiResult){ JSONObject matchInput = (JSONObject) o; JSONObject matchOut = new JSONObject(); - String date = (String) matchInput.get("MatchDateTime"); //2016-12-02T20:30:00 + String date = (String) matchInput.get("MatchDateTime"); //2016-12-02T20:30:00 - TODO: this HAS TO BE GMT! Is it? JSON.add(matchOut, "kickoff", date); - long dateUNIX = DateTimeConverters.getUnixTimeOfDate(date.replaceFirst("T", "_"), "yyyy-MM-dd_HH:mm:ss"); + long dateUNIX = DateTimeConverters.getUnixTimeOfDateGMT(date.replaceFirst("T", "_"), "yyyy-MM-dd_HH:mm:ss"); if ((dateUNIX > System.currentTimeMillis()) || (System.currentTimeMillis() - dateUNIX) < (1000*60*30*5)){ if (dateUNIX < activeOrNextMatchUnixTime){ activeOrNextMatchUnixTime = dateUNIX; @@ -549,9 +549,9 @@ public static JSONArray getNextMatchDayData(String leagueTag, String matchDay){ for (Object o : apiResult){ JSONObject matchInput = (JSONObject) o; JSONObject matchOut = new JSONObject(); - String date = (String) matchInput.get("MatchDateTime"); //2016-12-02T20:30:00 + String date = (String) matchInput.get("MatchDateTime"); //2016-12-02T20:30:00 - TODO: this HAS TO BE GMT! Is it? JSON.add(matchOut, "kickoff", date); - long dateUNIX = DateTimeConverters.getUnixTimeOfDate(date.replaceFirst("T", "_"), "yyyy-MM-dd_HH:mm:ss"); + long dateUNIX = DateTimeConverters.getUnixTimeOfDateGMT(date.replaceFirst("T", "_"), "yyyy-MM-dd_HH:mm:ss"); if (dateUNIX > System.currentTimeMillis()){ nextMatchUnixTime = Math.min(nextMatchUnixTime, dateUNIX); } diff --git a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_DateAndTime.java b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_DateAndTime.java index 79b093e8..282be32e 100644 --- a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_DateAndTime.java +++ b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_DateAndTime.java @@ -10,22 +10,25 @@ import net.b07z.sepia.server.assist.server.ConfigTestServer; import net.b07z.sepia.server.assist.tools.DateTimeConverters; import net.b07z.sepia.server.assist.users.User; +import net.b07z.sepia.server.core.tools.Debugger; import net.b07z.sepia.server.core.tools.JSON; public class Test_DateAndTime { public static void main(String[] args) { + long tic = Debugger.tic(); //fake input NluInput input = ConfigTestServer.getFakeInput("test", "de"); input.userTimeLocal = "2018.01.14_10:00:00"; - input.userTime = DateTimeConverters.getUnixTimeOfDate(input.userTimeLocal, "yyyy.MM.hh_HH:mm:ss"); + input.userTime = DateTimeConverters.getUnixTimeOfDateAtZone(input.userTimeLocal, "yyyy.MM.dd_HH:mm:ss", "GMT"); User user = ConfigTestServer.getTestUser(ConfigTestServer.email_id1, input, false, true); input.userLocation = JSON.make("country", "Germany", "city", "Essen", "street", "Somestreet 1", "latitude", 51.6, "longitude", 7.1).toString(); input.user = user; //time - System.out.println("chosen UNIX time (s): " + Math.round(input.userTime/1000)); + System.out.println("chosen UNIX time (ms): " + input.userTime); + System.out.println("chosen UNIX time (s): " + input.userTime/1000l); System.out.println("now: " + DateTimeConverters.getSpeakableDateSpecial(input.userTimeLocal, 5l, Config.defaultSdf, input) + ", " + input.userTimeLocal + "\n"); @@ -73,11 +76,13 @@ public static void main(String[] args) { testTimeString("set an alarm for tomorrow 6pm", "[2018.01.15, 18:00:00]", input); testTimeString("set an alarm for tomorrow 6 p.m.", "[2018.01.15, 18:00:00]", input); testTimeString("set an alarm for 8 a.m.", "[2018.01.14, 08:00:00]", input); + testTimeString("set an alarm for 1518697800000", "[2018.02.15, 12:30:00]", input); input.language = "de"; System.out.println("\n----- de -----"); testTimeString("alarm stellen fuer morgen abend 8:30uhr", "[2018.01.15, 20:30:00]", input); testTimeString("alarm stellen fuer mittwoch 8:30 uhr abends", "[2018.01.17, 20:30:00]", input); + testTimeString("alarm stellen fuer mittwoch morgen 8:30 uhr", "[2018.01.17, 08:30:00]", input); testTimeString("alarm stellen fuer 8:30 uhr abends am donnerstag", "[2018.01.18, 20:30:00]", input); testTimeString("alarm stellen fuer 8:30 uhr abends in 2 tagen", "[2018.01.16, 20:30:00]", input); testTimeString("alarm stellen fuer 8:30 in 3 tagen", "[2018.01.17, 08:30:00]", input); @@ -102,6 +107,10 @@ public static void main(String[] args) { testTimeString("erinnere mich in einer woche um 13 uhr an reifen", "[2018.01.21, 13:00:00]", input); testTimeString("erinnere mich samstag um 10 Uhr an das fest", "[2018.01.20, 10:00:00]", input); testTimeString("erinnere mich naechste woche samstag um 13 uhr an zeug", "[2018.01.20, 13:00:00]", input); + testTimeString("alarm stellen fuer 1518697800000", "[2018.02.15, 12:30:00]", input); + testTimeString("alarm stellen fuer 1555882260000", "[2019.04.21, 21:31:00]", input); + + System.out.println("Took: " + Debugger.toc(tic) + "ms"); } public static boolean testTimeString(String text, String shouldBe, NluInput input){ diff --git a/src/test/java/net/b07z/sepia/server/assist/services/Test_Alarms.java b/src/test/java/net/b07z/sepia/server/assist/services/Test_Alarms.java index 1bb57713..a8042b9d 100644 --- a/src/test/java/net/b07z/sepia/server/assist/services/Test_Alarms.java +++ b/src/test/java/net/b07z/sepia/server/assist/services/Test_Alarms.java @@ -31,7 +31,7 @@ public static void main(String[] args) { //set some values explicitly input.user.userName = new Name("Mister", "Tester", "Testy"); - input.setTime("2019.01.14_13:00:00"); + input.setTimeGMT("2019.01.14_13:00:00"); //Prevent database access for tests Alarms.testMode = true; From 1f2885d5587f35c01a86275a71a587182c5261ba Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Tue, 23 Apr 2019 01:07:21 +0200 Subject: [PATCH 12/27] added http(s) as slash-command; option to DateAndTime --- .../interpreters/NluKeywordAnalyzer.java | 41 +++++++++++++++---- .../interpreters/RegexParameterSearch.java | 6 +-- .../server/assist/parameters/DateAndTime.java | 6 +++ .../assist/services/OpenCustomLink.java | 13 ++++-- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzer.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzer.java index 93ccd831..4e8d143e 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzer.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzer.java @@ -13,6 +13,7 @@ import net.b07z.sepia.server.assist.parameters.AbstractParameterSearch; import net.b07z.sepia.server.assist.server.Config; import net.b07z.sepia.server.assist.server.ConfigServices; +import net.b07z.sepia.server.assist.services.OpenCustomLink; import net.b07z.sepia.server.assist.services.ServiceInfo; import net.b07z.sepia.server.assist.services.ServiceInterface; import net.b07z.sepia.server.core.assistant.CMD; @@ -218,19 +219,45 @@ public NluResult interpret(NluInput input) { //--------------------------- - //Repeat me - overwrites all other commands! - TODO: is this still valid or captured before? If it is make a function - if (NluTools.stringContains(text, "(^saythis|" + Pattern.quote(Config.assistantName) + " saythis)")){ - String this_text = input.textRaw.replaceFirst(".*?\\bsaythis|.*?\\bSaythis", "").trim(); - + //SLASH Commands and specials - will overwrite all other commands! - TODO: we should put all slash-command searches in one extra place + + //Repeat me + if (NluTools.stringContains(text, "(^saythis|^\\\\saythis|" + Pattern.quote(Config.assistantName) + " saythis)")){ + String thisText = input.textRaw.replaceFirst(".*?\\bsaythis|.*?\\bSaythis", "").trim(); //make it THE command possibleCMDs.add(CMD.REPEAT_ME); possibleScore.add(1); index++; possibleScore.set(index, 1000); //definitely this! - Map pv = new HashMap(); - pv.put(PARAMETERS.REPEAT_THIS, this_text); + pv.put(PARAMETERS.REPEAT_THIS, thisText); + possibleParameters.add(pv); + + //Link share + }else if (NluTools.stringContains(text, "(^linkshare|^\\\\linkshare|" + Pattern.quote(Config.assistantName) + " linkshare)")){ + String link = input.textRaw.replaceFirst(".*?\\blinkshare|.*?\\bLinkshare", "").trim().replaceFirst("\\s.*", ""); + String title = link.substring(0, Math.min(link.length(), 22)); + if (link.length() > 21) title += "..."; + possibleCMDs.add(CMD.OPEN_LINK); + possibleScore.add(1); index++; + possibleScore.set(index, 1000); //definitely this! + Map pv = new HashMap(); + pv.put(PARAMETERS.URL, link); + pv.put(OpenCustomLink.TITLE, title); possibleParameters.add(pv); - } + + //Link open + }else if (NluTools.stringContains(text, "^http(s|)://.*")){ + String link = input.textRaw.trim().replaceFirst("\\s.*", ""); + String title = link.substring(0, Math.min(link.length(), 22)); + if (link.length() > 21) title += "..."; + possibleCMDs.add(CMD.OPEN_LINK); + possibleScore.add(1); index++; + possibleScore.set(index, 1000); //definitely this! + Map pv = new HashMap(); + pv.put(PARAMETERS.URL, link); + pv.put(OpenCustomLink.TITLE, title); + possibleParameters.add(pv); + } //--set certainty_lvl-- int bestScoreIndex = 0; diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java index 8a546ad3..362d23a2 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java @@ -27,13 +27,13 @@ public class RegexParameterSearch { /** - * Check if input contains a special slash command like "saythis". + * Check if input contains a special slash command like "saythis" and "linkshare" or a URL with "http(s)://. * @param input - user input * @return true/false */ public static boolean contains_slashCMD(String input){ - //TODO: improve all slash commands! - if (input.toLowerCase().matches("(" + Pattern.quote(Config.assistantName.toLowerCase()) + " |^)(saythis)\\b.*")){ + //TODO: improve all slash commands and introduce some static variables for them! + if (input.toLowerCase().matches("(" + Pattern.quote(Config.assistantName.toLowerCase()) + " |^|^\\\\)(saythis|linkshare|http(s|)://)\\b.*")){ return true; }else{ return false; diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/DateAndTime.java b/src/main/java/net/b07z/sepia/server/assist/parameters/DateAndTime.java index c2790ea3..42e93f72 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/DateAndTime.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/DateAndTime.java @@ -2,6 +2,7 @@ import java.util.Calendar; import java.util.HashMap; +import java.util.regex.Pattern; import org.json.simple.JSONObject; @@ -346,6 +347,11 @@ public String build(String input) { String dateType = DateType.unknown.name(); String day = ""; String time = ""; + + //extract again/first + if (input.startsWith("")){ + input = extract(input.replaceFirst(Pattern.quote(""), "")); + } //extracted time-date if (input.startsWith("<") && input.contains("&&")){ diff --git a/src/main/java/net/b07z/sepia/server/assist/services/OpenCustomLink.java b/src/main/java/net/b07z/sepia/server/assist/services/OpenCustomLink.java index bc6db8d5..0c3418f2 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/OpenCustomLink.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/OpenCustomLink.java @@ -15,6 +15,7 @@ import net.b07z.sepia.server.assist.interviews.AskClient; import net.b07z.sepia.server.assist.server.Config; import net.b07z.sepia.server.core.assistant.ACTIONS; +import net.b07z.sepia.server.core.assistant.PARAMETERS; import net.b07z.sepia.server.core.tools.Converters; import net.b07z.sepia.server.core.tools.JSON; @@ -30,6 +31,10 @@ */ public class OpenCustomLink { + public static final String TITLE = "title"; + public static final String DESCRIPTION = "description"; + public static final String ICON_URL = "icon_url"; + /** * The default method to create a service result handling all the question/answer/action construction. * @param NluResult - typically this is given by a direct command @@ -40,19 +45,19 @@ public static ServiceResult get(NluResult nluResult){ ServiceBuilder api = new ServiceBuilder(nluResult); //get parameters - String url = nluResult.getParameter("url"); //the url to call (can include wildcards in the form ***) + String url = nluResult.getParameter(PARAMETERS.URL); //the url to call (can include wildcards in the form ***) String parameter_set = nluResult.getParameter("parameter_set"); //the parameters filling the wildcards connected by "&&" String question_set = nluResult.getParameter("question_set"); //a set of questions to the wildcards connected by "&&" String answer_set = nluResult.getParameter("answer_set"); //a set of answers to complete the command, if there is more than one (separated by "||") a random one will be chosen - String title = nluResult.getParameter("title"); //title of link-card + String title = nluResult.getParameter(TITLE); //title of link-card if (title.isEmpty()) title = "Link"; - String description = nluResult.getParameter("description"); //description of link-card + String description = nluResult.getParameter(DESCRIPTION); //description of link-card if (description.isEmpty()) description = ActionBuilder.getDefaultButtonText(api.language); - String iconUrl = nluResult.getParameter("icon_url"); //icon URL to be used for link-card + String iconUrl = nluResult.getParameter(ICON_URL); //icon URL to be used for link-card boolean isCustomIcon = !iconUrl.isEmpty(); if (!isCustomIcon) iconUrl = Config.urlWebImages + "/cards/link.png"; From c459d576eb6b49c9a548463f9820cab3d698da51 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Sat, 27 Apr 2019 00:08:29 +0200 Subject: [PATCH 13/27] added flex parameters to sentence matcher; updated news outlets + minor fixes --- Xtensions/ServiceProperties/news-outlets.json | 11 +- .../server/assist/data/SentenceMatch.java | 60 +++++++++- .../interpreters/InterpretationStep.java | 2 + .../NluTaggedSentenceMatcher.java | 13 ++- .../assist/interpreters/NormalizerLight.java | 2 +- .../interpreters/NormalizerLightDE.java | 2 +- .../interpreters/NormalizerLightEN.java | 2 +- .../interpreters/NormalizerLightTR.java | 2 +- .../server/assist/parameters/NewsSection.java | 4 +- .../sepia/server/assist/server/Config.java | 2 +- .../server/assist/services/NewsRssFeeds.java | 2 +- .../assist/services/SentenceConnect.java | 30 ++++- .../Test_TaggedSentenceMatcher.java | 107 +++++++++--------- 13 files changed, 162 insertions(+), 77 deletions(-) diff --git a/Xtensions/ServiceProperties/news-outlets.json b/Xtensions/ServiceProperties/news-outlets.json index 58974dac..fb93e1c5 100644 --- a/Xtensions/ServiceProperties/news-outlets.json +++ b/Xtensions/ServiceProperties/news-outlets.json @@ -2,7 +2,7 @@ "outlets":[ { "name": "SPIEGEL ONLINE", "url": "https://www.spiegel.de/schlagzeilen/tops/index.rss", "name_html": "SPIEGEL ONLINE" }, { "name": "bento", "url": "https://www.bento.de/rss/nachrichten/", "name_html": "bento" }, - { "name": "Tagesschau", "url": "http://www.tagesschau.de/xml/rss2", "name_html": "tagesschau.de" }, + { "name": "Tagesschau", "url": "http://www.tagesschau.de/xml/rss2", "name_html": "tagesschau.de" }, { "name": "Süddeutsche", "url": "http://rss.sueddeutsche.de/rss/Topthemen", "name_html": "Süddeutsche Zeitung" }, { "name": "FAZ", "url": "https://www.faz.net/rss/aktuell/", "name_html": "Frankfurter Allgemeine" }, { "name": "Bild", "url": "https://www.bild.de/rssfeeds/vw-home/vw-home-16725562,sort=1,view=rss2.bild.xml", "name_html": "Bild.de" }, @@ -17,6 +17,8 @@ { "name": "11FREUNDE", "url": "https://www.11freunde.de/feed", "name_html": "11FREUNDE" }, { "name": "golem.de", "url": "http://rss.golem.de/rss.php?feed=RSS2.0", "name_html": "golem.de" }, { "name": "heise online", "url": "https://www.heise.de/rss/heise-top-atom.xml", "name_html": "heise online" }, + { "name": "heise Developer", "url": "https://www.heise.de/developer/rss/news-atom.xml", "name_html": "heise Developer" }, + { "name": "heise Make", "url": "https://www.heise.de/make/rss/hardware-hacks-atom.xml", "name_html": "heise Make" }, { "name": "PCGames", "url": "http://www.pcgames.de/feed.cfm?menu_alias=home", "name_html": "PCGames" }, { "name": "GameStar", "url": "http://www.pcgames.de/feed.cfm?menu_alias=home", "name_html": "GameStar" }, { "name": "t3n", "url": "https://t3n.de/rss.xml", "name_html": "t3n" }, @@ -29,7 +31,8 @@ { "name": "Filmstarts.de - Serien", "url": "http://rss.filmstarts.de/fs/news/serien?format=xml", "name_html": "Filmstarts.de - Serien" }, { "name": "SPIEGEL Kino", "url": "https://www.spiegel.de/kultur/kino/index.rss", "name_html": "SPIEGEL Kino" }, { "name": "Kino.de - Film-News", "url": "https://www.kino.de/rss/movienews", "name_html": "Kino.de - Film-News" }, - { "name": "deutsche startups", "url": "https://www.deutsche-startups.de/feed/", "name_html": "deutsche startups" } + { "name": "deutsche startups", "url": "https://www.deutsche-startups.de/feed/", "name_html": "deutsche startups" }, + { "name": "wissenschaft.de", "url": "https://www.wissenschaft.de/feed-wissenschaft", "name_html": "wissenschaft.de.de" } ], "deprecated":[ { "name": "RevierSport", "url": "https://abo.reviersport.de/?news-rss-ama", "name_html": "RevierSport" }, @@ -40,8 +43,8 @@ {"section": "main", "group": ["SPIEGEL ONLINE", "Süddeutsche", "FAZ", "Bild", "Tagesschau", "golem.de", "Gruenderszene", "jetzt.de", "bento"]}, {"section": "economy", "group": ["SPIEGEL ONLINE", "Süddeutsche", "FAZ", "Tagesschau", "Gruenderszene"]}, {"section": "politics", "group": ["SPIEGEL ONLINE", "Süddeutsche", "FAZ", "Bild", "Tagesschau"]}, - {"section": "science", "group": ["Wired.de", "t3n", "golem.de", "heise online"]}, - {"section": "tech", "group": ["golem.de", "t3n", "heise online", "PCGames", "Wired.de"]}, + {"section": "science", "group": ["wissenschaft.de", "t3n", "golem.de", "heise online"]}, + {"section": "tech", "group": ["golem.de", "t3n", "heise online", "heise Developer", "heise Make", "PCGames"]}, {"section": "sports", "group": ["Sport1", "Sportschau", "Kicker"]}, {"section": "soccer", "group": ["Sportschau - Fussball", "Kicker", "Sport1", "11FREUNDE"]}, {"section": "games", "group": ["PCGames", "GameStar"]}, diff --git a/src/main/java/net/b07z/sepia/server/assist/data/SentenceMatch.java b/src/main/java/net/b07z/sepia/server/assist/data/SentenceMatch.java index 3ad9c308..59098ccf 100644 --- a/src/main/java/net/b07z/sepia/server/assist/data/SentenceMatch.java +++ b/src/main/java/net/b07z/sepia/server/assist/data/SentenceMatch.java @@ -2,10 +2,13 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.regex.Pattern; import net.b07z.sepia.server.assist.interpreters.NluTools; import net.b07z.sepia.server.assist.tools.StringCompare; +import net.b07z.sepia.server.core.tools.StringTools; /** * This class holds the results of a sentence comparison. @@ -16,13 +19,40 @@ public class SentenceMatch { /** - * Create matcher with two sentences. Consider normalizing the sentences before. + * Create matcher with two sentences. Consider normalizing the sentences before.
+ * Implemented special character sequences:
+ * !(...) - e.g.: "this is !(not) correct" - the sequence MUST match in both sentences or getCertainty() will be 0. * @param inputSentence - sentence to be checked, e.g. input of the user * @param testSentence - sentence to check against, e.g. tagged database sentence */ public SentenceMatch(String inputSentence, String testSentence){ + if (inputSentence.contains("!(") || testSentence.contains("!(")){ + this.wordsThatMustMatch = StringTools.findAllRexEx(inputSentence, "!\\(.*?\\)"); + this.wordsThatMustMatch.addAll(StringTools.findAllRexEx(testSentence, "!\\(.*?\\)")); + if (this.wordsThatMustMatch.size() > 0){ + for (int i=0; i + * Implemented special character sequences:
+ * !(...) - e.g.: "this is !(not) correct" - the sequence MUST match in both sentences + * @param inputSentence - sentence to be checked, e.g. input of the user. Must be free of special char. sequences! + * @param testSentence - sentence to check against, e.g. tagged database sentence. Must be free of special char. sequences! + * @param mustMatchTheseWords - words that MUST be in both sentences or getCertainty() will be 0 + */ + public SentenceMatch(String inputSentence, String testSentence, List mustMatchTheseWords){ this.inputSentence = inputSentence; this.testSentence = testSentence; + this.wordsThatMustMatch = mustMatchTheseWords; } //booleans @@ -34,6 +64,7 @@ public SentenceMatch(String inputSentence, String testSentence){ public int matchedWords_N = 0; //how many words match? (note: how many words of input are in test) public int differentWords_N = 0; //how many words are different? (note: sum of different words) public int taggedWords_N = 0; //how many words are tags? + public boolean matchedRequiredWords = true; //if there are 'wordsThatMustMatch' this indicates if they were all there public double matchedWords_P = 0.0d; //how many percent of the words match (note: max number of words of both sentences is used) public int editDistance = Integer.MAX_VALUE; //how many characters need to be changed to match the string public int wordDistance = Integer.MAX_VALUE; //how many words need to be changed to match @@ -42,9 +73,10 @@ public SentenceMatch(String inputSentence, String testSentence){ public String inputSentence = ""; //sentence to test and get statistics for //public String inputSentenceNorm = ""; //normalized sentence to test and get statistics for public String testSentence = ""; //sentence to test against - public ArrayList inputWords; //list with words of input sentence - public ArrayList testWords; //list with words of test sentence - public HashMap matchedTags; //if the test sentence has tags they are stored here with the findings + public List inputWords; //list with words of input sentence + public List testWords; //list with words of test sentence + public List wordsThatMustMatch; //list with words that have to be in both sentences + public Map matchedTags; //if the test sentence has tags they are stored here with the findings //checks private boolean checkedBoW = false; @@ -217,9 +249,27 @@ public SentenceMatch getWordsToTags(){ } /** - * Calculate certainty by using bag-of-words AND wordDistance. Defaults to 0.0d if none of the test have been made. + * Check if there are words or sequences of words that need to appear in both sentences, find them and set 'matchedRequiredWords' accordingly. + */ + public SentenceMatch getRequiredMatches(){ + if (this.wordsThatMustMatch != null && this.wordsThatMustMatch.size() > 0){ + for (int i=0; i cachedResults); + /* --- Static implementations --- */ + /** * Check if its a direct command or return null. */ diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluTaggedSentenceMatcher.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluTaggedSentenceMatcher.java index b1f9cfc2..5b486fb8 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluTaggedSentenceMatcher.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluTaggedSentenceMatcher.java @@ -70,11 +70,12 @@ public NluResult interpret(NluInput input) { if (commands_this != null){ //run through all sentences - for(Entry entry : commands_this.entrySet()) { + for(Entry entry : commands_this.entrySet()){ String sentence = entry.getKey(); SentenceMatch sm = new SentenceMatch(search, sentence); - //System.out.println("COMPARE: '" + search + "' and '" + sentence + "'"); //debug + //System.out.println("COMPARE: '" + search + "' and '" + sentence + "'"); //debug sm.getIdentity() + .getRequiredMatches() .getBagOfWordsMatch() .getWordDistance(); if (sm.isIdentical){ @@ -83,6 +84,8 @@ public NluResult interpret(NluInput input) { best_sm = sm; best_cmd = entry.getValue(); break; + }else if (!sm.matchedRequiredWords){ + continue; }else{ double this_score = (sm.matchedWords_P); int this_score2 = (sm.wordDistance); @@ -113,7 +116,6 @@ public NluResult interpret(NluInput input) { } } - //no commands list available }else{ return null; @@ -125,9 +127,10 @@ public NluResult interpret(NluInput input) { System.out.println("SENTENCE MATCH: " + best_score); //debug System.out.println("SENTENCE MATCH: " + best_sm.inputSentence); //debug System.out.println("SENTENCE MATCH: " + best_sm.testSentence); //debug - System.out.println("SENTENCE MATCH: " + best_sm.matchedWords_N); //debug + System.out.println("SENTENCE MATCH: " + best_sm.matchedWords_N); //debug System.out.println("SENTENCE MATCH: " + best_sm.taggedWords_N); //debug - System.out.println("SENTENCE MATCH: " + best_sm.differentWords_N); //debug */ + System.out.println("SENTENCE MATCH: " + best_sm.differentWords_N); //debug + */ //replace command summary with new parameters if (best_sm.isIdentical){ diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NormalizerLight.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NormalizerLight.java index 2b47367e..ac74dcb4 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NormalizerLight.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NormalizerLight.java @@ -18,7 +18,7 @@ public String normalizeText(String text) { text = text.replaceAll("İ", "i"); //text = text.replaceAll("I", "ı"); //not good for general languages - text = text.replaceAll("(!|¿|¡|\\?|,(?!\\d))", "").toLowerCase().trim(); + text = text.replaceAll("(!(?!\\()|¿|¡|\\?(?!\\()|,(?!\\d))", "").toLowerCase().trim(); //text = text.replaceAll("(?" + ")"; - text = text.replaceAll("(!|\\?|(?" + ")"; - text = text.replaceAll("(!|\\?|(? InterpretationStep.getDirectCommand(input)); - //spoken response + //response to previous input nluInterpretationSteps.add((input, cachedResults) -> InterpretationStep.getResponse(input)); //slash command nluInterpretationSteps.add((input, cachedResults) -> InterpretationStep.getSlashCommand(input)); diff --git a/src/main/java/net/b07z/sepia/server/assist/services/NewsRssFeeds.java b/src/main/java/net/b07z/sepia/server/assist/services/NewsRssFeeds.java index 405e51c3..9327e477 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/NewsRssFeeds.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/NewsRssFeeds.java @@ -88,7 +88,7 @@ public class NewsRssFeeds implements ServiceInterface{ localTerms_en.put("", "Soccer"); localTerms_en.put("", "Economy"); localTerms_en.put("", "Games"); - localTerms_en.put("", "MusiC"); + localTerms_en.put("", "Music"); localTerms_en.put("", "Cinema"); localTerms_en.put("", "Tv series"); localTerms_en.put("", "Start-up"); diff --git a/src/main/java/net/b07z/sepia/server/assist/services/SentenceConnect.java b/src/main/java/net/b07z/sepia/server/assist/services/SentenceConnect.java index 3ef3412d..7c5283e3 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/SentenceConnect.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/SentenceConnect.java @@ -1,5 +1,8 @@ package net.b07z.sepia.server.assist.services; +import java.util.ArrayList; +import java.util.List; + import org.json.simple.JSONArray; import org.json.simple.JSONObject; @@ -20,6 +23,7 @@ import net.b07z.sepia.server.core.assistant.CMD; import net.b07z.sepia.server.core.assistant.PARAMETERS; import net.b07z.sepia.server.core.tools.Debugger; +import net.b07z.sepia.server.core.tools.Is; /** * Class that handles NPS feedback @@ -29,6 +33,10 @@ */ public class SentenceConnect implements ServiceInterface{ + //flexible parameter replacements (e.g. for TeachUI) + public static final String VAR_BASE = "var"; + public static final int VAR_N = 5; //-> var1, var2 ... varN + //---data--- public static String getButtonText(String language){ if (language.equals(LANGUAGES.DE)){ @@ -49,6 +57,10 @@ public ServiceInfo getInfo(String language) { Parameter p1 = new Parameter(PARAMETERS.SENTENCES) .setRequired(true) .setQuestion(askSentence); + + //optional + //see below at 'background parameters' + info.addParameter(p1); //Answers: @@ -72,7 +84,14 @@ public ServiceResult getResult(NluResult nluResult) { JSONArray sentencesArray = (JSONArray) sentenceJson.get(InterviewData.ARRAY); //get background parameters - String reply = nluResult.getParameter(PARAMETERS.REPLY); + String reply = nluResult.getParameter(PARAMETERS.REPLY); //a reply + List flexParameters = new ArrayList<>(); //flex parameters + for (int i=0; i"; + if (s.contains(tag)){ + s = s.replace(tag, flexParameters.get(i)); + } + } + //TODO: making a clean input would be better nluResult.input.clearParameterResultStorage(); nluResult.input.inputType = "question"; diff --git a/src/test/java/net/b07z/sepia/server/assist/interpreters/Test_TaggedSentenceMatcher.java b/src/test/java/net/b07z/sepia/server/assist/interpreters/Test_TaggedSentenceMatcher.java index 38c261ca..58d94752 100644 --- a/src/test/java/net/b07z/sepia/server/assist/interpreters/Test_TaggedSentenceMatcher.java +++ b/src/test/java/net/b07z/sepia/server/assist/interpreters/Test_TaggedSentenceMatcher.java @@ -1,22 +1,13 @@ package net.b07z.sepia.server.assist.interpreters; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.TreeMap; import org.json.simple.JSONObject; -import net.b07z.sepia.server.assist.interviews.AbstractInterview; -import net.b07z.sepia.server.assist.interviews.InterviewResult; -import net.b07z.sepia.server.assist.interviews.InterviewServicesMap; -import net.b07z.sepia.server.assist.interviews.InterviewInterface; -import net.b07z.sepia.server.assist.server.ConfigServices; import net.b07z.sepia.server.assist.server.ConfigTestServer; -import net.b07z.sepia.server.assist.server.Start; -import net.b07z.sepia.server.assist.services.ServiceInterface; import net.b07z.sepia.server.assist.users.User; import net.b07z.sepia.server.core.tools.DateTime; import net.b07z.sepia.server.core.tools.JSON; @@ -39,57 +30,67 @@ public static void main(String[] args) { NluInterface NLP = new NluTaggedSentenceMatcher(sentences); //interpreters implement the NLU_interface - String text = "wie ist das wetter in München"; - text = "was ist ein synonym fuer Kabeljau"; - System.out.println("INPUT: " + text); - - //parameters - String language = "de"; - String context = "default"; - int mood = -1; - String environment = "web_app"; - - //get the result of the natural-language-processor (i will usually call it either NLP or NLU and mix at will ;-) ) - NluInput input = new NluInput(text, language, context, mood, environment); - input.userLocation = JSON.make("city", "Berlin City", "latitude", 52.519, "longitude", 13.405).toString(); - input.userTime = System.currentTimeMillis(); - input.userTimeLocal = DateTime.getFormattedDate("yyyy.MM.dd_HH:mm:ss"); - //System.out.println("Today: " + Tools_DateTime.getToday("HH:mm:ss yyyy.MM.dd", input)); - User user = ConfigTestServer.getTestUser(ConfigTestServer.email_id1, input, false, true); - input.user = user; - NluResult result = NLP.interpret(input); - - //show all possible results - System.out.println("CMDs identified by NL-Proc: " + result.showAllPossibleCMDs()); - System.out.println("CMD selected: " + result.getCommand()); - - //internally results are usually passed around as NLU_results, but clients can ask for a JSON string via HTTP GET - //JSON example as seen by clients: - JSONObject bestResult = result.getBestResultJSON(); //convert best interpreter result to JSON - System.out.println(bestResult.toJSONString()); //full result as JSON string - if (((String)bestResult.get("result")).matches("success")){ - System.out.println("BEST CMD: " + bestResult.get("command")); //JSON version of result.get_command() - JSONObject params = (JSONObject) bestResult.get("parameters"); //all parameters - System.out.println("PARAMETERS: " + params.toJSONString()); //JSON version of result.get_parameter("xy") - } - - System.out.println("---command: " + result.getCommand()); - System.out.println("---parameters:"); - for (Map.Entry entry : result.parameters.entrySet()) { - System.out.println("----" + entry.getKey() + " = " + entry.getValue()); + String[] texte = new String[]{ + "Wie ist das Wetter in München?", + "Was ist ein Synonym für Kabeljau?" + }; + for (int i=0; i entry : result.parameters.entrySet()) { + System.out.println("----" + entry.getKey() + " = " + entry.getValue()); + } + + System.out.println(""); } //interview module with services + /* + Config.connectToWebSocket = false; Start.setupModules(); Start.setupServicesAndParameters(); + //add a dev. service - Class devServiceClazz = null; String devServiceCMD = "tatoeba"; - ArrayList devService = new ArrayList(); - devService.add(devServiceClazz.getCanonicalName()); - InterviewServicesMap.get().put(devServiceCMD, devService); + ArrayList parrot = new ArrayList(); + parrot.add(RepeatMe.class.getCanonicalName()); + InterviewServicesMap.get().put(devServiceCMD, parrot); //ConfigServices.loadInterviewServicesMap(); //ParameterConfig.setup(); + List services = ConfigServices.getCustomOrSystemServices(input, user, result.getCommand()); if (!services.isEmpty()){ InterviewInterface interview = new AbstractInterview(); @@ -98,10 +99,8 @@ public static void main(String[] args) { InterviewResult iResult = interview.getMissingParameters(result); //<- overwrites old parameters String res =""; if (iResult.isComplete()){ - ///* res = interview.getServiceResults(iResult).getResultJSON(); System.out.println("Interview result: " + res); - //*/ }else{ res = iResult.getApiComment().getResultJSON(); System.out.println("Interview result: " + res); @@ -112,7 +111,7 @@ public static void main(String[] args) { System.out.println("----" + entry.getKey() + " = " + entry.getValue()); } } - + */ } } From dd4f9e37e72b22f278f12562234ad25dd0909449 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Fri, 3 May 2019 21:20:04 +0200 Subject: [PATCH 14/27] new parameters for music and new music search service (wip) --- .../interpreters/NluKeywordAnalyzerDE.java | 39 ++-- .../interpreters/NluKeywordAnalyzerEN.java | 40 ++-- .../assist/interpreters/NormalizerLight.java | 1 + .../interpreters/RegexParameterSearch.java | 14 +- .../interviews/InterviewServicesMap.java | 5 + .../assist/parameters/ClientFunction.java | 3 +- .../server/assist/parameters/MusicAlbum.java | 162 ++++++++++++++ .../server/assist/parameters/MusicArtist.java | 183 ++++++++++++++++ .../server/assist/parameters/MusicGenre.java | 6 + .../assist/parameters/MusicService.java | 197 ++++++++++++++++++ .../assist/parameters/ParameterConfig.java | 8 +- .../assist/parameters/PlaylistName.java | 152 ++++++++++++++ .../sepia/server/assist/parameters/Song.java | 152 ++++++++++++++ .../server/assist/services/MusicSearch.java | 169 +++++++++++++++ .../parameters/Test_MusicParameters.java | 122 +++++++++++ 15 files changed, 1197 insertions(+), 56 deletions(-) create mode 100644 src/main/java/net/b07z/sepia/server/assist/parameters/MusicAlbum.java create mode 100644 src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java create mode 100644 src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java create mode 100644 src/main/java/net/b07z/sepia/server/assist/parameters/PlaylistName.java create mode 100644 src/main/java/net/b07z/sepia/server/assist/parameters/Song.java create mode 100644 src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java create mode 100644 src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java index 9fc32e9f..ef231f01 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java @@ -418,34 +418,27 @@ public NluResult interpret(NluInput input) { } //music - if (NluTools.stringContains(text, "musik|spiele .*|spiel .*|song|songs|lied|lieder") + if (NluTools.stringContains(text, "musik|music|song(s|)|lied(er|)|" + + "spiel(e|) .*|(start(e|)|oeffne) .*\\b(titel|von)|.* ((ab|)spielen)|" + + "album|spotify|deezer|soundcloud|youtube") && !possibleCMDs.contains(CMD.KNOWLEDGEBASE)){ - String this_text = text; + //String this_text = text; possibleCMDs.add(CMD.MUSIC); possibleScore.add(1); index++; - search_music_parameters(this_text, language); - - //genre - String genre = music_parameters.get("music_genre"); - if (!genre.isEmpty()){ - possibleScore.set(index, possibleScore.get(index)+1); - } - //artist - String artist = music_parameters.get("music_artist"); - if (!artist.isEmpty()){ - possibleScore.set(index, possibleScore.get(index)+1); - } - String title = music_parameters.get("music_search"); - //a "startable" is to vague to trigger a score increase ... - //if (!title.matches("")){ - // possibleScore.set(index, possibleScore.get(index)+1); - //} + //it is so obvious, score again + //possibleScore.set(index, possibleScore.get(index)+1); - HashMap pv = new HashMap(); - pv.put(PARAMETERS.SONG, title); - pv.put(PARAMETERS.MUSIC_GENRE, genre); - pv.put(PARAMETERS.MUSIC_ARTIST, artist); + HashMap pv = new HashMap(); //TODO: pass this down to avoid additional checking + AbstractParameterSearch aps = new AbstractParameterSearch() + .setParameters( + PARAMETERS.MUSIC_SERVICE, PARAMETERS.MUSIC_GENRE, PARAMETERS.MUSIC_ALBUM, + PARAMETERS.MUSIC_ARTIST, PARAMETERS.SONG, + PARAMETERS.PLAYLIST_NAME + ) + .setup(input, pv); + aps.getParameters(); + possibleScore.set(index, possibleScore.get(index) + aps.getScore()); possibleParameters.add(pv); } diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java index 51c3da59..a185c7fe 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java @@ -412,34 +412,26 @@ public NluResult interpret(NluInput input) { } //music - if (NluTools.stringContains(text, "music|play .*|song|songs") - && !possibleCMDs.contains(CMD.KNOWLEDGEBASE)){ - String this_text = text; + if (NluTools.stringContains(text, "music|play .*|(start|open) .*\\b(title|of|by)|song(s|)|" + + "album|record|spotify|deezer|soundcloud|youtube") + && !possibleCMDs.contains(CMD.KNOWLEDGEBASE)){ + //String this_text = text; possibleCMDs.add(CMD.MUSIC); possibleScore.add(1); index++; - search_music_parameters(this_text, language); - - //genre - String genre = music_parameters.get("music_genre"); - if (!genre.isEmpty()){ - possibleScore.set(index, possibleScore.get(index)+1); - } - //artist - String artist = music_parameters.get("music_artist"); - if (!artist.isEmpty()){ - possibleScore.set(index, possibleScore.get(index)+1); - } - String title = music_parameters.get("music_search"); - //a "startable" is to vague to trigger a score increase ... - //if (!title.matches("")){ - // possibleScore.set(index, possibleScore.get(index)+1); - //} + //it is so obvious, score again + //possibleScore.set(index, possibleScore.get(index)+1); - HashMap pv = new HashMap(); - pv.put(PARAMETERS.SONG, title); - pv.put(PARAMETERS.MUSIC_GENRE, genre); - pv.put(PARAMETERS.MUSIC_ARTIST, artist); + HashMap pv = new HashMap(); //TODO: pass this down to avoid additional checking + AbstractParameterSearch aps = new AbstractParameterSearch() + .setParameters( + PARAMETERS.MUSIC_SERVICE, PARAMETERS.MUSIC_GENRE, PARAMETERS.MUSIC_ALBUM, + PARAMETERS.MUSIC_ARTIST, PARAMETERS.SONG, + PARAMETERS.PLAYLIST_NAME + ) + .setup(input, pv); + aps.getParameters(); + possibleScore.set(index, possibleScore.get(index) + aps.getScore()); possibleParameters.add(pv); } diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NormalizerLight.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NormalizerLight.java index ac74dcb4..7efbb7b6 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NormalizerLight.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NormalizerLight.java @@ -55,6 +55,7 @@ public static String recover(String rawText, String phrase){ phrase = phrase.replaceAll(" ", "( |, )"); } //System.out.println(phrase); //debug + //TODO: can be optimized with Pattern matcher I guess ... if (rawText.matches("(?i).*?(^|\\b|\\s)(" + phrase + ")($|\\b|\\s).*")){ return rawText.replaceFirst("(?i).*?(^|\\b|\\s)(" + phrase + ")($|\\b|\\s).*", "$2"); }else{ diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java index 362d23a2..f6ea4aa9 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/RegexParameterSearch.java @@ -2098,19 +2098,19 @@ public static String get_music_genre(String input, String language){ public static String get_creator(String input, String language){ String creator = ""; //German - if (language.matches("de")){ - if (!input.matches(".*\\b(von|vom)\\b.*(nach|bis)\\b.*")){ + if (language.matches(LANGUAGES.DE)){ + if (!input.matches(".*\\b(von|vom) .+ (nach|bis)\\b.*")){ creator = NluTools.stringFindFirst(input, "(von|vom) .*"); - creator = creator.replaceAll("^(von|vom)", ""); - creator = creator.replaceAll("^(der|die|das|dem|den|einer|eine|einem)", ""); + creator = creator.replaceAll("^(von|vom) ", ""); + creator = creator.replaceAll("^(der|die|das|dem|den|einer|eine|einem) ", ""); } //English and other }else{ - if (!input.matches(".*\\b(from|of|by)\\b.*(to|till|until)\\b.*")){ + if (!input.matches(".*\\b(from|of|by) .+ (to|till|until)\\b.*")){ creator = NluTools.stringFindFirst(input, "(from|of|by) .*"); - creator = creator.replaceAll("^(from|of|by)", ""); - creator = creator.replaceAll("^(the|a|an)", ""); + creator = creator.replaceAll("^(from|of|by) ", ""); + creator = creator.replaceAll("^(the|a|an) ", ""); } } return creator.trim(); diff --git a/src/main/java/net/b07z/sepia/server/assist/interviews/InterviewServicesMap.java b/src/main/java/net/b07z/sepia/server/assist/interviews/InterviewServicesMap.java index 11f00e35..16d99563 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interviews/InterviewServicesMap.java +++ b/src/main/java/net/b07z/sepia/server/assist/interviews/InterviewServicesMap.java @@ -20,6 +20,7 @@ import net.b07z.sepia.server.assist.services.LocationSearchBasic; import net.b07z.sepia.server.assist.services.MeshNodeConnector; import net.b07z.sepia.server.assist.services.MusicRadioMixed; +import net.b07z.sepia.server.assist.services.MusicSearch; import net.b07z.sepia.server.assist.services.NewsRssFeeds; import net.b07z.sepia.server.assist.services.PlatformControls; import net.b07z.sepia.server.assist.services.SentenceConnect; @@ -159,6 +160,10 @@ public static void load(){ ArrayList radio = new ArrayList(); radio.add(MusicRadioMixed.class.getCanonicalName()); systemInterviewServicesMap.put(CMD.MUSIC_RADIO, radio); + //MUSIC SEARCH + ArrayList music = new ArrayList(); + music.add(MusicSearch.class.getCanonicalName()); + systemInterviewServicesMap.put(CMD.MUSIC, music); //LISTS ArrayList list = new ArrayList(); list.add(Lists.class.getCanonicalName()); diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java b/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java index c37d696d..f15d1a3d 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java @@ -30,7 +30,8 @@ public static enum Type { alwaysOn, meshNode, clexi, - platformFunction //this is handled by PlatformControls service (we use it just for the action) + platformFunction, //this is handled by PlatformControls service (we use it just for the action) + searchForMusic //this is handled by MusicSearch service (we use it just for the action) } //-------data------- diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicAlbum.java b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicAlbum.java new file mode 100644 index 00000000..538a537c --- /dev/null +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicAlbum.java @@ -0,0 +1,162 @@ +package net.b07z.sepia.server.assist.parameters; + +import java.util.regex.Pattern; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.assistant.LANGUAGES; +import net.b07z.sepia.server.assist.interpreters.NluInput; +import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.interpreters.NluTools; +import net.b07z.sepia.server.assist.interpreters.Normalizer; +import net.b07z.sepia.server.assist.interviews.InterviewData; +import net.b07z.sepia.server.assist.server.Config; +import net.b07z.sepia.server.assist.users.User; +import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.tools.JSON; + +/** + * A parameter handler that searches for music albums. + * + * @author Florian Quirin + * + */ +public class MusicAlbum implements ParameterHandler { + + public User user; + public NluInput nluInput; + public String language; + public boolean buildSuccess = false; + + //keep that in mind + String found = ""; //exact (not generalized) string found during extraction (or guess?) + + @Override + public void setup(NluInput nluInput) { + this.nluInput = nluInput; + this.user = nluInput.user; + this.language = nluInput.language; + } + @Override + public void setup(NluResult nluResult) { + this.nluInput = nluResult.input; + this.user = nluResult.input.user; + this.language = nluResult.language; + } + + @Override + public String extract(String input) { + //check storage first + ParameterResult pr = nluInput.getStoredParameterResult(PARAMETERS.MUSIC_ALBUM); + if (pr != null){ + String item = pr.getExtracted(); + this.found = pr.getFound(); + + return item; + } + String optimizedInput = input; + //clean some parameters + ParameterResult prMusicService = ParameterResult.getResult(nluInput, PARAMETERS.MUSIC_SERVICE, optimizedInput); + if (prMusicService != null){ + optimizedInput = ParameterResult.cleanInputOfFoundParameter(nluInput, PARAMETERS.MUSIC_SERVICE, prMusicService, input); + } + + String albumTitle = ""; + //German + if (this.language.matches(LANGUAGES.DE)){ + albumTitle = NluTools.stringFindFirst(optimizedInput, "(von (dem|der) |vom )(album|platte) .*"); + if (!albumTitle.isEmpty()){ + albumTitle = albumTitle.replaceAll("(von dem |vom )(album|platte) ", ""); + albumTitle = albumTitle.replaceAll(" (den |das |)(song|lied|titel) .*", "").trim(); + }else{ + albumTitle = NluTools.stringFindFirst(optimizedInput, "(album|platte) .*"); + albumTitle = albumTitle.replaceAll("^(album|platte) ", "").trim(); + albumTitle = albumTitle.replaceAll(" (von) .*?$", "").trim(); + } + + //Other languages + }else{ + albumTitle = NluTools.stringFindFirst(optimizedInput, "(from (the |))(album|record) .*"); + if (!albumTitle.isEmpty()){ + albumTitle = albumTitle.replaceAll("(from (the |))(album|record) ", ""); + albumTitle = albumTitle.replaceAll(" (the |)(song|title) .*", "").trim(); + }else{ + albumTitle = NluTools.stringFindFirst(optimizedInput, "(album|record) .*"); + albumTitle = albumTitle.replaceAll("^(album|record) ", "").trim(); + albumTitle = albumTitle.replaceAll(" (from|by|of) .*?$", "").trim(); + } + } + this.found = albumTitle; + + //reconstruct original phrase to get proper item names + if (!albumTitle.isEmpty()){ + Normalizer normalizer = Config.inputNormalizers.get(this.language); + albumTitle = normalizer.reconstructPhrase(nluInput.textRaw, albumTitle); + } + + //store it + pr = new ParameterResult(PARAMETERS.MUSIC_ALBUM, albumTitle.trim(), found); + nluInput.addToParameterResultStorage(pr); + + return albumTitle; + } + + @Override + public String guess(String input) { + return ""; + } + + @Override + public String getFound() { + return found; + } + + @Override + public String remove(String input, String found) { + if (language.equals(LANGUAGES.DE)){ + found = "(vom |von |)(dem |der |das |die |)(album|platte) " + Pattern.quote(found); + }else{ + found = "(of |from |)(the |a |)(album|record) " + Pattern.quote(found); + } + return NluTools.stringRemoveFirst(input, found); + } + + @Override + public String responseTweaker(String input) { + if (language.equals(LANGUAGES.DE)){ + input = input.replaceFirst(".*? (dem |der |)(album|platte) ", ""); + input = input.replaceFirst("^(von|vom) ", ""); + return input.trim(); + }else{ + input = input.replaceFirst(".*? (the |)(album|record) ", ""); + input = input.replaceFirst("^(from|of) ", ""); + return input.trim(); + } + } + + @Override + public String build(String input) { + //build default result + JSONObject itemResultJSON = new JSONObject(); + JSON.add(itemResultJSON, InterviewData.VALUE, input); + + buildSuccess = true; + return itemResultJSON.toJSONString(); + } + + @Override + public boolean validate(String input) { + if (input.matches("^\\{\".*\":.+\\}$") && input.contains("\"" + InterviewData.VALUE + "\"")){ + //System.out.println("IS VALID: " + input); //debug + return true; + }else{ + return false; + } + } + + @Override + public boolean buildSuccess() { + return buildSuccess; + } + +} diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java new file mode 100644 index 00000000..548efd48 --- /dev/null +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java @@ -0,0 +1,183 @@ +package net.b07z.sepia.server.assist.parameters; + +import java.util.regex.Pattern; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.assistant.LANGUAGES; +import net.b07z.sepia.server.assist.interpreters.NluInput; +import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.interpreters.NluTools; +import net.b07z.sepia.server.assist.interpreters.Normalizer; +import net.b07z.sepia.server.assist.interviews.InterviewData; +import net.b07z.sepia.server.assist.server.Config; +import net.b07z.sepia.server.assist.users.User; +import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.tools.JSON; + +/** + * A parameter handler that searches for creators like music artists. + * + * @author Florian Quirin + * + */ +public class MusicArtist implements ParameterHandler { + + public User user; + public NluInput nluInput; + public String language; + public boolean buildSuccess = false; + + //keep that in mind + String found = ""; //exact (not generalized) string found during extraction (or guess?) + + @Override + public void setup(NluInput nluInput) { + this.nluInput = nluInput; + this.user = nluInput.user; + this.language = nluInput.language; + } + @Override + public void setup(NluResult nluResult) { + this.nluInput = nluResult.input; + this.user = nluResult.input.user; + this.language = nluResult.language; + } + + @Override + public String extract(String input) { + //check storage first + ParameterResult pr = nluInput.getStoredParameterResult(PARAMETERS.MUSIC_ARTIST); + if (pr != null){ + String item = pr.getExtracted(); + this.found = pr.getFound(); + + return item; + } + String optimizedInput = input; + //clean some parameters + ParameterResult prMusicService = ParameterResult.getResult(nluInput, PARAMETERS.MUSIC_SERVICE, optimizedInput); + if (prMusicService != null){ + optimizedInput = ParameterResult.cleanInputOfFoundParameter(nluInput, PARAMETERS.MUSIC_SERVICE, prMusicService, optimizedInput); + } + ParameterResult prMusicAlbum = ParameterResult.getResult(nluInput, PARAMETERS.MUSIC_ALBUM, optimizedInput); + if (prMusicAlbum != null){ + optimizedInput = ParameterResult.cleanInputOfFoundParameter(nluInput, PARAMETERS.MUSIC_ALBUM, prMusicAlbum, optimizedInput); + } + + String creator = ""; + //German + if (this.language.matches(LANGUAGES.DE)){ + if (!optimizedInput.matches(".*\\b(von|vom) .+ (nach|bis)\\b.*") + || NluTools.stringContains(optimizedInput, "spiel(en|e|)|start(en|e|)|oeffne(n|)")){ + creator = NluTools.stringFindFirst(optimizedInput, "(von|vom) .*"); + creator = creator.replaceAll("^(von|vom) ", ""); + creator = creator.replaceAll("^(der|die|das|dem|den|einer|eine|einem)\\b", "").trim(); + creator = creator.replaceAll("^(artist|musiker|kuenstler)\\b", "").trim(); + creator = creator.replaceAll("(spielen|oeffnen|starten)$", ""); + } + + //English and other + }else{ + if (!optimizedInput.matches(".*\\b(from|of|by) .+ (to|till|until)\\b.*") + || NluTools.stringContains(optimizedInput, "play|start|open")){ + creator = NluTools.stringFindFirst(optimizedInput, "(from|of|by) .*"); + creator = creator.replaceAll("^(from|of|by) ", ""); + creator = creator.replaceAll("^(the|a|an)\\b", "").trim(); + creator = creator.replaceAll("^(artist|musician)\\b", "").trim(); + } + } + //Phase2: + if (creator.isEmpty()){ + if (this.language.matches(LANGUAGES.DE)){ + creator = NluTools.stringFindFirst(optimizedInput, ".*? (songs|lieder|musik)"); + creator = creator.replaceAll(" (songs|lieder|musik)$", ""); + creator = creator.replaceAll(".*\\b(spiel(e|)|oeffne|start(e|))\\b", ""); + }else{ + creator = NluTools.stringFindFirst(optimizedInput, ".*? (songs|music)"); + creator = creator.replaceAll(" (songs|music)$", ""); + creator = creator.replaceAll(".*\\b(play|open|start)\\b", ""); + } + if (!creator.trim().isEmpty()){ + //clean genre parameter + ParameterResult prMusicGenre = ParameterResult.getResult(nluInput, PARAMETERS.MUSIC_GENRE, optimizedInput); + if (prMusicGenre != null){ + creator = ParameterResult.cleanInputOfFoundParameter(nluInput, PARAMETERS.MUSIC_GENRE, prMusicGenre, creator); + } + creator = creator.trim(); + } + } + this.found = creator; + + //reconstruct original phrase to get proper item names + if (!creator.isEmpty()){ + Normalizer normalizer = Config.inputNormalizers.get(this.language); + creator = normalizer.reconstructPhrase(nluInput.textRaw, creator); + } + + //store it + pr = new ParameterResult(PARAMETERS.MUSIC_ARTIST, creator.trim(), found); + nluInput.addToParameterResultStorage(pr); + + return creator; + } + + @Override + public String guess(String input) { + return ""; + } + + @Override + public String getFound() { + return found; + } + + @Override + public String remove(String input, String found) { + if (language.equals(LANGUAGES.DE)){ + found = "(von |vom |)" + Pattern.quote(found); + }else{ + found = "(by |of |from |)" + Pattern.quote(found); + } + return NluTools.stringRemoveFirst(input, found); + } + + @Override + public String responseTweaker(String input){ + if (language.equals(LANGUAGES.DE)){ + input = input.replaceAll("^(von|vom) ", ""); + input = input.replaceAll("^(der|die|das|dem|den|einer|eine|einem) ", ""); + return input.trim(); + }else{ + input = input.replaceAll("^(from|of|by) ", ""); + input = input.replaceAll("^(the|a|an) ", ""); + return input.trim(); + } + } + + @Override + public String build(String input) { + //build default result + JSONObject itemResultJSON = new JSONObject(); + JSON.add(itemResultJSON, InterviewData.VALUE, input); + + buildSuccess = true; + return itemResultJSON.toJSONString(); + } + + @Override + public boolean validate(String input) { + if (input.matches("^\\{\".*\":.+\\}$") && input.contains("\"" + InterviewData.VALUE + "\"")){ + //System.out.println("IS VALID: " + input); //debug + return true; + }else{ + return false; + } + } + + @Override + public boolean buildSuccess() { + return buildSuccess; + } + +} diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicGenre.java b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicGenre.java index 87d5af72..20879b21 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicGenre.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicGenre.java @@ -15,6 +15,12 @@ import net.b07z.sepia.server.core.assistant.PARAMETERS; import net.b07z.sepia.server.core.tools.JSON; +/** + * Parameter handler to search for a music genre like rock. + * + * @author Florian Quirin + * + */ public class MusicGenre implements ParameterHandler{ //-------data------- diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java new file mode 100644 index 00000000..1f4865d5 --- /dev/null +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java @@ -0,0 +1,197 @@ +package net.b07z.sepia.server.assist.parameters; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.assistant.LANGUAGES; +import net.b07z.sepia.server.assist.interpreters.NluInput; +import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.interpreters.NluTools; +import net.b07z.sepia.server.assist.interviews.InterviewData; +import net.b07z.sepia.server.assist.users.User; +import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.tools.Debugger; +import net.b07z.sepia.server.core.tools.JSON; + +/** + * Parameter handler to search for a music service like Spotify or Apple Music + * + * @author Florian Quirin + * + */ +public class MusicService implements ParameterHandler{ + + public static enum Service { + spotify, + apple_music, + amazon_music, + deezer, + soundcloud, + youtube + } + + //-------data------- + public static Map musicServices = new HashMap<>(); + static { + musicServices.put("", "Spotify"); + musicServices.put("", "Apple Music"); + musicServices.put("", "Amazon Music"); + musicServices.put("", "Deezer"); + musicServices.put("", "SoundCloud"); + musicServices.put("", "YouTube"); + } + /** + * Translate generalized value. + * If generalized value is unknown returns empty string. + * @param value - generalized value + * @param language - ISO language code + */ + public static String getLocal(String value, String language){ + String localName = musicServices.get(value); + if (localName == null){ + Debugger.println("MusicService.java - getLocal() has no '" + language + "' version for '" + value + "'", 3); + return ""; + } + return localName; + } + //------------------ + + User user; + NluInput nluInput; + String language; + boolean buildSuccess = false; + + //keep that in mind + String found = ""; //exact (not generalized) string found during extraction (or guess?) + + @Override + public void setup(NluInput nluInput) { + this.nluInput = nluInput; + this.user = nluInput.user; + this.language = nluInput.language; + } + @Override + public void setup(NluResult nluResult) { + this.nluInput = nluResult.input; + this.user = nluResult.input.user; + this.language = nluResult.language; + } + + @Override + public String extract(String input) { + String service = ""; + + //check storage first + ParameterResult pr = nluInput.getStoredParameterResult(PARAMETERS.MUSIC_SERVICE); + if (pr != null){ + service = pr.getExtracted(); + this.found = pr.getFound(); + + return service; + } + + String spotify = "spotify"; + String appleMusic = "apple music|apple|itunes"; + String amazonMusic = "amazon( music|)"; + String deezer = "deezer"; + String soundCloud = "sound( |-|)cloud"; + String youTube = "you( |-|)tube"; + + service = NluTools.stringFindFirst(input, + spotify + "|" + + appleMusic + "|" + + amazonMusic + "|" + + deezer + "|" + + soundCloud + "|" + + youTube + ); + + this.found = service; + + if (!service.isEmpty()){ + if (NluTools.stringContains(service, spotify)){ + service = "<" + Service.spotify.name() + ">"; + }else if (NluTools.stringContains(service, appleMusic)){ + service = "<" + Service.apple_music.name() + ">"; + }else if (NluTools.stringContains(service, amazonMusic)){ + service = "<" + Service.amazon_music.name() + ">"; + }else if (NluTools.stringContains(service, deezer)){ + service = "<" + Service.deezer.name() + ">"; + }else if (NluTools.stringContains(service, soundCloud)){ + service = "<" + Service.soundcloud.name() + ">"; + }else if (NluTools.stringContains(service, youTube)){ + service = "<" + Service.youtube.name() + ">"; + } + } + + //store it + pr = new ParameterResult(PARAMETERS.MUSIC_SERVICE, service, found); + nluInput.addToParameterResultStorage(pr); + + return service; + } + + @Override + public String guess(String input) { + return ""; + } + + @Override + public String getFound() { + return found; + } + + @Override + public String remove(String input, String found) { + //extend found due to split in 'extract' + if (language.equals(LANGUAGES.DE)){ + found = "(mittels |mit |ueber |via |auf |)" + Pattern.quote(found); + }else{ + found = "(with |via |using |on |)" + Pattern.quote(found); + } + return NluTools.stringRemoveFirst(input, found); + } + + @Override + public String responseTweaker(String input){ + if (language.equals(LANGUAGES.DE)){ + return input.replaceAll("(?i).*\\b(mittels|mit|ueber|via|auf)\\b", "").trim(); + }else{ + return input.replaceAll("(?i).*\\b(with|via|using|on)\\b", "").trim(); + } + } + + @Override + public String build(String input) { + String inputLocal = getLocal(input, this.language); + if (inputLocal.isEmpty()){ + return ""; + } + //build default result + JSONObject itemResultJSON = new JSONObject(); + JSON.add(itemResultJSON, InterviewData.VALUE, input.replaceAll("^<|>$", "").trim()); + JSON.add(itemResultJSON, InterviewData.VALUE_LOCAL, inputLocal); + + buildSuccess = true; + return itemResultJSON.toJSONString(); + } + + @Override + public boolean validate(String input) { + if (input.matches("^\\{\".*\":.+\\}$") && input.contains("\"" + InterviewData.VALUE + "\"")){ + //System.out.println("IS VALID: " + input); //debug + return true; + }else{ + return false; + } + } + + @Override + public boolean buildSuccess() { + return buildSuccess = true; + } + +} diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java b/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java index 64ee98c5..043b933c 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java @@ -42,7 +42,6 @@ public static boolean test(){ public static void setup(){ //NOTE: If you ever rename any of the handlers this breaks! (use the test method!) handlerToParameter.put(PARAMETERS.YES_NO, YesNo.class.getCanonicalName()); - handlerToParameter.put(PARAMETERS.CONFIRMATION, Confirm.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.NUMBER, net.b07z.sepia.server.assist.parameters.Number.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.ACTION, Action.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.PLACE, Place.class.getCanonicalName()); @@ -71,6 +70,11 @@ public static void setup(){ handlerToParameter.put(PARAMETERS.FOOD_CLASS, FoodClass.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.RADIO_STATION, RadioStation.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.MUSIC_GENRE, MusicGenre.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.MUSIC_ARTIST, MusicArtist.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.MUSIC_SERVICE, MusicService.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.MUSIC_ALBUM, MusicAlbum.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.SONG, Song.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.PLAYLIST_NAME, PlaylistName.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.SMART_DEVICE, SmartDevice.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.SMART_DEVICE_VALUE, SmartDeviceValue.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.ROOM, Room.class.getCanonicalName()); @@ -85,6 +89,8 @@ public static void setup(){ handlerToParameter.put(PARAMETERS.WINDOWS_FUN, GenericEmptyParameter.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.DEVICE_FUN, GenericEmptyParameter.class.getCanonicalName()); //Generics / Exceptions / Specials + handlerToParameter.put(PARAMETERS.CONFIRMATION, Confirm.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.SELECTION, GenericParameter.class.getCanonicalName()); //TODO: give own, special handler handlerToParameter.put(PARAMETERS.SENTENCES, Sentences.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.DATA, GenericEmptyParameter.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.REPLY, GenericParameter.class.getCanonicalName()); diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/PlaylistName.java b/src/main/java/net/b07z/sepia/server/assist/parameters/PlaylistName.java new file mode 100644 index 00000000..1d0adbef --- /dev/null +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/PlaylistName.java @@ -0,0 +1,152 @@ +package net.b07z.sepia.server.assist.parameters; + +import java.util.regex.Pattern; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.assistant.LANGUAGES; +import net.b07z.sepia.server.assist.interpreters.NluInput; +import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.interpreters.NluTools; +import net.b07z.sepia.server.assist.interpreters.Normalizer; +import net.b07z.sepia.server.assist.interviews.InterviewData; +import net.b07z.sepia.server.assist.server.Config; +import net.b07z.sepia.server.assist.users.User; +import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.tools.JSON; + +/** + * A parameter handler that searches for playlist names. + * + * @author Florian Quirin + * + */ +public class PlaylistName implements ParameterHandler { + + public User user; + public NluInput nluInput; + public String language; + public boolean buildSuccess = false; + + //keep that in mind + String found = ""; //exact (not generalized) string found during extraction (or guess?) + + @Override + public void setup(NluInput nluInput) { + this.nluInput = nluInput; + this.user = nluInput.user; + this.language = nluInput.language; + } + @Override + public void setup(NluResult nluResult) { + this.nluInput = nluResult.input; + this.user = nluResult.input.user; + this.language = nluResult.language; + } + + @Override + public String extract(String input) { + //check storage first + ParameterResult pr = nluInput.getStoredParameterResult(PARAMETERS.PLAYLIST_NAME); + if (pr != null){ + String item = pr.getExtracted(); + this.found = pr.getFound(); + + return item; + } + + String playlistName; + if (language.equals(LANGUAGES.DE)){ + //GERMAN + playlistName = NluTools.stringFindFirst(input, "playlist ((mit |)namen|namens|genannt) .*"); + if (!playlistName.isEmpty()){ + playlistName = playlistName.replaceAll(".*?\\b((mit |)namen|namens|genannt)\\b", ""); + }else{ + playlistName = NluTools.stringFindFirst(input, ".* playlist"); + if (!playlistName.isEmpty()){ + playlistName = playlistName.replaceAll(".*?\\b(meine|die)\\b", ""); + playlistName = playlistName.replaceAll(".*?\\b(starte|oeffne|spiel(e|))\\b", ""); + } + } + playlistName = playlistName.replaceAll("\\b(playlist)\\b", "").trim(); + }else{ + //ENGLISH + playlistName = NluTools.stringFindFirst(input, "playlist (called|named|(with (the |)|)name) .*"); + if (!playlistName.isEmpty()){ + playlistName = playlistName.replaceAll(".*?\\b(called|named|(with (the |)|)name)\\b", ""); + }else{ + playlistName = NluTools.stringFindFirst(input, ".* playlist"); + if (!playlistName.isEmpty()){ + playlistName = playlistName.replaceAll(".*?\\b(my|the)\\b", ""); + playlistName = playlistName.replaceAll(".*?\\b(start|open|play)\\b", ""); + } + } + playlistName = playlistName.replaceAll("\\b(playlist)\\b", "").trim(); + } + this.found = playlistName; + + //reconstruct original phrase to get proper item names + if (!playlistName.isEmpty()){ + Normalizer normalizer = Config.inputNormalizers.get(this.language); + playlistName = normalizer.reconstructPhrase(nluInput.textRaw, playlistName); + } + + //store it + pr = new ParameterResult(PARAMETERS.PLAYLIST_NAME, playlistName.trim(), found); + nluInput.addToParameterResultStorage(pr); + + return playlistName; + } + + @Override + public String guess(String input) { + return ""; + } + + @Override + public String getFound() { + return found; + } + + @Override + public String remove(String input, String found) { + return NluTools.stringRemoveFirst(input, Pattern.quote(found)); + } + + @Override + public String responseTweaker(String input){ + if (language.equals(LANGUAGES.DE)){ + input = input.replaceFirst(".* (heisst|name ist)\\b", ""); + }else{ + input = input.replaceFirst(".* (called|name is)\\b", ""); + } + return input.trim(); + } + + @Override + public String build(String input) { + //build default result + JSONObject itemResultJSON = new JSONObject(); + JSON.add(itemResultJSON, InterviewData.INPUT_RAW, nluInput.textRaw); + JSON.add(itemResultJSON, InterviewData.VALUE, input); + + buildSuccess = true; + return itemResultJSON.toJSONString(); + } + + @Override + public boolean validate(String input) { + if (input.matches("^\\{\".*\":.+\\}$") && input.contains("\"" + InterviewData.VALUE + "\"")){ + //System.out.println("IS VALID: " + input); //debug + return true; + }else{ + return false; + } + } + + @Override + public boolean buildSuccess() { + return buildSuccess; + } + +} diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java b/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java new file mode 100644 index 00000000..5ec3de38 --- /dev/null +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java @@ -0,0 +1,152 @@ +package net.b07z.sepia.server.assist.parameters; + +import java.util.regex.Pattern; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.assistant.LANGUAGES; +import net.b07z.sepia.server.assist.interpreters.NluInput; +import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.interpreters.NluTools; +import net.b07z.sepia.server.assist.interpreters.Normalizer; +import net.b07z.sepia.server.assist.interpreters.RegexParameterSearch; +import net.b07z.sepia.server.assist.interviews.InterviewData; +import net.b07z.sepia.server.assist.server.Config; +import net.b07z.sepia.server.assist.users.User; +import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.tools.JSON; + +/** + * A parameter handler that searches for song names. + * + * @author Florian Quirin + * + */ +public class Song implements ParameterHandler { + + public User user; + public NluInput nluInput; + public String language; + public boolean buildSuccess = false; + + //keep that in mind + String found = ""; //exact (not generalized) string found during extraction (or guess?) + + @Override + public void setup(NluInput nluInput) { + this.nluInput = nluInput; + this.user = nluInput.user; + this.language = nluInput.language; + } + @Override + public void setup(NluResult nluResult) { + this.nluInput = nluResult.input; + this.user = nluResult.input.user; + this.language = nluResult.language; + } + + @Override + public String extract(String input) { + //check storage first + ParameterResult pr = nluInput.getStoredParameterResult(PARAMETERS.SONG); + if (pr != null){ + String item = pr.getExtracted(); + this.found = pr.getFound(); + + return item; + } + String optimizedInput = input; + //clean some parameters + ParameterResult prMusicService = ParameterResult.getResult(nluInput, PARAMETERS.MUSIC_SERVICE, optimizedInput); + if (prMusicService != null){ + optimizedInput = ParameterResult.cleanInputOfFoundParameter(nluInput, PARAMETERS.MUSIC_SERVICE, prMusicService, input); + } + ParameterResult prMusicGenre = ParameterResult.getResult(nluInput, PARAMETERS.MUSIC_GENRE, optimizedInput); + if (prMusicGenre != null){ + optimizedInput = ParameterResult.cleanInputOfFoundParameter(nluInput, PARAMETERS.MUSIC_GENRE, prMusicGenre, optimizedInput); + } + ParameterResult prMusicAlbum = ParameterResult.getResult(nluInput, PARAMETERS.MUSIC_ALBUM, optimizedInput); + if (prMusicAlbum != null){ + optimizedInput = ParameterResult.cleanInputOfFoundParameter(nluInput, PARAMETERS.MUSIC_ALBUM, prMusicAlbum, optimizedInput); + } + + + String song = RegexParameterSearch.get_startable(optimizedInput, this.language); + //some filters + if (song.contains("playlist")){ + song = ""; + } + if (!song.isEmpty()){ + if (language.equals(LANGUAGES.DE)){ + song = song.replaceAll("^(ein(en|ige|) |den |das |etwas )", "").trim(); + song = song.replaceAll("^(song(s|)|lied(er|)|musik|titel|(irgend|)etwas|was|egal was)", "").trim(); + song = song.replaceAll("^((mit (dem |)|)(titel|namen)|namens)", "").trim(); + song = song.replaceAll(".*? (songs|lieder|musik( titel|)|titel)$", "").trim(); + }else{ + song = song.replaceAll("^(the |a |any |some )", "").trim(); + song = song.replaceAll("^(song(s|)|music|title|something|anything)", "").trim(); + song = song.replaceAll("^((with (the |)|)(title|name)|named|)", "").trim(); + song = song.replaceAll(".*? (songs|music)$", "").trim(); + } + } + this.found = song; + + //reconstruct original phrase to get proper item names + if (!song.isEmpty()){ + Normalizer normalizer = Config.inputNormalizers.get(this.language); + song = normalizer.reconstructPhrase(nluInput.textRaw, song); + } + + //store it + pr = new ParameterResult(PARAMETERS.SONG, song.trim(), found); + nluInput.addToParameterResultStorage(pr); + + return song; + } + + @Override + public String guess(String input) { + return ""; + } + + @Override + public String getFound() { + return found; + } + + @Override + public String remove(String input, String found) { + return NluTools.stringRemoveFirst(input, Pattern.quote(found)); + } + + @Override + public String responseTweaker(String input){ + return input.trim(); + } + + @Override + public String build(String input) { + //build default result + JSONObject itemResultJSON = new JSONObject(); + JSON.add(itemResultJSON, InterviewData.VALUE, input); + + buildSuccess = true; + return itemResultJSON.toJSONString(); + } + + @Override + public boolean validate(String input) { + if (input.matches("^\\{\".*\":.+\\}$") && input.contains("\"" + InterviewData.VALUE + "\"")){ + //System.out.println("IS VALID: " + input); //debug + return true; + }else{ + return false; + } + } + + @Override + public boolean buildSuccess() { + return buildSuccess; + } + +} diff --git a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java new file mode 100644 index 00000000..01348d0f --- /dev/null +++ b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java @@ -0,0 +1,169 @@ +package net.b07z.sepia.server.assist.services; + +import java.util.TreeSet; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.data.Parameter; +import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.parameters.ClientFunction; +import net.b07z.sepia.server.assist.services.ServiceBuilder; +import net.b07z.sepia.server.assist.services.ServiceInfo; +import net.b07z.sepia.server.assist.services.ServiceInterface; +import net.b07z.sepia.server.assist.services.ServiceResult; +import net.b07z.sepia.server.assist.services.ServiceInfo.Content; +import net.b07z.sepia.server.assist.services.ServiceInfo.Type; +import net.b07z.sepia.server.core.assistant.ACTIONS; +import net.b07z.sepia.server.core.assistant.CMD; +import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.data.Language; +import net.b07z.sepia.server.core.tools.Is; +import net.b07z.sepia.server.core.tools.JSON; + +/** + * A service to search music via client control actions. + * + * @author Florian Quirin + * + */ +public class MusicSearch implements ServiceInterface{ + + //Define some sentences for testing: + + @Override + public TreeSet getSampleSentences(String lang){ + TreeSet samples = new TreeSet<>(); + //GERMAN + if (lang.equals(Language.DE.toValue())){ + samples.add("Spiele All Along the Watchtower von Jimi Hendrix auf Spotify."); + + //OTHER + }else{ + samples.add("Play All Along the Watchtower by Jimi Hendrix on Spotify."); + } + return samples; + } + + //Basic service setup: + + @Override + public ServiceInfo getInfo(String language) { + ServiceInfo info = new ServiceInfo(Type.plain, Content.data, false); + + //Command + String CMD_NAME = CMD.MUSIC; + info.setIntendedCommand(CMD_NAME); + + //Parameters: + + //optional + Parameter p1 = new Parameter(PARAMETERS.MUSIC_SERVICE); + Parameter p2 = new Parameter(PARAMETERS.MUSIC_GENRE); + Parameter p3 = new Parameter(PARAMETERS.MUSIC_ALBUM); + Parameter p4 = new Parameter(PARAMETERS.MUSIC_ARTIST); + Parameter p5 = new Parameter(PARAMETERS.SONG); + Parameter p6 = new Parameter(PARAMETERS.PLAYLIST_NAME); + + info.addParameter(p1).addParameter(p2).addParameter(p3).addParameter(p4).addParameter(p5).addParameter(p6); + + //Default answers + info.addSuccessAnswer("ok_0b") + .addFailAnswer("error_0a") + .addOkayAnswer("default_not_possible_0a") + .addCustomAnswer("what_music", "music_ask_0a") + ; + + return info; + } + + @Override + public ServiceResult getResult(NluResult nluResult) { + //initialize result + ServiceBuilder api = new ServiceBuilder(nluResult, + getInfoFreshOrCache(nluResult.input, this.getClass().getCanonicalName())); + + //get parameters + Parameter serviceP = nluResult.getOptionalParameter(PARAMETERS.MUSIC_SERVICE, ""); + String service = serviceP.getValueAsString().replaceAll("^<|>$", "").trim(); + + Parameter genreP = nluResult.getOptionalParameter(PARAMETERS.MUSIC_GENRE, ""); + String genre = genreP.getValueAsString(); + + Parameter albumP = nluResult.getOptionalParameter(PARAMETERS.MUSIC_ALBUM, ""); + String album = albumP.getValueAsString(); + + Parameter artistP = nluResult.getOptionalParameter(PARAMETERS.MUSIC_ARTIST, ""); + String artist = artistP.getValueAsString(); + + Parameter songP = nluResult.getOptionalParameter(PARAMETERS.SONG, ""); + String song = songP.getValueAsString(); + + Parameter playlistP = nluResult.getOptionalParameter(PARAMETERS.PLAYLIST_NAME, ""); + String playlistName = playlistP.getValueAsString(); + + boolean hasMinimalInfo = Is.notNullOrEmpty(song) || Is.notNullOrEmpty(artist) + || Is.notNullOrEmpty(album) || Is.notNullOrEmpty(playlistName) || Is.notNullOrEmpty(genre); + + if (!hasMinimalInfo){ + //api.confirmActionOrParameter("", ""); //TODO: experiment with PARAMETERS.CONFIRMATION and PARAMETERS.SELECTION (don't forget response handler) + api.setIncompleteAndAsk(PARAMETERS.SELECTION, "music_ask_0a"); + ServiceResult result = api.buildResult(); + return result; + } + + //This service basically cannot fail here ... only inside client + + //If we have only a song we should declare the 'search' field and not rely on song-name + boolean hasOnlySong = Is.notNullOrEmpty(song) && + Is.nullOrEmpty(artist) && Is.nullOrEmpty(album) && Is.nullOrEmpty(playlistName) && Is.nullOrEmpty(genre); + String search = (hasOnlySong)? song : ""; + + String controlFun = ClientFunction.Type.searchForMusic.name(); + JSONObject controlData = JSON.make( + "artist", artist, + "song", song, + "album", album, + "playlist", playlistName, + "service", service + ); + JSON.put(controlData, "genre", genre); + JSON.put(controlData, "search", search); + api.addAction(ACTIONS.CLIENT_CONTROL_FUN); + api.putActionInfo("fun", controlFun); + api.putActionInfo("controlData", controlData); + + //some buttons - we use the custom function button but the client needs to parse the string itself! + if (Is.nullOrEmpty(service)){ + //simple action button + api.addAction(ACTIONS.BUTTON_CUSTOM_FUN); + api.putActionInfo("fun", "controlFun;;" + controlFun + ";;" + controlData.toJSONString()); + api.putActionInfo("title", "Button"); + + }else{ + //Cards + /* Cards should be generated by client ... + Card card = new Card(Card.TYPE_SINGLE); + card.addElement(ElementType.link, + JSON.make("title", "S.E.P.I.A." + ":", "desc", "Client Controls"), + null, null, "", + "https://sepia-framework.github.io/", + "https://sepia-framework.github.io/img/icon.png", + null, null); + //JSON.put(linkCard, "imageBackground", "transparent"); //use any CSS background option you wish + api.addCard(card.getJSON()); + */ + } + + //all good + /*if (!hasMinimalInfo){ + api.setStatusOkay(); + }else{ + api.setStatusSuccess(); + }*/ + api.setStatusSuccess(); + + //build the API_Result + ServiceResult result = api.buildResult(); + return result; + } +} diff --git a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java new file mode 100644 index 00000000..46cb88f1 --- /dev/null +++ b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java @@ -0,0 +1,122 @@ +package net.b07z.sepia.server.assist.parameters; + +import net.b07z.sepia.server.assist.interpreters.NluInput; +import net.b07z.sepia.server.assist.interpreters.Normalizer; +import net.b07z.sepia.server.assist.parameters.Test_Parameters.TestResult; +import net.b07z.sepia.server.assist.server.Config; +import net.b07z.sepia.server.assist.server.ConfigTestServer; +import net.b07z.sepia.server.assist.server.Start; +import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.tools.Debugger; + +public class Test_MusicParameters { + + private static String[] parametersToTest = new String[]{ + PARAMETERS.MUSIC_SERVICE, PARAMETERS.MUSIC_GENRE, + PARAMETERS.MUSIC_ARTIST, PARAMETERS.SONG, + PARAMETERS.PLAYLIST_NAME, PARAMETERS.MUSIC_ALBUM + }; + + public static void main(String[] args) { + long tic = Debugger.tic(); + + Start.setupServicesAndParameters(); + + //warm up + System.out.println("-----SENTENCE TESTING------"); + + String lang = "de"; + System.out.println("\n----- de -----"); + testMusicParameters("Starte heavy-metal Lieder", "[, heavy-metal, , , , ]", lang); + testMusicParameters("Starte The Metal von Tenacious D", "[, , Tenacious D, The Metal, , ]", lang); + testMusicParameters("Spiele Songs von Jimi Hendrix", "[, , Jimi Hendrix, , , ]", lang); + testMusicParameters("Spiele den Titel Purple Haze von Jimi Hendrix", "[, , Jimi Hendrix, Purple Haze, , ]", lang); + testMusicParameters("Spiele das Lied Purple Haze von Jimi Hendrix", "[, , Jimi Hendrix, Purple Haze, , ]", lang); + testMusicParameters("Spiele den Song mit dem Titel Purple Haze von Jimi Hendrix", "[, , Jimi Hendrix, Purple Haze, , ]", lang); + testMusicParameters("Spiele etwas von Prince", "[, , Prince, , , ]", lang); + testMusicParameters("Spiele All along the Watchtower von Jimi Hendrix", "[, , Jimi Hendrix, All along the Watchtower, , ]", lang); + testMusicParameters("starte meine morning Playlist", "[, meine, , , morning, ]", lang); + testMusicParameters("starte die Playlist mit Namen die Beste", "[, , , , die Beste, ]", lang); + testMusicParameters("spiele Beatsteaks über Spotify", "[, , Beatsteaks, , , ]", lang); + testMusicParameters("spiele Beatsteaks Songs über Spotify", "[, , Beatsteaks, , , ]", lang); + testMusicParameters("spiele 1/2 Love Song von den Ärzten", "[, , Ärzten, 1/2 Love Song, , ]", lang); + testMusicParameters("spiele Eric Clapton Musik auf Apple Music", "[, , Eric Clapton, , , ]", lang); + testMusicParameters("starte Against them All von Stick to your Guns", "[, , Stick to your Guns, Against them All, , ]", lang); + testMusicParameters("Spiele Songs von Jimi Hendrix auf YouTube", "[, , Jimi Hendrix, , , ]", lang); + testMusicParameters("Kannst du von Prince Purple Rain spielen", "[, , Prince, Purple Rain, , ]", lang); + testMusicParameters("Spiele Purple Rain von dem Künstler Prince", "[, , Prince, Purple Rain, , ]", lang); + testMusicParameters("Öffne Stairway to Heaven von Led Zeppelin", "[, , Led Zeppelin, Stairway to Heaven, , ]", lang); + testMusicParameters("Starte das Album Homework von Daft Punk", "[, , Daft Punk, , , Homework]", lang); + testMusicParameters("Starte Around the World von Daft Punk vom Album Homework", "[, , Daft Punk, Around the World, , Homework]", lang); + testMusicParameters("Spiele etwas vom Album Homework", "[, , , , , Homework]", lang); + testMusicParameters("Suche auf YouTube nach Metallica", "[, , Metallica, , , ]", lang); + + lang = "en"; + System.out.println("\n----- en -----"); + testMusicParameters("Start heavy-metal songs", "[, heavy-metal, , , , ]", lang); + testMusicParameters("Start The Metal by Tenacious D", "[, , Tenacious D, The Metal, , ]", lang); + testMusicParameters("play songs by Jimi Hendrix", "[, , Jimi Hendrix, , , ]", lang); + testMusicParameters("play the title Purple Haze by Jimi Hendrix", "[, , Jimi Hendrix, Purple Haze, , ]", lang); + testMusicParameters("play the song Purple Haze from Jimi Hendrix", "[, , Jimi Hendrix, Purple Haze, , ]", lang); + testMusicParameters("play the song with the title Purple Haze by Jimi Hendrix", "[, , Jimi Hendrix, Purple Haze, , ]", lang); + testMusicParameters("play anything by Prince", "[, , Prince, , , ]", lang); + testMusicParameters("play All along the Watchtower by Jimi Hendrix", "[, , Jimi Hendrix, All along the Watchtower, , ]", lang); + testMusicParameters("play my morning playlist", "[, my, , , morning, ]", lang); + testMusicParameters("start my playlist called the best", "[, my, , , the best, ]", lang); + testMusicParameters("play Beatsteaks via Spotify", "[, , Beatsteaks, , , ]", lang); + testMusicParameters("play Beatsteaks songs via Spotify", "[, , Beatsteaks, , , ]", lang); + testMusicParameters("play 1/2 Love Song by Die Ärzte", "[, , Die Ärzte, 1/2 Love Song, , ]", lang); + testMusicParameters("play Eric Clapton music on Apple Music", "[, , Eric Clapton, , , ]", lang); + testMusicParameters("play Against them All by Stick to your Guns", "[, , Stick to your Guns, Against them All, , ]", lang); + testMusicParameters("play Songs by Jimi Hendrix with YouTube", "[, , Jimi Hendrix, , , ]", lang); + testMusicParameters("can you play Prince with Purple Rain", "[, , Prince, Purple Rain, , ]", lang); + testMusicParameters("play Purple Rain by the artist Prince", "[, , Prince, Purple Rain, , ]", lang); + testMusicParameters("open Stairway to Heaven by Led Zeppelin", "[, , Led Zeppelin, Stairway to Heaven, , ]", lang); + testMusicParameters("play the album Homework by Daft Punk", "[, , Daft Punk, , , Homework]", lang); + testMusicParameters("play Around the World by Daft Punk from the album Homework", "[, , Daft Punk, Around the World, , Homework]", lang); + testMusicParameters("play something from the record Homework", "[, , , , , Homework]", lang); + testMusicParameters("Search YouTube for Metallica", "[, , Metallica, , , ]", lang); + + System.out.println("-----------"); + + System.out.println("Took: " + Debugger.toc(tic) + "ms"); + } + + private static boolean testMusicParameters(String text, String shouldBe, String language){ + + NluInput input = ConfigTestServer.getFakeInput("test", language); + + //normalize text + Normalizer normalizer = Config.inputNormalizers.get(language); + if (normalizer != null){ + input.textRaw = text; + text = normalizer.normalizeText(text); + input.text = text; + } + + System.out.println("\ntext: " + text); + TestResult tr = Test_Parameters.testAbstractParameterSearch(input, true, parametersToTest); + //System.out.println("score: " + tr.score); + System.out.println("EXTRACTED: "); + Debugger.printMap(tr.pv); + String res = "["; + for (String p : parametersToTest){ + res += tr.pv.get(p) + ", "; + } + res = res.trim().replaceFirst(",$", ""); + res += "]"; + System.out.println("Result: " + res); + //System.out.println("BUILT: "); + //Debugger.printMap(tr.pvBuild); + System.out.println(""); + + if (res.equals(shouldBe)){ + return true; + }else{ + try{ Thread.sleep(20); }catch(Exception e){} + System.err.println("Found: " + res + " - should be: " + shouldBe); + try{ Thread.sleep(20); }catch(Exception e){} + return false; + } + } +} From 90ed515e0125a985b4e1655e3c1df93a40c7b4f1 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Fri, 3 May 2019 21:20:22 +0200 Subject: [PATCH 15/27] youtube tweak in websearch --- .../assist/parameters/WebSearchEngine.java | 10 +------ .../assist/services/WebsearchBasic.java | 30 ++++++++++++++++--- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchEngine.java b/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchEngine.java index d60e2fc4..73c52a08 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchEngine.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchEngine.java @@ -1,6 +1,5 @@ package net.b07z.sepia.server.assist.parameters; -import java.util.ArrayList; import java.util.regex.Pattern; import org.json.simple.JSONObject; @@ -22,14 +21,7 @@ public class WebSearchEngine implements ParameterHandler{ public static final String BING = "Bing"; public static final String DUCK_DUCK_GO = "DuckDuckGo"; */ - public static final String names = "(google|bing|duck duck go|duck duck|duckduckgo|yahoo)"; - public static ArrayList list = new ArrayList<>(); - static{ - list.add("google"); - list.add("bing"); - list.add("duck duck go"); - list.add("yahoo"); - } + public static final String names = "(google|bing|duck duck go|duck duck|duckduckgo|yahoo|youtube)"; //-------------- User user; diff --git a/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java b/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java index 16e12b65..68d37925 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java @@ -3,7 +3,9 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; +import java.util.List; import java.util.Random; +import java.util.stream.Collectors; import org.json.simple.JSONObject; @@ -15,7 +17,6 @@ import net.b07z.sepia.server.assist.interpreters.NluResult; import net.b07z.sepia.server.assist.interviews.InterviewData; import net.b07z.sepia.server.assist.parameters.SearchSection; -import net.b07z.sepia.server.assist.parameters.WebSearchEngine; import net.b07z.sepia.server.assist.server.Config; import net.b07z.sepia.server.assist.services.ServiceInfo.Content; import net.b07z.sepia.server.assist.services.ServiceInfo.Type; @@ -32,6 +33,15 @@ */ public class WebsearchBasic implements ServiceInterface{ + //Search engines + public static ArrayList engines = new ArrayList<>(); + static{ + engines.add("google"); + engines.add("bing"); + engines.add("duck duck go"); + engines.add("yahoo"); + } + //--- data --- public static String getButtonText(String engine, String language){ if (language.equals(LANGUAGES.DE)){ @@ -232,6 +242,16 @@ public static String[] getWebSearchUrl(String engine, String section, String sea }else if (section.equals("recipes")){ search_url = "https://duckduckgo.com/?kae=d&ia=recipes&q="; search = searchReduced; } + }else if (engine.contains("youtube")){ + engine = "YouTube"; + search_url = "https://www.youtube.com/results?search_query="; + if (section.equals("pictures")){ + search_url = "https://www.google.com/search?q="; search = searchReduced; + }else if (section.equals("videos")){ + search_url = "https://www.youtube.com/results?search_query="; search = searchReduced; + }else if (section.equals("recipes")){ + search_url = "https://www.youtube.com/results?search_query="; search = searchReduced; + } }else{ engine = "Google"; search_url = "https://www.google.com/search?q="; //<- default is google @@ -263,10 +283,12 @@ public static String[] getWebSearchUrl(String engine, String section, String sea * @param section - optimize for this section (not yet working) */ public static String getPseudoRandomEngine(String except, String section){ - ArrayList engines = WebSearchEngine.list; - engines.remove(except); + List enginesToChoose = engines.stream().filter(s -> { + return !s.equals(except); + }).collect(Collectors.toList()); + //enginesToChoose.remove(except); Random rand = new Random(); - String randomEngine = engines.get(rand.nextInt(engines.size())); + String randomEngine = enginesToChoose.get(rand.nextInt(enginesToChoose.size())); //TODO: optimize for section return randomEngine; } From 41e2609e0549631a187492a9909b911d0bd68510 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Sat, 4 May 2019 18:12:18 +0200 Subject: [PATCH 16/27] added SELECT parameter and dynamic select option to ServiceBuilder --- Xtensions/Assistant/answers/answers_de.txt | 7 ++ Xtensions/Assistant/answers/answers_en.txt | 7 ++ .../server/assist/interpreters/NluResult.java | 3 +- .../assist/interpreters/ResponseHandler.java | 27 +++++- .../assist/parameters/ParameterConfig.java | 2 +- .../server/assist/parameters/Select.java | 97 +++++++++++++++++++ .../assist/services/ServiceBuilder.java | 33 +++++++ 7 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 src/main/java/net/b07z/sepia/server/assist/parameters/Select.java diff --git a/Xtensions/Assistant/answers/answers_de.txt b/Xtensions/Assistant/answers/answers_de.txt index 9a6007c5..3857dc7f 100644 --- a/Xtensions/Assistant/answers/answers_de.txt +++ b/Xtensions/Assistant/answers/answers_de.txt @@ -164,8 +164,15 @@ music_0c;; rep=0|mood=5;; Im Moment kann ich <1> leider nicht abspielen, sorry music_0d;; rep=0|mood=5;; Im Moment kann ich <1> von <2> leider nicht abspielen, sorry. Hast du schon eine Radio Suche probiert? ;;char=cool,polite,neutral music_ask_0a;; rep=0|mood=5;; Welche Musik soll ich für dich suchen? ;;char=neutral music_ask_0a;; rep=0|mood=5;; Wie heißt der Titel, Künstler oder das Genre das ich suchen soll? ;;char=polite,neutral +music_ask_0b;; rep=0|mood=5;; Möchtest du einen Song, einen Künstler oder eine Playlist hören? ;;char=neutral +music_ask_0b;; rep=1|mood=5;; Sorry sagtest du Song, Künstler oder Playlist? ;;char=neutral +music_ask_0b;; rep=2|mood=5;; Sorry , habs immer noch nicht verstanden. Du kannst wählen zwischen Song, Künstler oder Playlist. Was soll es sein? ;;char=neutral +music_ask_1a;; rep=0|mood=5;; Wie heißt der Song, den ich suchen soll? ;;char=neutral +music_ask_1b;; rep=0|mood=5;; Wie heißt der Künstler, den ich suchen soll? ;;char=neutral +music_ask_1c;; rep=0|mood=5;; Wie heißt die Playlist, die ich suchen soll? ;;char=neutral music_1a;; rep=0|mood=5;; Sekunde, ich lade <1> von <2>. ;;char=polite,neutral music_1b;; rep=0|mood=5;; Alles klar, ich starte dann mal <1>. ;;char=cool,polite,neutral +music_1c;; rep=0|mood=5;; Eine Sekunde, Musik wird gesucht. ;;char=cool,polite,neutral music_radio_0a;; rep=0|mood=5;; , ich kann gerade leider kein Radio finden. Weiß auch nicht wieso. ;;char=cool,rude music_radio_0b;; rep=0|mood=5;; , zu diesem Suchbegriff habe ich leider kein Radio gefunden. ;;char=polite,neutral music_radio_ask_0a;; rep=0|mood=5;; Welchen Radiosender, welches Genre oder welche Band soll ich für dich suchen? ;;char=cool,polite,neutral diff --git a/Xtensions/Assistant/answers/answers_en.txt b/Xtensions/Assistant/answers/answers_en.txt index 9518042a..ebee6d82 100644 --- a/Xtensions/Assistant/answers/answers_en.txt +++ b/Xtensions/Assistant/answers/answers_en.txt @@ -152,8 +152,15 @@ music_0c;; rep=0|mood=5;; Right now I can't play <1>, sorry. Did you try a rad music_0d;; rep=0|mood=5;; Right now I can't play <1> by <2>, sorry. Did you try a radio search? ;;char=polite,neutral music_ask_0a;; rep=0|mood=5;; What kind of song or music do you want me to search? ;;char=neutral music_ask_0a;; rep=0|mood=5;; What is the name of the song, the artist or genre I should look for? ;;char=neutral +music_ask_0b;; rep=0|mood=5;; Do you want to hear a song an artist or a playlist? ;;char=neutral +music_ask_0b;; rep=1|mood=5;; Sorry did you say song, artist or playlist? ;;char=neutral +music_ask_0b;; rep=2|mood=5;; Sorry , still didn't get that. You can choose between song, artist or playlist. What should it be? ;;char=neutral +music_ask_1a;; rep=0|mood=5;; What is the name of the song? ;;char=neutral +music_ask_1b;; rep=0|mood=5;; What is the name of the artist? ;;char=neutral +music_ask_1c;; rep=0|mood=5;; What is the name of the playlist? ;;char=neutral music_1a;; rep=0|mood=5;; One moment, I'm loading <1> by <2>. ;;char=neutral music_1b;; rep=0|mood=5;; Ok, I'll start <1>. ;;char=neutral +music_1c;; rep=0|mood=5;; One second, searching music. ;;char=cool,polite,neutral music_radio_0a;; rep=0|mood=5;; , I can't find any radio station. Seems there is some problem. ;;char=neutral music_radio_0b;; rep=0|mood=5;; , I haven't found any radio station for this. ;;char=neutral music_radio_ask_0a;; rep=0|mood=5;; What's the name of the radio station, genre or band you want to hear? ;;char=neutral diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluResult.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluResult.java index f3947721..2ee225f0 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluResult.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluResult.java @@ -295,7 +295,8 @@ public void removeFinal(String param){ } } /** - * Add a dynamic parameter. + * Add a dynamic parameter. + * A dynamic parameter is created on-the-fly inside a service but will be included in interview build scripts. */ public void addDynamicParameter(String parameter){ Set dynamics = new HashSet<>(); diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/ResponseHandler.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/ResponseHandler.java index 975e54c5..953a5b86 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/ResponseHandler.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/ResponseHandler.java @@ -3,17 +3,21 @@ import java.util.HashMap; import java.util.List; +import org.json.simple.JSONObject; + import net.b07z.sepia.server.assist.data.Parameter; import net.b07z.sepia.server.assist.parameters.Confirm; import net.b07z.sepia.server.assist.parameters.DateAndTime; import net.b07z.sepia.server.assist.parameters.ParameterConfig; import net.b07z.sepia.server.assist.parameters.ParameterHandler; +import net.b07z.sepia.server.assist.parameters.Select; import net.b07z.sepia.server.assist.server.Config; import net.b07z.sepia.server.assist.server.ConfigServices; import net.b07z.sepia.server.assist.services.ServiceInfo; import net.b07z.sepia.server.assist.services.ServiceInterface; import net.b07z.sepia.server.core.assistant.CMD; import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.tools.Is; /** * This interpreter handles responses to questions the server (assistant) asked the client (user). @@ -99,7 +103,10 @@ public NluResult interpret(NluInput input) { //check for special (dynamic) parameters (can also be custom) }else if (missing_input.startsWith(Confirm.PREFIX)){ - result = confirmResponse(result, response, missing_input, language, cmd, input); + result = confirmResponse(result, response, missing_input, input); + + }else if (missing_input.startsWith(Select.PREFIX)){ + result = selectionResponse(result, response, missing_input, input); //SDK or custom handler? }else if (Parameter.isCustom(missing_input)){ @@ -161,7 +168,9 @@ public static NluResult tweakResult(NluResult nluResult, String response, String //nluResult.remove_final(p.getName()); nluResult.setParameter(p.getName(), value); //TODO: use the remove method? - if (command.equals(CMD.FASHION)){ //TODO: this should not be here ... find a more consistent, logical location ... + + //TODO: this should not be here ... find a more consistent, logical location ... + if (command.equals(CMD.FASHION)){ if (p.getName().equals(PARAMETERS.COLOR)){ tweaked = handler.remove(tweaked, handler.getFound()); }else if (p.getName().equals(PARAMETERS.FASHION_SIZE)){ @@ -196,13 +205,23 @@ public static NluResult tweakResult(NluResult nluResult, String response, String } //check for special (dynamic) parameters - public static NluResult confirmResponse(NluResult nluResult, String response, String parameter, String language, String command, NluInput input){ + public static NluResult confirmResponse(NluResult nluResult, String response, String parameter, NluInput input){ //Confirmation request ParameterHandler handler = new Parameter(PARAMETERS.CONFIRMATION).getHandler(); handler.setup(input); String value = handler.extract(response); nluResult.setParameter(parameter, value); - + return nluResult; + } + public static NluResult selectionResponse(NluResult nluResult, String response, String parameter, NluInput input){ + //Confirmation request + ParameterHandler handler = new Parameter(PARAMETERS.SELECTION).getHandler(); + handler.setup(input); + String value = handler.extract(response); + JSONObject result = Select.matchSelection(value, parameter, nluResult); + if (Is.notNullOrEmpty(result)){ + nluResult.setParameter(parameter, result.toJSONString()); + } return nluResult; } diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java b/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java index 043b933c..cf2939d9 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java @@ -90,7 +90,7 @@ public static void setup(){ handlerToParameter.put(PARAMETERS.DEVICE_FUN, GenericEmptyParameter.class.getCanonicalName()); //Generics / Exceptions / Specials handlerToParameter.put(PARAMETERS.CONFIRMATION, Confirm.class.getCanonicalName()); - handlerToParameter.put(PARAMETERS.SELECTION, GenericParameter.class.getCanonicalName()); //TODO: give own, special handler + handlerToParameter.put(PARAMETERS.SELECTION, Select.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.SENTENCES, Sentences.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.DATA, GenericEmptyParameter.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.REPLY, GenericParameter.class.getCanonicalName()); diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/Select.java b/src/main/java/net/b07z/sepia/server/assist/parameters/Select.java new file mode 100644 index 00000000..d732ab26 --- /dev/null +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/Select.java @@ -0,0 +1,97 @@ +package net.b07z.sepia.server.assist.parameters; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.interpreters.NluTools; +import net.b07z.sepia.server.assist.interviews.InterviewData; +import net.b07z.sepia.server.core.tools.Debugger; +import net.b07z.sepia.server.core.tools.Is; +import net.b07z.sepia.server.core.tools.JSON; + +/** + * This is the parameter that receives the current selection request. + * It is basically a generic parameter that holds some extras to find the correct selection.
+ * Note: Generic parameters do not increase matching score in NLU process. + * + * @author Florian Quirin + * + */ +public class Select extends CustomParameter implements ParameterHandler{ + + //-----data----- + public static final String PREFIX = "select_"; //prefix for custom select parameter + public static final String OPTIONS_PREFIX = "options_"; //prefix for options to custom select parameter + + //--- Methods to evaluate selection --- + + /** + * Try to match the selection options to the response. + * @param response - User response given + * @param customParameterName - parameter that will store the result + * @param nluResult - NluResult given + * @return null or JSONObject with match and number of match like this {"value": "green", "selection": 1} + */ + public static JSONObject matchSelection(String response, String customParameterName, NluResult nluResult){ + String optionsToParameterString = nluResult.getParameter(customParameterName.replace(PREFIX, OPTIONS_PREFIX)); + if (Is.nullOrEmpty(optionsToParameterString) || !optionsToParameterString.startsWith("{")){ + //User forgot to submit options .. this should not happen when using service-result builder properly + Debugger.println("Missing or invalid options for select parameter '" + customParameterName + "': " + optionsToParameterString, 1); + return null; + }else{ + String match = ""; + int matchingKey = 0; + try { + JSONObject options = JSON.parseString(optionsToParameterString); //NOTE: Has to be built like this {"1":"Song", "2":"Playlist", "3":"Artist" ...} + for (int i=0; i + * Format of selectOptions: {"1": "red", "2": "green", "3": "blue|yellow", "4": ...} - Allowed are words and regular expressions. + */ + public void askUserToSelectOption(String customSelectparameterName, JSONObject selectOptions, String question){ + String dynamicSelectParameter = Select.PREFIX + customSelectparameterName; + String dynamicSelectOptions = Select.OPTIONS_PREFIX + customSelectparameterName; + nluResult.addDynamicParameter(dynamicSelectParameter); + nluResult.setParameter(dynamicSelectOptions, selectOptions.toJSONString()); + setIncompleteAndAsk(dynamicSelectParameter, question); + } + /** + * Get result of custom select request to user or null. Format: {"value": "green", "selection": 2, "input": "..."} + */ + public JSONObject getSelectedOptionOf(String customSelectparameterName){ + Parameter selectP = nluResult.getOptionalParameter(Select.PREFIX + customSelectparameterName, ""); + if (selectP.isDataEmpty()){ + return null; + }else{ + JSONObject json = selectP.getData(); + if (!json.containsKey("selection") || JSON.getIntegerOrDefault(json, "selection", 0) == 0){ + return null; + }else{ + //clean up options + nluResult.setParameter(Select.OPTIONS_PREFIX + customSelectparameterName, ""); + //return + return json; + } + } + } + /** * Put "value" to "key" in current actionInfo element. E.g.: putActionInfo("url", call_url). The current JSONArray element (action) is previously * set by using addAction(value), so be sure to first add an action and then add info for that action.
From 6a9d8a8f9c30cc62e267e6871cbe7c90c4e450a0 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Sat, 4 May 2019 18:13:10 +0200 Subject: [PATCH 17/27] added media-stop to client functions; updated music search service --- .../assist/parameters/ClientFunction.java | 17 ++++++-- .../assist/services/ClientControls.java | 16 +++++++- .../server/assist/services/MusicSearch.java | 40 ++++++++++++++++--- 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java b/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java index f15d1a3d..916a832a 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java @@ -30,6 +30,7 @@ public static enum Type { alwaysOn, meshNode, clexi, + media, platformFunction, //this is handled by PlatformControls service (we use it just for the action) searchForMusic //this is handled by MusicSearch service (we use it just for the action) } @@ -43,12 +44,14 @@ public static enum Type { local_de.put("", "der Always-On Modus"); local_de.put("", "die Mesh-Node"); local_de.put("", "CLEXI"); + local_de.put("", "die Medienwiedergabe"); local_en.put("", "the settings"); local_en.put("", "the volume"); local_en.put("", "the Always-On mode"); local_en.put("", "the mesh-node"); local_en.put("", "CLEXI"); + local_en.put("", "the media player"); } /** * Translate generalized value. @@ -111,11 +114,12 @@ public String extract(String input) { } */ - String settings, volume, alwaysOn, meshNode, clexi; + String settings, volume, alwaysOn, meshNode, clexi, media; //German if (language.matches(LANGUAGES.DE)){ settings = "einstellung(en|)|setting(s|)|menue|option(en|)"; - volume = "lautstaerke|musik|radio|sound"; + media = "medienwiedergabe|medi(a|en) player|(musik|song|lied) (anhalten|stoppen|stop|beenden|schliessen)"; + volume = "lautstaerke|musik|radio|sound"; //NOTE: musik is used here as well, make sure media is checked first alwaysOn = "always(-| |)on"; meshNode = "mesh(-| |)node"; clexi = "clexi"; @@ -123,14 +127,16 @@ public String extract(String input) { //English and other }else{ settings = "setting(s|)|menu(e|)|option(s|)"; - volume = "volume|music|radio|sound"; + media = "media player|(media|music|song) (stop|close|end)"; + volume = "volume|music|radio|sound"; //NOTE: music is used here as well, make sure media is checked first alwaysOn = "always(-| |)on"; meshNode = "mesh(-| |)node"; clexi = "clexi"; } String extracted = NluTools.stringFindFirst(input, - settings + "|" + + settings + "|" + + media + "|" + volume + "|" + alwaysOn + "|" + meshNode + "|" + @@ -141,6 +147,9 @@ public String extract(String input) { //SETTINGS if (NluTools.stringContains(extracted, settings)){ clientFun = "<" + Type.settings + ">"; + //MEDIA - NOTE: use before VOLUME! + }else if (NluTools.stringContains(extracted, media)){ + clientFun = "<" + Type.media + ">"; //VOLUME }else if (NluTools.stringContains(extracted, volume)){ clientFun = "<" + Type.volume + ">"; diff --git a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java index ad1cf976..be6dee52 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java @@ -75,13 +75,17 @@ public ServiceInfo getInfo(String language) { + "(.* |)(einstellung(en|)|settings) oeffnen( .*|)|" + "(.* |)always(-| |)on( .*|)|" + "(.* |)(musik|sound|radio) (lauter|leiser)( .*|)|" - + "(.* |)lautstaerke( .*|)" + + "(.* |)lautstaerke( .*|)|" + + "(.* |)(medi(a|en) player|medienwiedergabe)( .*|)|" + + "(.* |)(musik|song|lied|medien|media) (anhalten|stoppen|stop|beenden|schliessen)( .*|)" + ")$", DE); info.setCustomTriggerRegX("^(" + "( .*|)open setting(s|)( .*|)|" + "(.* |)always(-| |)on( .*|)|" + "( .*|)(music|sound|radio) (quieter|louder)( .*|)|" - + "(.* |)volume( .*|)" + + "(.* |)volume( .*|)|" + + "(.* |)(media player)( .*|)|" + + "(.* |)(media|music|song) (stop|close|end)( .*|)" + ")$", EN); info.setCustomTriggerRegXscoreBoost(2); //boost service a bit to increase priority over similar ones @@ -141,6 +145,7 @@ public ServiceResult getResult(NluResult nluResult) { boolean isAlwaysOn = controlFun.equals(ClientFunction.Type.alwaysOn.name()); boolean isMeshNode = controlFun.equals(ClientFunction.Type.meshNode.name()); boolean isClexi = controlFun.equals(ClientFunction.Type.clexi.name()); + boolean isMedia = controlFun.equals(ClientFunction.Type.media.name()); Parameter dataP = nluResult.getOptionalParameter(PARAMETERS.DATA, ""); String data = dataP.getValueAsString(); @@ -161,6 +166,13 @@ public ServiceResult getResult(NluResult nluResult) { }else{ //TODO: implement, ask or fail? } + }else if (isMedia){ + //media support - TODO: limited to stop currently + if (isActionClose){ + actionName = "stop"; + }else{ + //TODO: implement, ask or fail? + } }else if (isVolume){ //check data for volume if (num.isEmpty() && !Is.nullOrEmpty(data)){ diff --git a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java index 01348d0f..ef9cc4b0 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java @@ -17,6 +17,7 @@ import net.b07z.sepia.server.core.assistant.CMD; import net.b07z.sepia.server.core.assistant.PARAMETERS; import net.b07z.sepia.server.core.data.Language; +import net.b07z.sepia.server.core.tools.Debugger; import net.b07z.sepia.server.core.tools.Is; import net.b07z.sepia.server.core.tools.JSON; @@ -67,10 +68,13 @@ public ServiceInfo getInfo(String language) { info.addParameter(p1).addParameter(p2).addParameter(p3).addParameter(p4).addParameter(p5).addParameter(p6); //Default answers - info.addSuccessAnswer("ok_0b") + info.addSuccessAnswer("music_1c") .addFailAnswer("error_0a") .addOkayAnswer("default_not_possible_0a") - .addCustomAnswer("what_music", "music_ask_0a") + .addCustomAnswer("music_type", "music_ask_0b") + .addCustomAnswer("what_song", "music_ask_1a") + .addCustomAnswer("what_artist", "music_ask_1b") + .addCustomAnswer("what_playlist", "music_ask_1c") ; return info; @@ -105,10 +109,34 @@ public ServiceResult getResult(NluResult nluResult) { || Is.notNullOrEmpty(album) || Is.notNullOrEmpty(playlistName) || Is.notNullOrEmpty(genre); if (!hasMinimalInfo){ - //api.confirmActionOrParameter("", ""); //TODO: experiment with PARAMETERS.CONFIRMATION and PARAMETERS.SELECTION (don't forget response handler) - api.setIncompleteAndAsk(PARAMETERS.SELECTION, "music_ask_0a"); - ServiceResult result = api.buildResult(); - return result; + String customTypeSelectionParameter = "music_type"; + JSONObject typeSelection = api.getSelectedOptionOf(customTypeSelectionParameter); + if (Is.notNullOrEmpty(typeSelection)){ + //System.out.println(typeSelection.toJSONString()); //DEBUG + int selection = JSON.getIntegerOrDefault(typeSelection, "selection", 0); + if (selection == 1){ + api.setIncompleteAndAsk(PARAMETERS.SONG, "music_ask_1a"); + }else if (selection == 2){ + api.setIncompleteAndAsk(PARAMETERS.MUSIC_ARTIST, "music_ask_1b"); + }else if (selection == 3){ + api.setIncompleteAndAsk(PARAMETERS.PLAYLIST_NAME, "music_ask_1c"); + }else{ + //This should never happen + Debugger.println("MusicSearch had invalid selection result for 'music_type'", 1); + api.setStatusFail(); + } + ServiceResult result = api.buildResult(); + return result; + }else{ + api.askUserToSelectOption(customTypeSelectionParameter, JSON.make( + "1", "song|lied", + "2", "artist|kuenstler", + "3", "playlist" + ), "music_ask_0b"); + ServiceResult result = api.buildResult(); + //System.out.println(result.getResultJSON().toString()); //DEBUG + return result; + } } //This service basically cannot fail here ... only inside client From 1f51738f62e214671d95b08a1ec4342389a2494e Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Sun, 5 May 2019 22:17:14 +0200 Subject: [PATCH 18/27] added SpotifyApi class to authenticate and search items; fixed some bugs --- Xtensions/assist.custom.properties | 2 + Xtensions/assist.properties | 2 + .../interpreters/NluKeywordAnalyzerDE.java | 4 +- .../interpreters/NluKeywordAnalyzerEN.java | 4 +- .../server/assist/parameters/MusicArtist.java | 8 +- .../assist/parameters/MusicService.java | 54 +++- .../sepia/server/assist/parameters/Song.java | 4 +- .../sepia/server/assist/server/Config.java | 4 + .../assist/services/ClientControls.java | 4 +- .../server/assist/services/MusicSearch.java | 16 ++ .../sepia/server/assist/tools/GeoCoding.java | 7 + .../sepia/server/assist/tools/SpotifyApi.java | 230 ++++++++++++++++++ .../parameters/Test_MusicParameters.java | 4 + .../Test_RadioStation_MusicGenre.java | 6 +- .../server/assist/tools/Test_SpotifyApi.java | 50 ++++ 15 files changed, 374 insertions(+), 25 deletions(-) create mode 100644 src/main/java/net/b07z/sepia/server/assist/tools/SpotifyApi.java create mode 100644 src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java diff --git a/Xtensions/assist.custom.properties b/Xtensions/assist.custom.properties index 7efac3b5..c592c393 100644 --- a/Xtensions/assist.custom.properties +++ b/Xtensions/assist.custom.properties @@ -53,6 +53,8 @@ amazon_dynamoDB_secret= google_maps_key= graphhopper_key= forecast_io_key= +spotify_client_id= +spotify_client_secret= openhab_host= dirble_key= acapela_vaas_app= diff --git a/Xtensions/assist.properties b/Xtensions/assist.properties index bda46277..7e9ad973 100644 --- a/Xtensions/assist.properties +++ b/Xtensions/assist.properties @@ -52,6 +52,8 @@ amazon_dynamoDB_secret= google_maps_key= graphhopper_key= forecast_io_key= +spotify_client_id= +spotify_client_secret= openhab_host= dirble_key= acapela_vaas_app= diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java index ef231f01..357e0385 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java @@ -419,8 +419,8 @@ public NluResult interpret(NluInput input) { //music if (NluTools.stringContains(text, "musik|music|song(s|)|lied(er|)|" - + "spiel(e|) .*|(start(e|)|oeffne) .*\\b(titel|von)|.* ((ab|)spielen)|" - + "album|spotify|deezer|soundcloud|youtube") + + "spiel(e|) .*|(start(e|)|oeffne|zeig(e|)) .*\\b(titel|von)|.* ((ab|)spielen)|" + + "album|spotify|deezer|soundcloud|youtube|vlc") && !possibleCMDs.contains(CMD.KNOWLEDGEBASE)){ //String this_text = text; possibleCMDs.add(CMD.MUSIC); diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java index a185c7fe..cc45e81e 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java @@ -412,8 +412,8 @@ public NluResult interpret(NluInput input) { } //music - if (NluTools.stringContains(text, "music|play .*|(start|open) .*\\b(title|of|by)|song(s|)|" - + "album|record|spotify|deezer|soundcloud|youtube") + if (NluTools.stringContains(text, "music|play .*|(start|open|show) .*\\b(title(s|)|track(s|)|of|by)|song(s|)|" + + "album|record|spotify|deezer|soundcloud|youtube|vlc") && !possibleCMDs.contains(CMD.KNOWLEDGEBASE)){ //String this_text = text; possibleCMDs.add(CMD.MUSIC); diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java index 548efd48..4ab7f92f 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java @@ -90,12 +90,12 @@ public String extract(String input) { //Phase2: if (creator.isEmpty()){ if (this.language.matches(LANGUAGES.DE)){ - creator = NluTools.stringFindFirst(optimizedInput, ".*? (songs|lieder|musik)"); - creator = creator.replaceAll(" (songs|lieder|musik)$", ""); + creator = NluTools.stringFindFirst(optimizedInput, ".*? (songs|lieder|titel|musik)"); + creator = creator.replaceAll(" (songs|lieder|musik|titel)$", ""); creator = creator.replaceAll(".*\\b(spiel(e|)|oeffne|start(e|))\\b", ""); }else{ - creator = NluTools.stringFindFirst(optimizedInput, ".*? (songs|music)"); - creator = creator.replaceAll(" (songs|music)$", ""); + creator = NluTools.stringFindFirst(optimizedInput, ".*? (songs|titles|tracks|music)"); + creator = creator.replaceAll(" (songs|music|titles|tracks)$", ""); creator = creator.replaceAll(".*\\b(play|open|start)\\b", ""); } if (!creator.trim().isEmpty()){ diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java index 1f4865d5..e15452a7 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java @@ -26,22 +26,28 @@ public class MusicService implements ParameterHandler{ public static enum Service { spotify, + spotify_link, apple_music, + apple_music_link, amazon_music, deezer, soundcloud, - youtube + youtube, + vlc_media_player } //-------data------- public static Map musicServices = new HashMap<>(); static { musicServices.put("", "Spotify"); + musicServices.put("", "Spotify"); musicServices.put("", "Apple Music"); + musicServices.put("", "Apple Music"); musicServices.put("", "Amazon Music"); musicServices.put("", "Deezer"); musicServices.put("", "SoundCloud"); musicServices.put("", "YouTube"); + musicServices.put("", "VLC Media Player"); } /** * Translate generalized value. @@ -99,20 +105,46 @@ public String extract(String input) { String deezer = "deezer"; String soundCloud = "sound( |-|)cloud"; String youTube = "you( |-|)tube"; + String vlc = "vlc( media|)( player|)"; - service = NluTools.stringFindFirst(input, - spotify + "|" + - appleMusic + "|" + - amazonMusic + "|" + - deezer + "|" + - soundCloud + "|" + - youTube - ); + if (language.equals(LANGUAGES.DE)){ + //GERMAN + service = NluTools.stringFindFirst(input, + "(mittels |mit |ueber |via |auf |durchsuche )(dem |der |die |)(" + + spotify + "|" + + appleMusic + "|" + + amazonMusic + "|" + + deezer + "|" + + soundCloud + "|" + + youTube + "|" + + vlc + + ")( link|)" + ); + }else{ + //OTHER LANGUAGES + service = NluTools.stringFindFirst(input, + "(with |via |using |on |over |search )(the |)(" + + spotify + "|" + + appleMusic + "|" + + amazonMusic + "|" + + deezer + "|" + + soundCloud + "|" + + youTube + "|" + + vlc + + ")( link|)" + ); + } this.found = service; if (!service.isEmpty()){ - if (NluTools.stringContains(service, spotify)){ + if (NluTools.stringContains(service, "link")){ + if (NluTools.stringContains(service, spotify)){ + service = "<" + Service.spotify_link.name() + ">"; + }else if (NluTools.stringContains(service, appleMusic)){ + service = "<" + Service.apple_music_link.name() + ">"; + } + }else if (NluTools.stringContains(service, spotify)){ service = "<" + Service.spotify.name() + ">"; }else if (NluTools.stringContains(service, appleMusic)){ service = "<" + Service.apple_music.name() + ">"; @@ -124,6 +156,8 @@ public String extract(String input) { service = "<" + Service.soundcloud.name() + ">"; }else if (NluTools.stringContains(service, youTube)){ service = "<" + Service.youtube.name() + ">"; + }else if (NluTools.stringContains(service, vlc)){ + service = "<" + Service.vlc_media_player.name() + ">"; } } diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java b/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java index 5ec3de38..a351d1a1 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java @@ -84,9 +84,9 @@ public String extract(String input) { song = song.replaceAll(".*? (songs|lieder|musik( titel|)|titel)$", "").trim(); }else{ song = song.replaceAll("^(the |a |any |some )", "").trim(); - song = song.replaceAll("^(song(s|)|music|title|something|anything)", "").trim(); + song = song.replaceAll("^(song(s|)|music|title(s|)|track(s|)|something|anything)", "").trim(); song = song.replaceAll("^((with (the |)|)(title|name)|named|)", "").trim(); - song = song.replaceAll(".*? (songs|music)$", "").trim(); + song = song.replaceAll(".*? (songs|music|titles)$", "").trim(); } } this.found = song; diff --git a/src/main/java/net/b07z/sepia/server/assist/server/Config.java b/src/main/java/net/b07z/sepia/server/assist/server/Config.java index 630d87ef..4397a383 100644 --- a/src/main/java/net/b07z/sepia/server/assist/server/Config.java +++ b/src/main/java/net/b07z/sepia/server/assist/server/Config.java @@ -301,6 +301,8 @@ public static void setup_nlu_steps(){ public static String google_maps_key = ""; public static String graphhopper_key = ""; public static String forecast_io_key = ""; + public static String spotify_client_id = ""; + public static String spotify_client_secret = ""; public static String dirble_key = ""; public static String acapela_vaas_app = ""; public static String acapela_vaas_key = ""; @@ -472,6 +474,8 @@ public static void loadSettings(String confFile){ google_maps_key = settings.getProperty("google_maps_key"); graphhopper_key = settings.getProperty("graphhopper_key"); forecast_io_key = settings.getProperty("forecast_io_key"); + spotify_client_id = settings.getProperty("spotify_client_id"); + spotify_client_secret = settings.getProperty("spotify_client_secret"); dirble_key = settings.getProperty("dirble_key"); acapela_vaas_app = settings.getProperty("acapela_vaas_app"); acapela_vaas_key = settings.getProperty("acapela_vaas_key"); diff --git a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java index be6dee52..6a1094fb 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java @@ -76,7 +76,7 @@ public ServiceInfo getInfo(String language) { + "(.* |)always(-| |)on( .*|)|" + "(.* |)(musik|sound|radio) (lauter|leiser)( .*|)|" + "(.* |)lautstaerke( .*|)|" - + "(.* |)(medi(a|en) player|medienwiedergabe)( .*|)|" + + "(.* |)(medi(a|en)(-| |)player|medienwiedergabe)( .*|)|" + "(.* |)(musik|song|lied|medien|media) (anhalten|stoppen|stop|beenden|schliessen)( .*|)" + ")$", DE); info.setCustomTriggerRegX("^(" @@ -84,7 +84,7 @@ public ServiceInfo getInfo(String language) { + "(.* |)always(-| |)on( .*|)|" + "( .*|)(music|sound|radio) (quieter|louder)( .*|)|" + "(.* |)volume( .*|)|" - + "(.* |)(media player)( .*|)|" + + "(.* |)(media(-| |)player)( .*|)|" + "(.* |)(media|music|song) (stop|close|end)( .*|)" + ")$", EN); info.setCustomTriggerRegXscoreBoost(2); //boost service a bit to increase priority over similar ones diff --git a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java index ef9cc4b0..ed04caa5 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java @@ -14,8 +14,10 @@ import net.b07z.sepia.server.assist.services.ServiceInfo.Content; import net.b07z.sepia.server.assist.services.ServiceInfo.Type; import net.b07z.sepia.server.core.assistant.ACTIONS; +import net.b07z.sepia.server.core.assistant.CLIENTS; import net.b07z.sepia.server.core.assistant.CMD; import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.assistant.CLIENTS.Platform; import net.b07z.sepia.server.core.data.Language; import net.b07z.sepia.server.core.tools.Debugger; import net.b07z.sepia.server.core.tools.Is; @@ -156,10 +158,23 @@ public ServiceResult getResult(NluResult nluResult) { ); JSON.put(controlData, "genre", genre); JSON.put(controlData, "search", search); + api.addAction(ACTIONS.CLIENT_CONTROL_FUN); api.putActionInfo("fun", controlFun); api.putActionInfo("controlData", controlData); + //Check platform + Platform platform = CLIENTS.getPlatform(nluResult.input.clientInfo); + if (platform.equals(Platform.browser)){ + + }else if (platform.equals(Platform.android)){ + + }else if (platform.equals(Platform.ios)){ + + }else if (platform.equals(Platform.windows)){ + + } + //some buttons - we use the custom function button but the client needs to parse the string itself! if (Is.nullOrEmpty(service)){ //simple action button @@ -168,6 +183,7 @@ public ServiceResult getResult(NluResult nluResult) { api.putActionInfo("title", "Button"); }else{ + //Link service? //Cards /* Cards should be generated by client ... Card card = new Card(Card.TYPE_SINGLE); diff --git a/src/main/java/net/b07z/sepia/server/assist/tools/GeoCoding.java b/src/main/java/net/b07z/sepia/server/assist/tools/GeoCoding.java index 8ed6f30d..5ca367e9 100644 --- a/src/main/java/net/b07z/sepia/server/assist/tools/GeoCoding.java +++ b/src/main/java/net/b07z/sepia/server/assist/tools/GeoCoding.java @@ -142,7 +142,10 @@ private static HashMap graphhopper_get_coordinates(String addres String url = "https://graphhopper.com/api/1/geocode?q=" + URLEncoder.encode(address, "UTF-8") + "&locale=" + language + add_params; //System.out.println("gh-url: " + url); //debug + long tic = System.currentTimeMillis(); JSONObject response = Connectors.httpGET(url.trim()); + Statistics.addExternalApiHit("Graphhopper Geocoder"); + Statistics.addExternalApiTime("Graphhopper Geocoder", tic); JSONArray hits = (JSONArray) response.get("hits"); if (!hits.isEmpty()){ JSONObject points = (JSONObject) ((JSONObject) hits.get(0)).get("point"); @@ -186,6 +189,10 @@ private static HashMap graphhopper_get_address(String latitude, return null; } // TODO implement + /* + Statistics.addExternalApiHit("Graphhopper GetAddress"); + Statistics.addExternalApiTime("Graphhopper GetAddress", tic); + */ HashMap result = new HashMap(); //copy from input result.put(LOCATION.LAT, latitude); diff --git a/src/main/java/net/b07z/sepia/server/assist/tools/SpotifyApi.java b/src/main/java/net/b07z/sepia/server/assist/tools/SpotifyApi.java new file mode 100644 index 00000000..f7c0d5cd --- /dev/null +++ b/src/main/java/net/b07z/sepia/server/assist/tools/SpotifyApi.java @@ -0,0 +1,230 @@ +package net.b07z.sepia.server.assist.tools; + +import java.net.URLEncoder; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.server.Statistics; +import net.b07z.sepia.server.core.tools.Connectors; +import net.b07z.sepia.server.core.tools.ContentBuilder; +import net.b07z.sepia.server.core.tools.Is; +import net.b07z.sepia.server.core.tools.JSON; + +/** + * Class that helps to connect with Spotify APIs. + * + * @author Florian Quirin + * + */ +public class SpotifyApi { + + private String clientId; + private String clientSecret; + + public String token = ""; + public String tokenType = ""; + public long tokenValidUntil = 0; + + public static final String spotifyAuthUrl = "https://accounts.spotify.com/api/token"; + public static final String spotifySearchUrl = "https://api.spotify.com/v1/search"; + + public static final String TYPE_TRACK = "track"; + public static final String TYPE_ALBUM = "album"; + public static final String TYPE_ARTIST = "artist"; + public static final String TYPE_PLAYLIST = "playlist"; + + public SpotifyApi(String clientId, String clientSecret){ + this.clientId = clientId; + this.clientSecret = clientSecret; + } + + /** + * Get an access token using client ID and secret. + * @return status code: 0:all good, 1:no credentials, 2:access problems, 3:unknown problems, 4:access denied + */ + public int getTokenViaClientCredentials(){ + //Do we have credentials? + if (Is.nullOrEmpty(clientId) || Is.nullOrEmpty(clientSecret)){ + return 1; + } + try{ + //Build auth. header entry + String authString = "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()); + + //Request headers + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); + headers.put("Authorization", authString); + + //Request data + String data = ContentBuilder.postForm("grant_type", "client_credentials"); + + //Call + long tic = System.currentTimeMillis(); + JSONObject res = Connectors.httpPOST(spotifyAuthUrl, data, headers); + Statistics.addExternalApiHit("SpotifyApi getTokenViaClientCredentials"); + Statistics.addExternalApiTime("SpotifyApi getTokenViaClientCredentials", tic); + //System.out.println(res.toJSONString()); //DEBUG + if (!Connectors.httpSuccess(res)){ + return 2; + } + String token = JSON.getString(res, "access_token"); + String tokenType = JSON.getString(res, "token_type"); + long expiresIn = JSON.getLongOrDefault(res, "expires_in", 0); + if (Is.nullOrEmpty(token)){ + return 4; + }else{ + this.token = token; + this.tokenType = tokenType; + this.tokenValidUntil = System.currentTimeMillis() + (expiresIn * 1000); + } + return 0; + + }catch (Exception e){ + Statistics.addExternalApiHit("SpotifyApi-error getTokenViaClientCredentials"); + return 3; + } + } + + /** + * Search ONE item in the Spotify database that fits best. + * Search 'type' (track, artist, album, playlist) is automatically set using the information given for track, artist, album and playlist. + * @param track + * @param artist + * @param album + * @param playlist + * @return + */ + public JSONObject searchBestItem(String track, String artist, String album, String playlist, String genre){ + if (Is.nullOrEmpty(this.token)){ + return JSON.make("error", "not authorized", "status", 401); + } + if ((this.tokenValidUntil - System.currentTimeMillis()) <= 0){ + //Try to get new token + int refreshCode = getTokenViaClientCredentials(); + if (refreshCode > 0){ + return JSON.make("error", "failed to refresh token", "status", 401, "code", refreshCode); + } + } + //Build request + String type = ""; + String q = ""; + try{ + //Search + if (Is.notNullOrEmpty(playlist)){ + type = TYPE_PLAYLIST; + q = URLEncoder.encode(playlist, "UTF-8"); + + }else if (Is.notNullOrEmpty(track) && Is.notNullOrEmpty(album)){ + type = TYPE_TRACK; + q = URLEncoder.encode("track:" + track + " album:" + album, "UTF-8"); + + }else if (Is.notNullOrEmpty(track) && Is.notNullOrEmpty(artist)){ + type = TYPE_TRACK; + q = URLEncoder.encode("track:" + track + " artist:" + artist, "UTF-8"); + + }else if (Is.notNullOrEmpty(album)){ + type = TYPE_ALBUM; + q = URLEncoder.encode(album, "UTF-8"); + + }else if (Is.notNullOrEmpty(artist)){ + type = TYPE_ARTIST; + q = URLEncoder.encode(artist, "UTF-8"); + + }else if (Is.notNullOrEmpty(genre)){ + type = TYPE_PLAYLIST; + q = URLEncoder.encode(genre, "UTF-8"); + + }else if (Is.notNullOrEmpty(track)){ + type = TYPE_TRACK; + q = URLEncoder.encode(track, "UTF-8"); + + }else{ + return JSON.make("error", "combination of search keys invalid", "status", 400); + } + int N = 1; + String url = spotifySearchUrl + "?q=" + q + "&type=" + type + "&limit=" + N; + + //Header + String authString = "Bearer " + this.token; + + Map headers = new HashMap<>(); + headers.put("Accept", "application/json"); + headers.put("Content-Type", "application/json"); + headers.put("Authorization", authString); + + //Call + //System.out.println("URL: " + url); //DEBUG + long tic = System.currentTimeMillis(); + JSONObject res = Connectors.httpGET(url, null, headers); + Statistics.addExternalApiHit("SpotifyApi searchBestItem"); + Statistics.addExternalApiTime("SpotifyApi searchBestItem", tic); + + //Get first item + if (Connectors.httpSuccess(res)){ + JSONArray items = null; + if (type.equals(TYPE_PLAYLIST)){ + items = JSON.getJArray(res, new String[]{"playlists", "items"}); + }else if (type.equals(TYPE_ALBUM)){ + items = JSON.getJArray(res, new String[]{"albums", "items"}); + }else if (type.equals(TYPE_ARTIST)){ + items = JSON.getJArray(res, new String[]{"artists", "items"}); + }else if (type.equals(TYPE_TRACK)){ + items = JSON.getJArray(res, new String[]{"tracks", "items"}); + } + if (Is.notNullOrEmpty(items)){ + JSONObject firstItem = JSON.getJObject(items, 0); + if (firstItem != null){ + String resType = JSON.getString(firstItem, "type"); + if (resType.equals(TYPE_PLAYLIST)){ + return JSON.make( + "type", TYPE_PLAYLIST, + "name", JSON.getString(firstItem, "name"), + "uri", JSON.getString(firstItem, "uri"), + "owner_display_name", JSON.getObject(firstItem, new String[]{"owner", "display_name"}), + "total_tracks", JSON.getObject(firstItem, new String[]{"tracks", "total"}) + ); + }else if (resType.equals(TYPE_ALBUM)){ + JSONArray artists = JSON.getJArray(firstItem, "artists"); + return JSON.make( + "type", TYPE_ALBUM, + "name", JSON.getString(firstItem, "name"), + "uri", JSON.getString(firstItem, "uri"), + "primary_artist", (Is.notNullOrEmpty(artists)? JSON.getString(JSON.getJObject(artists, 0), "name") : ""), + "total_tracks", JSON.getIntegerOrDefault(firstItem, "total_tracks", -1) + ); + }else if (resType.equals(TYPE_ARTIST)){ + return JSON.make( + "type", TYPE_ARTIST, + "name", JSON.getString(firstItem, "name"), + "uri", JSON.getString(firstItem, "uri"), + "genres", JSON.getJArray(firstItem, "genres"), + "album", JSON.getObject(firstItem, new String[]{"album", "name"}) + ); + }else if (resType.equals(TYPE_TRACK)){ + JSONArray artists = JSON.getJArray(firstItem, "artists"); + return JSON.make( + "type", TYPE_TRACK, + "name", JSON.getString(firstItem, "name"), + "uri", JSON.getString(firstItem, "uri"), + "primary_artist", (Is.notNullOrEmpty(artists)? JSON.getString(JSON.getJObject(artists, 0), "name") : ""), + "album", JSON.getObject(firstItem, new String[]{"album", "name"}) + ); + } + return firstItem; + } + } + } + + return res; + + }catch (Exception e){ + Statistics.addExternalApiHit("SpotifyApi-error searchBestItem"); + return JSON.make("error", e.getMessage(), "status", 500); + } + } +} diff --git a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java index 46cb88f1..9a785549 100644 --- a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java +++ b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java @@ -50,6 +50,8 @@ public static void main(String[] args) { testMusicParameters("Starte Around the World von Daft Punk vom Album Homework", "[, , Daft Punk, Around the World, , Homework]", lang); testMusicParameters("Spiele etwas vom Album Homework", "[, , , , , Homework]", lang); testMusicParameters("Suche auf YouTube nach Metallica", "[, , Metallica, , , ]", lang); + testMusicParameters("starte Rockmusik mit VLC player", "[, rock, , , , ]", lang); + testMusicParameters("starte Rockmusik via Spotify link", "[, rock, , , , ]", lang); lang = "en"; System.out.println("\n----- en -----"); @@ -76,6 +78,8 @@ public static void main(String[] args) { testMusicParameters("play Around the World by Daft Punk from the album Homework", "[, , Daft Punk, Around the World, , Homework]", lang); testMusicParameters("play something from the record Homework", "[, , , , , Homework]", lang); testMusicParameters("Search YouTube for Metallica", "[, , Metallica, , , ]", lang); + testMusicParameters("start Rock music using VLC player", "[, Rock, , , , ]", lang); + testMusicParameters("start Rock music via Spotify link", "[, Rock, , , , ]", lang); System.out.println("-----------"); diff --git a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_RadioStation_MusicGenre.java b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_RadioStation_MusicGenre.java index bf445f4f..21221e7b 100644 --- a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_RadioStation_MusicGenre.java +++ b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_RadioStation_MusicGenre.java @@ -36,7 +36,7 @@ public static void main(String[] args) { texts.add("Spiele ein Radio mit Rockmusik"); texts.add("Spiele ein Radio mit Metallica"); texts.add("Spiele ein Radio mit Musik von Metallica"); - texts.add("Spiele Rockradio"); + texts.add("Spiele Rockradio"); //TODO: this one fails printTestResults(texts, parametersToTest, language); @@ -54,12 +54,12 @@ public static void main(String[] args) { texts.add("play a radio with rockmusic"); texts.add("play a radio with Metallica"); texts.add("play a radio with songs of Metallica"); - texts.add("play rockstation"); + texts.add("play rockstation"); //TODO: this one fails printTestResults(texts, parametersToTest, language); } - static void printTestResults(List texts, String[] parametersToTest, String language){ + private static void printTestResults(List texts, String[] parametersToTest, String language){ for (String text : texts){ NluInput input = ConfigTestServer.getFakeInput("test", language); diff --git a/src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java b/src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java new file mode 100644 index 00000000..dc0f6dcb --- /dev/null +++ b/src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java @@ -0,0 +1,50 @@ +package net.b07z.sepia.server.assist.tools; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.server.Config; +import net.b07z.sepia.server.assist.server.Start; +import net.b07z.sepia.server.core.tools.Debugger; + +public class Test_SpotifyApi { + + public static void main(String[] args) { + + //load custom config + Start.loadSettings(new String[]{"--test"}); + + SpotifyApi sapi = new SpotifyApi(Config.spotify_client_id, Config.spotify_client_secret); + int code = sapi.getTokenViaClientCredentials(); + + System.out.println("SpotifyApi auth. result code: " + code); + System.out.println("Token: " + sapi.token); + System.out.println("Token type: " + sapi.tokenType); + System.out.println("Token expires in (ms): " + (sapi.tokenValidUntil - System.currentTimeMillis())); + + Debugger.sleep(1000); + + JSONObject s1 = sapi.searchBestItem("Paradise City", "Guns n Roses", "", "", ""); + System.out.println(s1.toJSONString()); + + Debugger.sleep(1000); + + JSONObject s2 = sapi.searchBestItem("", "Guns n Roses", "Appetite For Destruction", "", ""); + System.out.println(s2.toJSONString()); + + Debugger.sleep(1000); + + JSONObject s3 = sapi.searchBestItem("", "Guns n Roses", "", "", ""); + System.out.println(s3.toJSONString()); + + Debugger.sleep(1000); + + JSONObject s4 = sapi.searchBestItem("", "", "", "", "Rock"); + System.out.println(s4.toJSONString()); + + Debugger.sleep(1000); + + JSONObject s5 = sapi.searchBestItem("", "", "", "Party", ""); + System.out.println(s5.toJSONString()); + } + +} From a9eb2acb7e38099cf7feacf9bd0363f6be4d279c Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Sun, 5 May 2019 22:24:43 +0200 Subject: [PATCH 19/27] Update assist.test.properties --- Xtensions/assist.test.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Xtensions/assist.test.properties b/Xtensions/assist.test.properties index 7b8d6e52..db10436c 100644 --- a/Xtensions/assist.test.properties +++ b/Xtensions/assist.test.properties @@ -3,6 +3,7 @@ assistant_name=Sepia assistant_id=uid1005 assistant_email=assistant@sepia.localhost assistant_pwd=4be708b703c518d10a97e4db421f0f75b66f9ff8c0ae65b6c5c13684a01f804d +assistant_allow_follow_ups=true module_account=elasticsearch #module_account=dynamo_db module_answers=file @@ -52,6 +53,8 @@ amazon_dynamoDB_secret= google_maps_key= graphhopper_key= forecast_io_key= +spotify_client_id= +spotify_client_secret= openhab_host= dirble_key= acapela_vaas_app= From f880d4159d3f9eb1c02048d825b35aa689fcfa28 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Thu, 9 May 2019 22:59:43 +0200 Subject: [PATCH 20/27] MusicSearch cards; SpotifyApi tweaks; bug fixes --- .../files/images/brands/apple-music-logo.png | Bin 0 -> 9348 bytes .../files/images/brands/spotify-logo.png | Bin 0 -> 2092 bytes .../files/images/brands/youtube-logo.png | Bin 0 -> 1157 bytes .../files/images/cards/music_default.png | Bin 0 -> 1443 bytes .../server/assist/interviews/NoResult.java | 2 +- .../server/assist/parameters/MusicAlbum.java | 16 +- .../server/assist/parameters/MusicArtist.java | 30 +-- .../assist/parameters/PlaylistName.java | 8 +- .../sepia/server/assist/parameters/Song.java | 16 +- .../assist/parameters/WebSearchRequest.java | 14 +- .../sepia/server/assist/server/Config.java | 21 +- .../sepia/server/assist/server/Start.java | 3 +- .../server/assist/services/MusicSearch.java | 222 +++++++++++++++--- .../server/assist/services/Wikipedia.java | 4 +- .../sepia/server/assist/tools/SpotifyApi.java | 35 ++- .../parameters/Test_MusicParameters.java | 2 + .../server/assist/tools/Test_SpotifyApi.java | 5 + 17 files changed, 293 insertions(+), 85 deletions(-) create mode 100644 Xtensions/WebContent/files/images/brands/apple-music-logo.png create mode 100644 Xtensions/WebContent/files/images/brands/spotify-logo.png create mode 100644 Xtensions/WebContent/files/images/brands/youtube-logo.png create mode 100644 Xtensions/WebContent/files/images/cards/music_default.png diff --git a/Xtensions/WebContent/files/images/brands/apple-music-logo.png b/Xtensions/WebContent/files/images/brands/apple-music-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..722799eccd6136d0b5d0151cb9c51b4890af7212 GIT binary patch literal 9348 zcmbt)WltOo(=`Q(JB#b$P~6?!-QC@tg)Lg3c#-1n?hf5z#ofJlab2AEy1%@C;F)CR zWKME2$z(F0PK>&$96Aay3JeSkx`Mp4=6~q_zeGa#uL@Z6&ix1QUXlvhNdE;0$vXPK z9@$;qzzYTj73Y8XX>K#*^*@rC41=u(QG?*|dU|=X=6r?4z z{d3N9gHo;aOfJRxd!Yh4iw71Hz3TT<%Y4#N)(XdRzQjl=cDeLc@`d6h@{;+BXK83FLFN?})r`a%@{G8tmwP9vWM>hSdPv;+FmAONkU=xC`axXBL5y(59a-HT!<)&0sZc6%MV!m)3{$^-0K04;`g(Y1Dx11e5fIrF$9V8mh0{4sR$$Y> z!aAy>QA%@2W5=&3>kOmXK_#0+ey;hgIXuUQQ^&iBohJnpeZloKyn%tlv8sH=xo*#Ai`z>2&vR0@+pbA;zXc0`+{`wFx~qLTKIwi*dj z=wEbR5aSZ%zgk7z3c3w1f9XmjNE8ag?7LW;u6`QTGm4*X2cq!2>?r3UP9s9ojd_n&ly3U_y2ye0me5?*uNTX*?=h4eaue>bPZK_E)x zbr=}eXo5psnyqeHNVQlRE1zLqwRbF_D^=icO)w+^)!2BWI1a|7!gV=5m6`+Sgtw<} zEcVS2AA_Nt;43FHK^r*F9RCTN*I(IY&DU|^=YGqeYsK6eQ?v-cvta0GF9%)*RTGh zt}ka=4?dfDUgQ%yNMCj~YxyJ~Axww71@)@$@}PA3Fr6egWk(q0lx&s>xxh^#=tJ_m z=aKKNFHx{^kREL`6bY&L8Z9Bs{x#oz?)7Gq`{Vlbm^vsqRFuX;n#bCq%geIk{gCDI zW3k`#F)xRoTnoPc)l4jVo54t+fjNhctG_jX|tDKV23)}99hZR$pqx6}v)($ys*o3r52T;rz zEZUCneI~?P4DtQu`(yoP_#Gd+0(xT7@_aqTn($4N()Rn+=hH9yj#~-m4g}_S|MTc@E)t}b&PDPb_q>fN{{}^BthAT@fqWN zyj|dRmW_%f@q~i1DrNcX;lY0UUajxGbz8I$g9S@f2-0l|9d?SMH_ESzWVb9DqS#Wq zPAyOBIU(?H(!;e9w;D8Oa)RDrF>4{FkJvMAI-~ELa4G`ceIS6C ztvAtm+4ZJY$lh&RrSTL74Xt4povoBZFl<==X(bj#-wDfV_4%5O>nZc{Z`A2mpj(H_ z*7EV4{h{KI+VDeM52Vu+I z#2n!o2;sm0#S_7BpFgIne{_W+hxz508EA?}&)CPOZHGVrQ8hsHaaf4rO}zIiLk9cK z{QFhdX~p&(4o#{CgN2Qu5`qaHPZJb-Z@A} z$&k%M^SbCSo!8%zaR(rGMcT*YY}b%cccB-t7+G%+>Bsg|`GZh@7!N*3<1;`FtEzNg zACWV6;_r^=jf>FLx-U*&e_DqXf$e3;hW`(mug#2QrXw_rvvksp(v9#uI#VlH$(~~P zVzYDs?*};n^S2B)mP@9nC^6J6BG9*o@=;&X-}m>}ahf#>Avdu`uCMF3{VPv845vrCon4kQ!pKn=rK(lm$u^3;WNpwP*~5SP~x6t@Y%Y<8teAs!3g((=kVkMtF{hf6bJo-iTbKO%|e`G-&|>cEWq0sJuY@^K^Pa38R}E zaPaoFNze_PyLS#w*h}_C7JRsE@_%odn6DQG`8uLlb)HgE!=HHFSA)U;ed$x##7pI8 z7FxDwAhDhz3tr4h9TO{`9QR_7m4*TmKWu_MGlVd#5m2Umk_)h5)Aq^guS4-ykI%XP zTP_Y(jFv}dHTdJh@ZPtTmP6#Vhq%SZ+t{f?7lXjsgAmqK_Kpi^DHtFZzNV2 z)QJ$(R>Db+RnJv*X3t$lhR_`JIkG7%q~^fc>?dKSo9ti2R_-2(jL zwcKTIYO4j_v?*F7+lv2rXNeQSXgHS6=-S13{_Zvb%TZ?q{Ks#zIFWVfAP0b)v)l%{ z+Q6a~H(PXRBxkub=QfKYqk?`axq^-2=Wnc|2k$e$W&I+L#dNIrfpv^ZvQZv*rz+@y zoeU2q+@J%_#(XV*YodPCgk>Ff6tQ#HGwF?RYSv)3(u-)zZP(CWa}8uwuaG?ZWZx41 z-$V;_tCAC3ap|MpcYg#PJN}%n0>T{?X@HKE>ERu9UFbCM3Rfvu%8E%P&l`V-d=}j* z+sGjLJ^EpM02f-PKu;b3Z=t(F#?IDNx$o`V{O7ql=RyYl+Z&Y*9Ul-t642xk@Mh`J zU=RQC;`HwR19|ioqQOmlk!jTM=3|LnmQ7owB~|40<-B+(Yn-9EdV`G526-IT?!ExAuv+$1&t!AZs zF5(nSqqfO#z!6zLYBtbmxwg#3~bsKPz>Z@aWOF zx^m^Of;zLj&8}{gDpE2bl_}g7v1PBUu|S-GkfPNalVkQ$Qch=z_W_n|gKA}P+A-WL zBB=V;UAa6s*EfaA5JBQ=)$fw3jWY$csWZ1DVr;3|hd5_KYx_NhZ{$Q@q)H5WLPt;` z+fSWh#{HV78N@t*tr_s@s&w&S-r9bE(s83`{V<=SWa^xHQS_j$Dm%h<#;;i51W|EE ze08OZ>34(vl(j1CGjTGHHtlLrN$eS2UE;5JieE$uh+{s5v7j@vA!x@Cm-mDcy=C|I z+a!PWLUx)qnyhpAR%tY2&iXQ8w%zwW;?@&5qJg-}-^c)usl33Jyn_Hzw=PiaI#6Et z_Srr5FiiW_CH64Ek`f}kXPn|SRnTTZ>pF?VxsphOUlDyro^K=H&GkW|ZJjQv*j_*r z3v9P(87I+Fg=;3%;VLW==|8^Fzod4WCYNRNKdLa)00dx#b+~Z5NdH!RQ6s3aI*?wBB@7c7B!PLDSCXd0S31xkC`p5I>VjN zaTg(N(N#3UQ`;{XkcMVjJ55h8s}ottkP{is%LTVU_Vq*M!=``Z{VG>;6t^|kK;nfM z(>PxD6?jY$x46y@c+uk>8l`cZ`e|rOouCXw6m8G;y8@T{bl1>DfHrXC>z;$ia zhRmPKONFup5L0!zH?wLox3fCONv{L6OHGvs4Tb(}HNtUA+sk#@zw3#LF?VF&#hbk! zJ1)2|u^4_6@sl~Z^>w7OLv_^VPgb`XX^qWNmsbp>ex5-G`T~ycZYP(PQjN%}YWbo+ zDPjS=kk4rf+1s8$A#b98iK`6@@tj_+VYg7o+fN?qgM;oRZyI;t*QFkukw>edfn_{r zT@cMQ3#M;>?bojhj)Iu{afBVx#0`~_HeOCEB|2azJ%63Ofop; z>+bRw8>YCLONggZBHK%hLFGlN*hqNY&V&(Zn;%2dsTOiGi{qSPVaul#R!93C&jo4~2%9rK*$=3uR ziB?hxS-?fPjFDAN=ygxfRYgKob^nxos;^DmW(wz|sArQt_$VoXC(I}@rZ_V7bDG#r z{haCR-f3tNmGu&KZE7gS>}u-ht0C@~aa-e0Zb){tK9Fz~WXUp>5Sd|K?*w}g%x;9*8Vk%nv`7zg z;CG8j1s)+dRMiHAY=*X$oOs5giW!9}pE#Oh{dSwf1ODmRfpmMKXrz?%^a_7&UERx; z<_F$iXD{9HC|rr^l^Zl#wk18uH>{%cH8Fx(veLOb@#b+!*gtTc-F2DyA&{R`mMIMQ z@ntC2I`48B+P3h2)%KO~0+)nlB2_!HK#=+33vFU5bREotQ5|h1}kr zq?!6u6Z!k=8f({D9GWzkJZKZo(P-Duc7xh5|0-!yND-i4IjShVJh zc>TbVzWP~LHbb5Zc0Qj80Gh*JruhPjSXvWlML`p&q8Q8_6r^z5S}H}vN*s)XqxzAS zLQJjC6s;&rYUlYZC4S7Qtd4^}l9UvIj2ek|27$qAt82L|fs5yIJ`u4j-;V};Pm2;C zHj8tms`EkfUw%|c+iC0-sZ2l<0%k`-Kht**-ud|hF_(5n(1-j(`E1;KxjHz89gNsU z$tGl8lNpd129vV^i84X^j)%XXZG7iGP!eyQddai_UB{SG9R`=mg_b`#!D9G=64=yZ z{`fzm!`33~M@u!jt+vo7;ZWVITjwq-Y4(2VLnr0i#Aez%YpQbEB>fU*c0i_5Jgr)W zv$A7N+H467g0-drys1?>-NEXBn9@>Gyf|z})y64v04Q#w-6@Ribs3(Qs4O~JT$*8; zb}`_uhrvQD_5h@;91lGhgEVf0%19mA^ih0-=!el8^aa}avxYTR{6T(dhnDA6!?nQE zlYw4TP11JmqK7}@caDo29$IlSe@*zc?o@b?@cP4nvygg2uyTMua|)kqMq1E;qoTt{b&|1NU+sn_oOZ47;T9pNI3VQet`bG4dwkW8ppMn|5MJ7;f;Y;PmP?8ir-_R75HKF!WQc^2R~ zSBWm8Wtw713-wGTZ=$2bH1`#Jm@GLtX2c*iT1A?Rp6LB(keY95U?T(a$L||5lspoX zvB&Vx8Q2<0{?LfNI}GD`E+d0=7}@JOV+MR@dLy1qlzf(y(y9fV!t-6~kDlyxiW=N` zjeqgPqXj@7!_WL<0-Y6c?G>7I3=smo;j2@NsHkxV$>%>l{UD@@^*R)~B}sd-(m=vU z5c(XhK#D7by0#8?-yFG7UC^mC+N91$iU2zYR!S zgm}{ye?d^kMUqaHuSn0UV2NRVp;`fg$qpo9@|@ZG$rW7i-+~aH#c4i{`vCUd(jD7EQ3-pR%p*p(P0S5=z?*rTx?H z(|U$y+b*+N14^CB-d!ikdN$BA%L2OBTQK+2;Z#};Az8^Ut-&VOH7jKA^jKn8O~pE2 zMbB~KQ`eQ&?1VG@!`@|n9ncI1H?CFZQ4;<4sNb@hR&;#y_ilMJUnxF+r-JZpJ;aga z_rDi*hZ z$$yHCBIPI?)lW%&kyO!qWw(L#+4y;Kgadaj*|GOd>;MiomKaLkovceUsiUv!lK?QK znb@X`HBY2@ClS?`Z5dIiP&{3H8low6b20W^KCN5rD8Nf=h>pAr=)L!IiX}gY;+=2 zC_|9BrHzhCx(W`O$e7puHs?R@jkiAyf?2Bu9t_AkO-7+4@)3k8HZ98AWoUrHCCu)N z@u!z6Q)O1u$G%@z8a!IYBl zD&2FcmA29pfN3M~Ec}cQKsa?D9nQI?(iThMq$N~tUH#XOv<35*e?N^vgubbY^)U~? zK)UY9TXnbkhX5+~0RPQU$|M5a;er14_y*ZR136^GEaycH^3;t7S<<}tFs2b=&^OZI z(*ChALHxG_LFkf&zoC^v2woty`b||;j^RCh;!uI4i8%wiu8}mOg2(=|=LaxoJQtg&=)(HS<*M#P}3>;yK zrx0ti1TSsB8R>!V^YOoj%g{c4DjJqOUHXkwMi=Lz*70#`j>u9`Po*R`H8ji=)NTOF z)ALnVdRVbZu)er5x>o+-Yb~;aq=9}&s(Wcsiw>a*i(612#2{Sd!FtReYfZymcEB17 z11~B#($WOt#Abi9M1c`;k&@<#!;pbAP1_rbeqh-eg(up{)>Q4z&%tlx9+lmL7vjrd z*#T32C~FVC#|^qDAnc(Uj8+qwe#(`%s{PBAYa8ktavv|zarz?x{BlP*>h4oroLREg z6t}1sfrz;G*?B{F)Ct@c&nDPTk$XbnqrB#stF2wi7TIY0_zK1C>s=zA_-r|C}zpV8XqG|!O7uc+uJm$9VSPG<_Bm(zDuc&lf;Qn%FZc{*q9QH!_59e zVAkSU@G5NfMu^$h+yh^P zbt@Y)05I$nu_=>p4x?ngUKve(5^Jx$H>{%P5X~n0$A2bu#Ni<>Jk@MKMH71*z~rcbyy6ZHuukAUin*xv$h7< zmQ@A1mpvpdPa!?*{`aqgiS0z>3HzeHw~*2x&a_l9Psr+BFpxglfrNvGux*=kfDDMEzz=A&xEEQ)(9g zjhp{7T;xVkWrTJ@O zy6)J7k!yP|D0p2UvA8AYrRK(7*?(G94#ffTjCxYS7Qn~#(Q!5 zF}k~q*vLIXon3E#y}@S#Fre{IV&X25&S@sB20AY=4@okOTgkFdb6v;Apet#5Xz{~zoX?9sp+A8xW((4tMk?>2J!P9F* zT%Pn>ON#BzpKSUGUAbB+xr+?y(oVheDdyVDFJEU}+DGtC35Ki!MlKNYvuhg10zExy z<{ltl1iUw2Sl6$@uNTrVwm!x&jen2{?=_2ZBMcE@s7dGfD}1Nuu9R6LuvdA&=qSh? z8K1&!R&eLr<3cPG>HD@?+M6RWwl4vY&?amgtNZDGTg=n9Ro6V0?s2QLM%q6}>c5M~ zCUA;)+NnU+bE(&6HZRz6GY{mJUHETF*5`*g1X~NXn}>0S{`5HpHaUk9H?yv_?#y8p zib4#Y5r;5i*8P&dlI5-0V?f0Qd%4}G!T`rOeOh^In+Z%4S7`5XxZ37x_|ty#f6FPW zC1}U2f7ix-=CzE_B`Lx8U%JON4%CrAOJ=UG*%PSQH=PiGJ^*&eCW#X`--P7I%cBK} z6(k0)8{v3FK@GA=siM^kd5c+Y{eP5A!Rxe6;D&sY708R*)0Tl%5Z`Os^h-W|Jar;_ zasaEpXSQ8G<A%%oIhhyZi57&U-%MO>v4?!inx>r_%K7j6HN9#|`C+2xh5j3fRc=YYdmF+~2Ip zrFf=!j~>eEPN}$4{wz`L;=Yi)P8JfYI1yIHqC2KEX2xKGEhaw#aqZsLZf?VmehY0s zUnv<)>h$LiJ$IQ$N}^BtT3hQ(&9jbo?ic0EVyiJwPLF$=Aj)Nf6SK|na-U-&ubdRX85W zGWN_b#l=KMd4LXorR?VAK*vf=K+-pTQCK;2^(_*vhnjD5stl5)%W$Vxu(&1iY$#;59+~gZb(y7-Q7zfP5=1dyMoPIg#y7mK~#iE-Dy1;p( z2_GU~?C`k;>_-jseXyvUcaJ$Ka@#zqeqnHlFRA2gqH1t$Z3@tt>cR`fgG=6xnsZmQ z94C3F)QMssywTTnCuNA(%xq{q=>T)?hE6m!H?+hh8A_$VwDp9m z@9w{A{_9He-@w{{OkM`wbt2w8I~meUPq*@_6MjbmsQ$s@)IkY7rfSu$Tuv*YqjC5} zcfgq;iIyqOg`yI9ZU3|66=IIAP_7+maBK*=s*q^pz$F>REe^uPZCg&c$_4(|4zSpUT=v|q z-Qt&;K51JhUqKkkz`U9R{Yt}aC7rQ)5BdwCdn8i`etS*H=Gs1$X2+2-@CN>$!N z7l=>zQeLlmm+Kr|d#|(T&qX)i%;cFB!ZJ7_wRD@B4+4M~?8vVy+1dculle4BkNH=O`WTitp;*W1bMPFJoR$ivARHrS~#w}Gw zFP4T;3quxu^Q2^=KR;6L#Yh z;eW!blinvOm1(@fl%M1{Mtls-H+t*R2nz5Yi-1jX0`u?ixY}{{xWb9^I0ckCJ_Qs6 z4xa1*+sPid2t4WvR6f4+WCMsm13;@lsYCNoOMqzF84TzK9f3e^7l!Q+Z?TYGSG9I8 zu^wlUZYREO`>SneiB>0(7F)g+YiW{$aE&EjjTyAmR;t8Wtk?qn#7g?H1vtwI zlAdg&kyM19c=!!)m==_53yAO+sN@-LwT)b zWDFH04duWFP(ghu33VAsb(pxCw6N+GIXO8#6;a4lh$viK1a?_MR8#~kBnal`72)OK zFVfB*1Y%x49CU~(`D{QG!|T}MEwS$SAQv1OWbERK!+N9LFfPKn7#unX zi}UmsHntU(g~R3J1FQN$pwk@s+FDk@Ija-2dqdj%jO%zXSmKnVERQA6*PeAlrCNze z-y4Y^`((=$$yqrUiNvB3XrbXMgG{bl&2ZgNy+eb>h+#v?E5pH0jG3H1{`2!M>

^WoaK47S{#9|g7{1p_)9*U8cM)(!2s=5_O38}d6+I)Y0{`^6IJ zEEKuaDS8)O?(PE<@;MUAyAYwKd*e|o3yP%WQuVYC$+C?w6}+qS12*@3TcUQJiywsB zpvMUQJEL8{v+ExaLhzgBi|6S5%#CQT(_e~q&aGFb${zHp$DtzWkOp6O8H@MkJL^TB z)Frr9d&ghjIE zq?qedOxfdKpr#+%_LNy~b~(z_HOB-#Ev@*rztxGzWsfsoNR|(dXAtykD2#7tutC!c z6h*OFqn&0@h4n8`?;$*PaiyC%(lgdvf)(gx0SBKn1yjF}(6*ZP=nC($YG^k^INM$3 z+{1$AvT7_DXPkvGp^)ASqt_Nhg(k-#Z^>y=j*o&w5`4wZMZZtab(&(1{>jtI@{nu z*|f*hp{f=BU)Mx0B_ zyRTxbbr>~6p*D!bj3?zoZ%KQ}i>hn56eK#6El?)m;HL;AbjqEwbXUr=F*#s&{%KJS zHja11>y>q00`=^#b4Bp)A$qghCRNn=fZF>hEW5*9+XU8_V99`m1)=bmUCN)>glpcSd<-Ba|wN3&3JE=!uV+1*en~l`<2T>jT0Z`zDBwRGJX} z_fplGGFCoJ+fHjyXEB0bjSIYm0;B4}VPgZ9oKU4m!KOg(7CT2NweXC7+V0MP2anob zO0$9*g5ICX!4aR-?ylq5%#tJ!c7EvQa&JK`q71_2+NYs+I_(*;H9!9X5&N31%40Nu zS0mOnBM8nP6tk5OdLb)`Sm@KJ37RM^*J8e;J4N&`_RSuom<*_U8DQkh6lBoYfj2K5 zM=B;Xdef5{+xfqP`y?jUSbM(WN>r4jGb{;L_NhH|sz1!Rvia~ownq9tU)LDR;&Hr# zWpM5qYG?gIn~3UZ{c{C}651!ZmdKS0_$L@I4Z`g1GK{&k?Me*~NzbkP-fB-+oPp|^ zG?8&A(@_mZXC}4fM3%Wyp1bFKlVX(-KQ?(|OdF|hSRF4H6t=Iuu>bLEHC~=fW~&Gb z{h8XrFgx3_|IYM_?brmvE#tKTA)kqDq%TA6=O0Gvs_m7knK{>jHkMKx?7=qg^qj&F k5!B72Hwt*Hc3bCf;d(8Reh~Ie;5h~9>zHU)Bb=lE2K%K6(f|Me literal 0 HcmV?d00001 diff --git a/Xtensions/WebContent/files/images/brands/youtube-logo.png b/Xtensions/WebContent/files/images/brands/youtube-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2840c18a647d6aa1878075a8018eac2f7c8d87bb GIT binary patch literal 1157 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P3?%t>9eV(zn2Vh}LpV4%Za?$~av2o@d_r9R zL%{Rr|F2&CzkdDynKS=4H~%jx`oC-$h~3o%;?~rFMAFj!$He^i^7?OV3>9&5`fp_g z;%aF8S5yQE0@VOTAY4gF5Erf{CCjo;lp(MyJ zm_ZN%l;FS^0$ji#6bRBlpd1J~z+fg2tOA4GyLX>CbLQ^dyYJq;gN8WS? z-v|XG0|S%xvNh)!7?|QcT^vIy=DfX`U3|$w|4ox`#?N#%+Ym8L#d8u2IV|nY-fq6i^!Clt z*z=!j?Kyr2&RH^flEtfb$A8DSJ>RvvZ|~l%fsxwNpQ%jGyApjjZie<_9{vCEhvJrB z-Lcz5IXdr{$a|m1J1SGfzJHuudF9bj^B3|x=zl$;90w86&ySnD)gu4 z^w~|*RV8y;H3Id=YMsl_=}o5ab^J!1q{l zB7eiws_(olwGw&G8=Tx_4{YE+$M{HhY2yO_8Bz~j&rs@FR17j ze)nMA*2A3JIXFVdQ&MBb@0KpkMP5=M^ literal 0 HcmV?d00001 diff --git a/Xtensions/WebContent/files/images/cards/music_default.png b/Xtensions/WebContent/files/images/cards/music_default.png new file mode 100644 index 0000000000000000000000000000000000000000..ce545cee97112b384b17bc20b3924d028d5f1107 GIT binary patch literal 1443 zcma)6`7;{`6pqG`(3DW(7FSW~imQ~i*`QfhoDFp(b%bpRL1kQRmxQ9IR^3*$nA*@F z?z5;{+ie9cag;`N-Dl9QjelTg-@NaA^WJh&Kd1+~B6bdCKCMGg6G9Vzp8|Ulp?vBA=Y;0_hNTjK$DFT7e z(9nRx;j(ZQDJdzgH53Z{lsVqTUFkxElf5$ls)RrVL?!1{wvMpYxVHZleE9N{3&Ek+ z+`|C?!HpB}AZZ4<+@WfOB{ssD7!VPKC;0>H@V+Esu)kjr9&Qyx@{b~tZiU0`uyB1{ zU4x`Mw08giFB*-y>OwADomwW9UJ(J{gs-3+UDEgs+3$PBXO9oYh5XYk_^CyoD|cpj zJEYm6--0}QLlU->>@j&Q{VUDww0XsL>;|adR9CP59{Td(6d9({quG5x`bvu8mBF0 zchbfsC?X0oJGy;;lf<6*#v=7ng;FT}o{57CAu>8r*u)jSHgo!Nv01*x)Sc$qxEwX( zdD3ElCv`;jU4_#|H7G0xan~Uq=Sh7_=*${oJTj!&&>qu=YJABo`LUbmXiQbYMQueO zYlTVB@aRNz?a9Q#vXe}W={;W}N?PQ|sO>ik`)lF*2?I4P`-%HCFB*;~{e};;q*%(o zELz<4SP;b---i!%YX;{s zvgX#@wRnUsJIe(m*x@kR!nXr!p74)-08tmWy1}D(us8PpLWR)J)5FdKBGkntVFaD5 z9-c!UTNsR0>bmy>i~2mm|A%*^r@oz!_jRJeQP08Uaaj}Mf`doc@P^-0?ONvE_KhDI zwuwB=qYRHk`%iA1_>Q>MF_^C3U(*upc3Upwg=$W*0 zk1aVy&vs%ia2l-W-W{v$laL01r2&VqIy#{?b}}(86r8j$P6_WT@tjz_&Q1pOC9F(96?iwy6N?X>eucfO=a}>GFL33;oQ*INVlz(7!qeH7M znAJ}+AM4EsBRb|V6&Ah-OfW{9rPYIc$DLeVAEl)6tVe?hKD-iYnO`M$Pigey&X$aU z7|CyM4i?Iyz(vf`W|D}d)!I;Hp?FraW>NTct#8$rK6qUX9Cioa9`^d{RjdBe)4J}p zr>#zV;k=P4`}IefAx(##&%UkzA8M|$A3w1%IRq_9hgg`kP@fA|h0HI0thFY`!{cpX e2FH3uZ#Y77gp?Uh9Nw7wg#c(vdsH InterpretationStep.getDirectCommand(input)); //response to previous input @@ -371,8 +373,21 @@ public static void setupChats(){ //------other tools------ - //RSS feed reader - public static RssFeedReader rssReader = new RssFeedReader(); + //RSS feed reader, Spotify API, etc. ... + public static RssFeedReader rssReader; + public static SpotifyApi spotifyApi; + + /** + * Setup tools like RssFeedReader or SpotifyApi. + */ + public static void setupTools(){ + //RSS + rssReader = new RssFeedReader(); + //Spotify + if (Is.notNullOrEmpty(spotify_client_id) && Is.notNullOrEmpty(spotify_client_secret)){ + spotifyApi = new SpotifyApi(spotify_client_id, spotify_client_secret); + } + } //----------helpers---------- diff --git a/src/main/java/net/b07z/sepia/server/assist/server/Start.java b/src/main/java/net/b07z/sepia/server/assist/server/Start.java index 61135c96..e4eb976b 100644 --- a/src/main/java/net/b07z/sepia/server/assist/server/Start.java +++ b/src/main/java/net/b07z/sepia/server/assist/server/Start.java @@ -209,7 +209,8 @@ public static void setupModules(){ Config.setupAnswers(); //answers Config.setupCommands(); //predefined commands Config.setupChats(); //predefined chats - Config.setup_nlu_steps(); //interpretation chain + Config.setupNluSteps(); //interpretation chain + Config.setupTools(); //tools like RssFeedReader or SpotifyApi Workers.setupWorkers(); //setup and start selected workers if (Config.connectToWebSocket){ Clients.setupSocketMessenger(); //setup webSocket messenger and connect diff --git a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java index ed04caa5..f4ffe7f9 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java @@ -1,23 +1,32 @@ package net.b07z.sepia.server.assist.services; +import java.net.URLEncoder; import java.util.TreeSet; +import org.json.simple.JSONArray; import org.json.simple.JSONObject; +import net.b07z.sepia.server.assist.assistant.CmdBuilder; +import net.b07z.sepia.server.assist.data.Card; +import net.b07z.sepia.server.assist.data.Card.ElementType; import net.b07z.sepia.server.assist.data.Parameter; import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.interviews.InterviewData; import net.b07z.sepia.server.assist.parameters.ClientFunction; +import net.b07z.sepia.server.assist.parameters.MusicService; +import net.b07z.sepia.server.assist.server.Config; import net.b07z.sepia.server.assist.services.ServiceBuilder; import net.b07z.sepia.server.assist.services.ServiceInfo; import net.b07z.sepia.server.assist.services.ServiceInterface; import net.b07z.sepia.server.assist.services.ServiceResult; +import net.b07z.sepia.server.assist.tools.SpotifyApi; import net.b07z.sepia.server.assist.services.ServiceInfo.Content; import net.b07z.sepia.server.assist.services.ServiceInfo.Type; import net.b07z.sepia.server.core.assistant.ACTIONS; import net.b07z.sepia.server.core.assistant.CLIENTS; +import net.b07z.sepia.server.core.assistant.CLIENTS.Platform; import net.b07z.sepia.server.core.assistant.CMD; import net.b07z.sepia.server.core.assistant.PARAMETERS; -import net.b07z.sepia.server.core.assistant.CLIENTS.Platform; import net.b07z.sepia.server.core.data.Language; import net.b07z.sepia.server.core.tools.Debugger; import net.b07z.sepia.server.core.tools.Is; @@ -77,6 +86,7 @@ public ServiceInfo getInfo(String language) { .addCustomAnswer("what_song", "music_ask_1a") .addCustomAnswer("what_artist", "music_ask_1b") .addCustomAnswer("what_playlist", "music_ask_1c") + .addCustomAnswer("no_music_match", "music_0b") ; return info; @@ -91,6 +101,9 @@ public ServiceResult getResult(NluResult nluResult) { //get parameters Parameter serviceP = nluResult.getOptionalParameter(PARAMETERS.MUSIC_SERVICE, ""); String service = serviceP.getValueAsString().replaceAll("^<|>$", "").trim(); + String serviceLocal = (String) serviceP.getDataFieldOrDefault(InterviewData.VALUE_LOCAL); + + boolean isSpotifyService = service.equals(MusicService.Service.spotify.name()) || service.equals(MusicService.Service.spotify_link.name()); Parameter genreP = nluResult.getOptionalParameter(PARAMETERS.MUSIC_GENRE, ""); String genre = genreP.getValueAsString(); @@ -141,7 +154,146 @@ public ServiceResult getResult(NluResult nluResult) { } } - //This service basically cannot fail here ... only inside client + //Check platform + Platform platform = CLIENTS.getPlatform(nluResult.input.clientInfo); + /* + if (platform.equals(Platform.browser)){ + + }else if (platform.equals(Platform.android)){ + + }else if (platform.equals(Platform.ios)){ + + }else if (platform.equals(Platform.windows)){ + + } + */ + + //Basically this service cannot fail here ... only inside client ... but we'll also try to get some more data: + + String foundTrack = ""; + String foundArtist = ""; + String foundAlbum = ""; + String foundPlaylist = ""; + String foundUri = ""; + String foundType = ""; + String cardTitle = ""; + String cardSubtitle = ""; + String cardIconUrl = Config.urlWebImages + "cards/music_default.png"; + String cardBrand = "default"; + + //Use YouTube for now + if (service.equals(MusicService.Service.youtube.name()) || (service.isEmpty() && Config.spotifyApi == null)){ + //Icon + cardIconUrl = Config.urlWebImages + "brands/youtube-logo.png"; + cardBrand = "YouTube"; + cardSubtitle = "YouTube Search"; + //Search + String q = ""; + try{ + if (Is.notNullOrEmpty(playlistName)){ + q = URLEncoder.encode(playlistName + " playlist", "UTF-8"); + cardTitle = "Playlist: " + playlistName; + + }else if (Is.notNullOrEmpty(song) && Is.notNullOrEmpty(album)){ + q = URLEncoder.encode(song + ", " + album + " album", "UTF-8"); + cardTitle = "Song: " + song + ", Album: " + album; + + }else if (Is.notNullOrEmpty(song) && Is.notNullOrEmpty(artist)){ + q = URLEncoder.encode(song + ", " + artist, "UTF-8"); + cardTitle = "Song: " + song + ", Artist: " + artist; + + }else if (Is.notNullOrEmpty(album)){ + if (Is.notNullOrEmpty(artist)){ + q = URLEncoder.encode(artist + ", " + album + " album", "UTF-8"); + cardTitle = "Artist: " + artist + ", Album: " + album; + }else{ + q = URLEncoder.encode(album + " album", "UTF-8"); + cardTitle = "Album: " + album; + } + + }else if (Is.notNullOrEmpty(artist)){ + q = URLEncoder.encode(artist + " playlist", "UTF-8"); + cardTitle = "Playlist: " + artist; + + }else if (Is.notNullOrEmpty(genre)){ + q = URLEncoder.encode(genre + " playlist", "UTF-8"); + cardTitle = "Playlist: " + genre; + + }else if (Is.notNullOrEmpty(song)){ + q = URLEncoder.encode(song, "UTF-8"); + cardTitle = "Q: " + song; + } + }catch (Exception e){ + //ignore + } + if (!q.isEmpty()){ + foundUri = "https://www.youtube.com/results?search_query=" + q; + } + + //Spotify API (currently used when service is Spotify or platform can only handle URIs) + }else if (isSpotifyService || (service.isEmpty() && Config.spotifyApi != null)){ + //we need the API (in early version it was possible to call it without registration) + if (Config.spotifyApi != null){ + //Icon + cardIconUrl = Config.urlWebImages + "brands/spotify-logo.png"; + cardBrand = "Spotify"; + //Search + JSONObject spotifyBestItem = Config.spotifyApi.searchBestItem(song, artist, album, playlistName, genre); + foundUri = JSON.getString(spotifyBestItem, "uri"); + foundType = JSON.getString(spotifyBestItem, "type"); + //get URI and build Card data + if (Is.notNullOrEmpty(foundUri)){ + //get info by type + if (foundType.equals(SpotifyApi.TYPE_TRACK)){ + foundTrack = JSON.getString(spotifyBestItem, "name"); + foundArtist = JSON.getString(spotifyBestItem, "primary_artist"); + foundAlbum = JSON.getString(spotifyBestItem, "album"); + cardTitle = "Song: " + foundTrack; + cardSubtitle = (foundAlbum.isEmpty())? foundArtist : (foundArtist + ", " + foundAlbum); + //add play tag to URI + foundUri = foundUri + ":play"; + + }else if (foundType.equals(SpotifyApi.TYPE_ALBUM)){ + foundArtist = JSON.getString(spotifyBestItem, "primary_artist"); + foundAlbum = JSON.getString(spotifyBestItem, "name"); + cardTitle = "Album: " + foundAlbum; + cardSubtitle = foundArtist; + //add play tag to URI + foundUri = foundUri + ":play"; + + }else if (foundType.equals(SpotifyApi.TYPE_ARTIST)){ + foundArtist = JSON.getString(spotifyBestItem, "name"); + JSONArray genres = JSON.getJArray(spotifyBestItem, "genres"); + String genresString = ""; + if (Is.notNullOrEmpty(genres)){ + for (int i=0; i 0){ + cardSubtitle = "By: " + owner + ", Tracks: " + tracks; + }else if (owner.isEmpty() && tracks > 0){ + cardSubtitle = "Tracks: " + tracks; + }else if (!owner.isEmpty()){ + cardSubtitle = "By: " + owner; + } + //add play tag to URI + //foundUri = foundUri + ":play"; //not supported? breaks link? + } + } + } + } //If we have only a song we should declare the 'search' field and not rely on song-name boolean hasOnlySong = Is.notNullOrEmpty(song) && @@ -150,52 +302,64 @@ public ServiceResult getResult(NluResult nluResult) { String controlFun = ClientFunction.Type.searchForMusic.name(); JSONObject controlData = JSON.make( + /* "artist", artist, "song", song, "album", album, "playlist", playlistName, + */ + "artist", (foundArtist.isEmpty())? artist : foundArtist, + "song", (foundTrack.isEmpty())? song : foundTrack, + "album", (foundAlbum.isEmpty())? album : foundAlbum, + "playlist", (foundPlaylist.isEmpty())? playlistName : foundPlaylist, "service", service ); JSON.put(controlData, "genre", genre); JSON.put(controlData, "search", search); + if (Is.notNullOrEmpty(foundUri)){ + JSON.put(controlData, "uri", foundUri); + } api.addAction(ACTIONS.CLIENT_CONTROL_FUN); api.putActionInfo("fun", controlFun); api.putActionInfo("controlData", controlData); - //Check platform - Platform platform = CLIENTS.getPlatform(nluResult.input.clientInfo); - if (platform.equals(Platform.browser)){ - - }else if (platform.equals(Platform.android)){ - - }else if (platform.equals(Platform.ios)){ - - }else if (platform.equals(Platform.windows)){ - - } - //some buttons - we use the custom function button but the client needs to parse the string itself! - if (Is.nullOrEmpty(service)){ - //simple action button - api.addAction(ACTIONS.BUTTON_CUSTOM_FUN); - api.putActionInfo("fun", "controlFun;;" + controlFun + ";;" + controlData.toJSONString()); - api.putActionInfo("title", "Button"); - - }else{ - //Link service? - //Cards - /* Cards should be generated by client ... + + //simple action button + api.addAction(ACTIONS.BUTTON_CUSTOM_FUN); + api.putActionInfo("fun", "controlFun;;" + controlFun + ";;" + controlData.toJSONString()); + api.putActionInfo("title", Is.notNullOrEmpty(serviceLocal)? serviceLocal : "Button"); + + //Cards (or web-search?) + if (Is.notNullOrEmpty(foundUri)){ Card card = new Card(Card.TYPE_SINGLE); + //JSONObject linkCard = card.addElement(ElementType.link, - JSON.make("title", "S.E.P.I.A." + ":", "desc", "Client Controls"), + JSON.make( + "title", cardTitle, + "desc", cardSubtitle, + "type", "musicSearch", + "brand", cardBrand + ), null, null, "", - "https://sepia-framework.github.io/", - "https://sepia-framework.github.io/img/icon.png", + foundUri, + cardIconUrl, null, null); - //JSON.put(linkCard, "imageBackground", "transparent"); //use any CSS background option you wish + //JSON.put(linkCard, "imageBackground", "#f0f0f0"); //use any CSS background option you wish api.addCard(card.getJSON()); - */ + }else{ + //web-search action + api.addAction(ACTIONS.BUTTON_CMD); + api.putActionInfo("title", "Web Search"); + api.putActionInfo("info", "direct_cmd"); + api.putActionInfo("cmd", CmdBuilder.getWebSearch(nluResult.input.textRaw)); + api.putActionInfo("options", JSON.make(ACTIONS.SKIP_TTS, true)); + + //can we still search via Android Intent? + if (!platform.equals(Platform.android)){ + api.setCustomAnswer("music_0b"); + } } //all good diff --git a/src/main/java/net/b07z/sepia/server/assist/services/Wikipedia.java b/src/main/java/net/b07z/sepia/server/assist/services/Wikipedia.java index 5b395505..4d097807 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/Wikipedia.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/Wikipedia.java @@ -87,7 +87,7 @@ public ServiceResult getResult(NluResult nluResult){ api.hasInfo = false; api.hasCard = false; api.addAction(ACTIONS.BUTTON_CMD); - api.putActionInfo("title", "Websearch"); + api.putActionInfo("title", "Web Search"); api.putActionInfo("info", "direct_cmd"); api.putActionInfo("cmd", CmdBuilder.getWebSearch(search)); api.putActionInfo("options", JSON.make(ACTIONS.SKIP_TTS, true)); @@ -409,7 +409,7 @@ public ServiceResult getResult(NluResult nluResult){ api.hasInfo = false; api.hasCard = false; api.addAction(ACTIONS.BUTTON_CMD); - api.putActionInfo("title", "Websearch"); + api.putActionInfo("title", "Web Search"); api.putActionInfo("info", "direct_cmd"); api.putActionInfo("cmd", CmdBuilder.getWebSearch(search)); api.putActionInfo("options", JSON.make(ACTIONS.SKIP_TTS, true)); diff --git a/src/main/java/net/b07z/sepia/server/assist/tools/SpotifyApi.java b/src/main/java/net/b07z/sepia/server/assist/tools/SpotifyApi.java index f7c0d5cd..bf848e30 100644 --- a/src/main/java/net/b07z/sepia/server/assist/tools/SpotifyApi.java +++ b/src/main/java/net/b07z/sepia/server/assist/tools/SpotifyApi.java @@ -28,6 +28,8 @@ public class SpotifyApi { public String token = ""; public String tokenType = ""; public long tokenValidUntil = 0; + public long lastRefreshTry = 0; + public static final long FAILED_AUTH_TIMEOUT = 1000*60*15; public static final String spotifyAuthUrl = "https://accounts.spotify.com/api/token"; public static final String spotifySearchUrl = "https://api.spotify.com/v1/search"; @@ -65,6 +67,7 @@ public int getTokenViaClientCredentials(){ //Call long tic = System.currentTimeMillis(); + this.lastRefreshTry = tic; JSONObject res = Connectors.httpPOST(spotifyAuthUrl, data, headers); Statistics.addExternalApiHit("SpotifyApi getTokenViaClientCredentials"); Statistics.addExternalApiTime("SpotifyApi getTokenViaClientCredentials", tic); @@ -100,14 +103,21 @@ public int getTokenViaClientCredentials(){ * @return */ public JSONObject searchBestItem(String track, String artist, String album, String playlist, String genre){ - if (Is.nullOrEmpty(this.token)){ - return JSON.make("error", "not authorized", "status", 401); + if (Is.nullOrEmpty(this.token) && (System.currentTimeMillis() - this.lastRefreshTry) < FAILED_AUTH_TIMEOUT ){ + return JSON.make( + "error", "not authorized", + "status", 401 + ); } if ((this.tokenValidUntil - System.currentTimeMillis()) <= 0){ //Try to get new token int refreshCode = getTokenViaClientCredentials(); if (refreshCode > 0){ - return JSON.make("error", "failed to refresh token", "status", 401, "code", refreshCode); + return JSON.make( + "error", "failed to refresh token", + "status", 401, + "code", refreshCode + ); } } //Build request @@ -176,7 +186,20 @@ public JSONObject searchBestItem(String track, String artist, String album, Stri }else if (type.equals(TYPE_TRACK)){ items = JSON.getJArray(res, new String[]{"tracks", "items"}); } - if (Is.notNullOrEmpty(items)){ + if (Is.nullOrEmpty(items)){ + if (Is.notNullOrEmpty(res) && res.containsKey("error")){ + JSONObject error = JSON.getJObject(res, "error"); + JSON.put(error, "type", "error"); + return error; + }else{ + return JSON.make( + "message", "no item found for query", + "query", q, + "type", "no_match", + "status", 200 + ); + } + }else{ JSONObject firstItem = JSON.getJObject(items, 0); if (firstItem != null){ String resType = JSON.getString(firstItem, "type"); @@ -202,8 +225,7 @@ public JSONObject searchBestItem(String track, String artist, String album, Stri "type", TYPE_ARTIST, "name", JSON.getString(firstItem, "name"), "uri", JSON.getString(firstItem, "uri"), - "genres", JSON.getJArray(firstItem, "genres"), - "album", JSON.getObject(firstItem, new String[]{"album", "name"}) + "genres", JSON.getJArray(firstItem, "genres") ); }else if (resType.equals(TYPE_TRACK)){ JSONArray artists = JSON.getJArray(firstItem, "artists"); @@ -219,7 +241,6 @@ public JSONObject searchBestItem(String track, String artist, String album, Stri } } } - return res; }catch (Exception e){ diff --git a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java index 9a785549..0bd47cf6 100644 --- a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java +++ b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java @@ -52,6 +52,7 @@ public static void main(String[] args) { testMusicParameters("Suche auf YouTube nach Metallica", "[, , Metallica, , , ]", lang); testMusicParameters("starte Rockmusik mit VLC player", "[, rock, , , , ]", lang); testMusicParameters("starte Rockmusik via Spotify link", "[, rock, , , , ]", lang); + testMusicParameters("Öffne eine Rock Playlist bitte", "[, Rock, , , Rock, ]", lang); lang = "en"; System.out.println("\n----- en -----"); @@ -80,6 +81,7 @@ public static void main(String[] args) { testMusicParameters("Search YouTube for Metallica", "[, , Metallica, , , ]", lang); testMusicParameters("start Rock music using VLC player", "[, Rock, , , , ]", lang); testMusicParameters("start Rock music via Spotify link", "[, Rock, , , , ]", lang); + testMusicParameters("Search a rock playlist please", "[, rock, , , rock, ]", lang); System.out.println("-----------"); diff --git a/src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java b/src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java index dc0f6dcb..b0b124aa 100644 --- a/src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java +++ b/src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java @@ -45,6 +45,11 @@ public static void main(String[] args) { JSONObject s5 = sapi.searchBestItem("", "", "", "Party", ""); System.out.println(s5.toJSONString()); + + Debugger.sleep(1000); + + JSONObject s6 = sapi.searchBestItem("Foxy Lady", "Jimi Hendrix", "", "", ""); //note the typo in foxy (not foxey) + System.out.println(s6.toJSONString()); } } From 0b6271842ec8cc02f2fd7db6813ea20ea4251b9e Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Sat, 11 May 2019 00:41:58 +0200 Subject: [PATCH 21/27] MusicSearch and PlatformControls tweaks --- .../server/assist/services/MusicSearch.java | 14 +++++++++----- .../assist/services/PlatformControls.java | 3 ++- .../server/assist/tools/Test_SpotifyApi.java | 17 +++++++++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java index f4ffe7f9..728d7868 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java @@ -293,6 +293,9 @@ public ServiceResult getResult(NluResult nluResult) { } } } + + }else if (platform.equals(Platform.android)){ + //TODO: any other options? (can we even reach this code?) } //If we have only a song we should declare the 'search' field and not rely on song-name @@ -320,13 +323,12 @@ public ServiceResult getResult(NluResult nluResult) { JSON.put(controlData, "uri", foundUri); } + //client control action api.addAction(ACTIONS.CLIENT_CONTROL_FUN); api.putActionInfo("fun", controlFun); api.putActionInfo("controlData", controlData); - - //some buttons - we use the custom function button but the client needs to parse the string itself! - - //simple action button + + //... and action button api.addAction(ACTIONS.BUTTON_CUSTOM_FUN); api.putActionInfo("fun", "controlFun;;" + controlFun + ";;" + controlData.toJSONString()); api.putActionInfo("title", Is.notNullOrEmpty(serviceLocal)? serviceLocal : "Button"); @@ -356,10 +358,12 @@ public ServiceResult getResult(NluResult nluResult) { api.putActionInfo("cmd", CmdBuilder.getWebSearch(nluResult.input.textRaw)); api.putActionInfo("options", JSON.make(ACTIONS.SKIP_TTS, true)); - //can we still search via Android Intent? + /* -- we leave this to the client -- if (!platform.equals(Platform.android)){ + //we have no other option to search for music (like e.g. Android Intent) api.setCustomAnswer("music_0b"); } + */ } //all good diff --git a/src/main/java/net/b07z/sepia/server/assist/services/PlatformControls.java b/src/main/java/net/b07z/sepia/server/assist/services/PlatformControls.java index 30445e60..c037a873 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/PlatformControls.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/PlatformControls.java @@ -35,7 +35,8 @@ public class PlatformControls implements ServiceInterface{ public static enum FunctionTypes { - androidIntent, + androidActivity, + androidBroadcast, iosIntent, windowsIntent, browserIntent, diff --git a/src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java b/src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java index b0b124aa..180922d1 100644 --- a/src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java +++ b/src/test/java/net/b07z/sepia/server/assist/tools/Test_SpotifyApi.java @@ -21,35 +21,40 @@ public static void main(String[] args) { System.out.println("Token type: " + sapi.tokenType); System.out.println("Token expires in (ms): " + (sapi.tokenValidUntil - System.currentTimeMillis())); - Debugger.sleep(1000); + Debugger.sleep(500); JSONObject s1 = sapi.searchBestItem("Paradise City", "Guns n Roses", "", "", ""); System.out.println(s1.toJSONString()); - Debugger.sleep(1000); + Debugger.sleep(500); JSONObject s2 = sapi.searchBestItem("", "Guns n Roses", "Appetite For Destruction", "", ""); System.out.println(s2.toJSONString()); - Debugger.sleep(1000); + Debugger.sleep(500); JSONObject s3 = sapi.searchBestItem("", "Guns n Roses", "", "", ""); System.out.println(s3.toJSONString()); - Debugger.sleep(1000); + Debugger.sleep(500); JSONObject s4 = sapi.searchBestItem("", "", "", "", "Rock"); System.out.println(s4.toJSONString()); - Debugger.sleep(1000); + Debugger.sleep(500); JSONObject s5 = sapi.searchBestItem("", "", "", "Party", ""); System.out.println(s5.toJSONString()); - Debugger.sleep(1000); + Debugger.sleep(500); JSONObject s6 = sapi.searchBestItem("Foxy Lady", "Jimi Hendrix", "", "", ""); //note the typo in foxy (not foxey) System.out.println(s6.toJSONString()); + + Debugger.sleep(500); + + JSONObject s7 = sapi.searchBestItem("Immer wieder", "Selig", "", "", ""); + System.out.println(s7.toJSONString()); } } From 64ceca79e7f97191860373d7b71be46701c827b7 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Thu, 16 May 2019 14:14:54 +0200 Subject: [PATCH 22/27] added MediaControls PARAMETER; tweaked music NLU --- .../interpreters/NluKeywordAnalyzerDE.java | 4 +- .../interpreters/NluKeywordAnalyzerEN.java | 2 +- .../assist/parameters/ClientFunction.java | 18 +- .../assist/parameters/MediaControls.java | 267 ++++++++++++++++++ .../server/assist/parameters/MusicArtist.java | 8 + .../assist/parameters/ParameterConfig.java | 1 + .../assist/parameters/PlaylistName.java | 23 +- .../sepia/server/assist/parameters/Song.java | 2 + .../assist/server/ConfigTestServer.java | 8 + .../assist/services/ClientControls.java | 19 +- .../parameters/Test_MusicParameters.java | 33 +++ .../assist/services/Test_ClientControls.java | 150 ++++++++++ 12 files changed, 511 insertions(+), 24 deletions(-) create mode 100644 src/main/java/net/b07z/sepia/server/assist/parameters/MediaControls.java create mode 100644 src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java index 357e0385..7d69cef9 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java @@ -418,8 +418,8 @@ public NluResult interpret(NluInput input) { } //music - if (NluTools.stringContains(text, "musik|music|song(s|)|lied(er|)|" - + "spiel(e|) .*|(start(e|)|oeffne|zeig(e|)) .*\\b(titel|von)|.* ((ab|)spielen)|" + if (NluTools.stringContains(text, "musik|music|song(s|)|lied(er|)|playlist(e|)|" + + "spiel(e|) .*|(start(e|)|oeffne|zeig(e|)) .*\\b(titel|tracks|von)|.* ((ab|)spielen)|" + "album|spotify|deezer|soundcloud|youtube|vlc") && !possibleCMDs.contains(CMD.KNOWLEDGEBASE)){ //String this_text = text; diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java index cc45e81e..83f07811 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java @@ -412,7 +412,7 @@ public NluResult interpret(NluInput input) { } //music - if (NluTools.stringContains(text, "music|play .*|(start|open|show) .*\\b(title(s|)|track(s|)|of|by)|song(s|)|" + if (NluTools.stringContains(text, "music|play .*|(start|open|show) .*\\b(title(s|)|track(s|)|of|by)|song(s|)|playlist|" + "album|record|spotify|deezer|soundcloud|youtube|vlc") && !possibleCMDs.contains(CMD.KNOWLEDGEBASE)){ //String this_text = text; diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java b/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java index 916a832a..d47f98b1 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java @@ -71,7 +71,7 @@ public static String getLocal(String value, String language){ localName = local_en.get(valueWithBrackets); } if (localName == null){ - Debugger.println("Action.java - getLocal() has no '" + language + "' version for '" + value + "'", 3); + Debugger.println("ClientFunction.java - getLocal() has no '" + language + "' version for '" + value + "'", 3); return ""; } return localName; @@ -118,8 +118,8 @@ public String extract(String input) { //German if (language.matches(LANGUAGES.DE)){ settings = "einstellung(en|)|setting(s|)|menue|option(en|)"; - media = "medienwiedergabe|medi(a|en) player|(musik|song|lied) (anhalten|stoppen|stop|beenden|schliessen)"; - volume = "lautstaerke|musik|radio|sound"; //NOTE: musik is used here as well, make sure media is checked first + volume = "lautstaerke|(musik|radio|sound) (lauter|leiser|rauf(\\w+|)|runter(\\w+|)|aufdrehen)"; + media = "medienwiedergabe|medi(a|en)|player|musik|song|lied"; //NOTE: music is used here as well, make sure media is checked first alwaysOn = "always(-| |)on"; meshNode = "mesh(-| |)node"; clexi = "clexi"; @@ -127,8 +127,8 @@ public String extract(String input) { //English and other }else{ settings = "setting(s|)|menu(e|)|option(s|)"; - media = "media player|(media|music|song) (stop|close|end)"; - volume = "volume|music|radio|sound"; //NOTE: music is used here as well, make sure media is checked first + volume = "volume|(music|radio|sound|player) (louder|quieter|up|down)|turn (up|down)"; + media = "media|player|music|song|track"; //NOTE: music is used here as well, make sure volume is checked first alwaysOn = "always(-| |)on"; meshNode = "mesh(-| |)node"; clexi = "clexi"; @@ -147,12 +147,12 @@ public String extract(String input) { //SETTINGS if (NluTools.stringContains(extracted, settings)){ clientFun = "<" + Type.settings + ">"; - //MEDIA - NOTE: use before VOLUME! - }else if (NluTools.stringContains(extracted, media)){ - clientFun = "<" + Type.media + ">"; - //VOLUME + //VOLUME - NOTE: use before MEDIA! }else if (NluTools.stringContains(extracted, volume)){ clientFun = "<" + Type.volume + ">"; + //MEDIA + }else if (NluTools.stringContains(extracted, media)){ + clientFun = "<" + Type.media + ">"; //ALWAYS-ON }else if (NluTools.stringContains(extracted, alwaysOn)){ clientFun = "<" + Type.alwaysOn + ">"; diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/MediaControls.java b/src/main/java/net/b07z/sepia/server/assist/parameters/MediaControls.java new file mode 100644 index 00000000..27c4bfb3 --- /dev/null +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/MediaControls.java @@ -0,0 +1,267 @@ +package net.b07z.sepia.server.assist.parameters; + +import java.util.HashMap; +import java.util.regex.Pattern; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.assistant.LANGUAGES; +import net.b07z.sepia.server.assist.interpreters.NluInput; +import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.interpreters.NluTools; +import net.b07z.sepia.server.assist.interviews.InterviewData; +import net.b07z.sepia.server.assist.users.User; +import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.tools.Debugger; +import net.b07z.sepia.server.core.tools.Is; +import net.b07z.sepia.server.core.tools.JSON; + +/** + * A parameter to find media player controls like "next", "stop", "play", "volume up" etc.
+ * This is probably as close as it gets to a reference implementation of a {@link ParameterHandler}. + * + * @author Florian Quirin + * + */ +public class MediaControls implements ParameterHandler { + + public static enum Type { + play, + stop, + pause, + close, + next, + previous, + volume_up, + volume_down, + volume_set + } + + //-------data------- + public static HashMap local_de = new HashMap<>(); + public static HashMap local_en = new HashMap<>(); + static { + local_de.put("", "abspielen"); + local_de.put("", "stoppen"); + local_de.put("", "pausieren"); + local_de.put("", "schließen"); + local_de.put("", "weiter"); + local_de.put("", "zurück"); + local_de.put("", "lauter"); + local_de.put("", "leiser"); + local_de.put("", "Lautstärke auf "); + + local_en.put("", "play"); + local_en.put("", "stop"); + local_en.put("", "pause"); + local_en.put("", "close"); + local_en.put("", "next"); + local_en.put("", "back"); + local_en.put("", "volume up"); + local_en.put("", "volume down"); + local_en.put("", "volume to "); + } + /** + * Translate generalized value. + * If generalized value is unknown returns empty string. + * @param value - generalized value + * @param language - ISO language code + */ + public static String getLocal(String value, String language){ + String localName = ""; + String valueWithBrackets = value; + if (!value.startsWith("<")){ + valueWithBrackets = "<" + value + ">"; + } + if (language.equals(LANGUAGES.DE)){ + localName = local_de.get(valueWithBrackets); + }else if (language.equals(LANGUAGES.EN)){ + localName = local_en.get(valueWithBrackets); + } + if (localName == null){ + Debugger.println("MediaControls.java - getLocal() has no '" + language + "' version for '" + value + "'", 3); + return ""; + } + return localName; + } + //------------------ + + public User user; + public NluInput nluInput; + public String language; + public boolean buildSuccess = false; + + //keep that in mind + String found = ""; //exact (not generalized) string found during extraction (or guess?) + + @Override + public void setup(NluInput nluInput) { + this.nluInput = nluInput; + this.user = nluInput.user; + this.language = nluInput.language; + } + @Override + public void setup(NluResult nluResult) { + this.nluInput = nluResult.input; + this.user = nluResult.input.user; + this.language = nluResult.language; + } + + @Override + public String extract(String input) { + String mediaControl = ""; + + //check storage first + ParameterResult pr = nluInput.getStoredParameterResult(PARAMETERS.MEDIA_CONTROLS); + if (pr != null){ + mediaControl = pr.getExtracted(); + this.found = pr.getFound(); + return mediaControl; + } + + String play, pause, stop, close, next, previous, vol_up, vol_down, vol_set; + //German + if (language.matches(LANGUAGES.DE)){ + play = "(spiele(n|)|abspielen|starten|oeffne(n|)|play)(?!.*\\b(naechste(\\w|)|vorherige(\\w|)))"; + pause = "pausieren|pause|anhalten"; + stop = "stoppen|stop(p|)"; + close = "schliesse(n|)"; + next = "naechste(\\w|)|vorwaerts|vor|next"; + previous = "zurueck|vorherige(\\w|)"; + vol_set = "lautstaerke (\\w+ |)auf( |$)"; + vol_up = "lauter|lautstaerke( .* | )(erhoehen|rauf|hoch|plus|groesser)|(vergroessern|erhoehen|rauf mit|hoch mit|(hoeher|groesser) machen)( der | )(lautstaerke)"; + vol_down = "leiser|lautstaerke( .* | )(erniedrigen|runter|niedriger|minus|kleiner)|(verkleinern|erniedrigen|runter mit|(niedriger|kleiner) machen)( der | )(lautstaerke)"; + + //English and other + }else{ + play = "(play|start|open)(?!.*\\b(next|previous))"; + pause = "pause"; + stop = "stop|end"; + close = "close"; + next = "next|forward"; + previous = "back|previous"; + vol_set = "(set |)(the |)volume( \\w+ | )to( |$)"; + vol_up = "louder|(turn |)(the |)volume( .* | )(up|increase|plus)|(increase|(turn |)up)( the | )volume"; + vol_down = "quieter|(turn |)(the |)volume( .* | )(down|decrease|minus)|(decrease|(turn |)down)( the | )volume"; + } + + String extracted = NluTools.stringFindFirst(input, + pause + "|" + + stop + "|" + + close + "|" + + next + "|" + + previous + "|" + + vol_set + "|" + //before up, down! + vol_up + "|" + + vol_down + "|" + + play //at the end! + ); + + if (!extracted.isEmpty()){ + //PAUSE + if (NluTools.stringContains(extracted, pause)){ + mediaControl = "<" + Type.pause + ">"; + //STOP + }else if (NluTools.stringContains(extracted, stop)){ + mediaControl = "<" + Type.stop + ">"; + //CLOSE + }else if (NluTools.stringContains(extracted, close)){ + mediaControl = "<" + Type.close + ">"; + //NEXT + }else if (NluTools.stringContains(extracted, next)){ + mediaControl = "<" + Type.next + ">"; + //PREVIOUS + }else if (NluTools.stringContains(extracted, previous)){ + mediaControl = "<" + Type.previous + ">"; + //VOLUME SET - NOTE: do this before vol_up, vol_down, because user could say "increase volume to 11" + }else if (NluTools.stringContains(extracted, vol_set)){ + mediaControl = "<" + Type.volume_set + ">"; + //VOLUME UP + }else if (NluTools.stringContains(extracted, vol_up)){ + mediaControl = "<" + Type.volume_up + ">"; + //VOLUME DOWN + }else if (NluTools.stringContains(extracted, vol_down)){ + mediaControl = "<" + Type.volume_down + ">"; + //PLAY - NOTE: put this at the end for things like "play next song" + }else if (NluTools.stringContains(extracted, play)){ + mediaControl = "<" + Type.play + ">"; + }else{ + mediaControl = ""; + } + } + this.found = extracted; + + //store it - currently we assume this is only used in client-control service + pr = new ParameterResult(PARAMETERS.MEDIA_CONTROLS, mediaControl, found); + nluInput.addToParameterResultStorage(pr); + + if (Is.notNullOrEmpty(this.found) && Is.notNullOrEmpty(mediaControl)){ + return (mediaControl + ";;" + this.found); + }else{ + return ""; + } + } + + @Override + public String guess(String input) { + return ""; + } + + @Override + public String getFound() { + return found; + } + + @Override + public String remove(String input, String found) { + return NluTools.stringRemoveFirst(input, Pattern.quote(found)); + } + + @Override + public String responseTweaker(String input){ + return input; + } + + @Override + public String build(String input) { + String ex = ""; + //String foundInExtract = ""; + if (input.contains(";;")){ + String[] array = input.split(";;"); + ex = array[0]; + //foundInExtract = array[1]; + }else{ + ex = input; + } + //is accepted result? + String inputLocal = getLocal(ex, language); + if (inputLocal.isEmpty()){ + return ""; + } + //build default result + JSONObject itemResultJSON = new JSONObject(); + //JSON.add(itemResultJSON, InterviewData.INPUT_RAW, nluInput.textRaw); + JSON.add(itemResultJSON, InterviewData.VALUE, ex); + JSON.add(itemResultJSON, InterviewData.VALUE_LOCAL, inputLocal); + //JSON.add(itemResultJSON, InterviewData.FOUND, foundInExtract); + + buildSuccess = true; + return itemResultJSON.toJSONString(); + } + + @Override + public boolean validate(String input) { + if (input.matches("^\\{\".*\":.+\\}$") && input.contains("\"" + InterviewData.VALUE + "\"")){ + //System.out.println("IS VALID: " + input); //debug + return true; + }else{ + return false; + } + } + + @Override + public boolean buildSuccess() { + return buildSuccess; + } + +} diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java index f19b3e1b..d025a83f 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java @@ -64,6 +64,10 @@ public String extract(String input) { if (prMusicAlbum != null){ optimizedInput = ParameterResult.cleanInputOfFoundParameter(nluInput, PARAMETERS.MUSIC_ALBUM, prMusicAlbum, optimizedInput); } + ParameterResult prMusicPlaylist = ParameterResult.getResult(nluInput, PARAMETERS.PLAYLIST_NAME, optimizedInput); + if (prMusicPlaylist != null){ + optimizedInput = ParameterResult.cleanInputOfFoundParameter(nluInput, PARAMETERS.PLAYLIST_NAME, prMusicPlaylist, optimizedInput); + } String creator = ""; //German @@ -93,10 +97,14 @@ public String extract(String input) { creator = NluTools.stringFindFirst(optimizedInput, ".*? (songs|lieder|titel|musik)"); creator = creator.replaceFirst(" (songs|lieder|musik|titel)$", ""); creator = creator.replaceFirst(".*\\b(spiel(e|)|oeffne|start(e|))\\b", ""); + //clean actions + creator = creator.trim().replaceFirst("^(stoppe(n|)|stop|naechste(\\w|)|vorherige(\\w|)|anhalten|schliessen|cancel|abbrechen|zurueck|vor)$", ""); }else{ creator = NluTools.stringFindFirst(optimizedInput, ".*? (songs|titles|tracks|music)"); creator = creator.replaceFirst(" (songs|music|titles|tracks)$", ""); creator = creator.replaceFirst(".*\\b(play|open|start)\\b", ""); + //clean actions + creator = creator.trim().replaceFirst("^(stop|next|previous|clear|cancel|abort|close|end|back|forward)$", ""); } if (!creator.trim().isEmpty()){ //clean genre parameter diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java b/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java index cf2939d9..d82c09bb 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/ParameterConfig.java @@ -75,6 +75,7 @@ public static void setup(){ handlerToParameter.put(PARAMETERS.MUSIC_ALBUM, MusicAlbum.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.SONG, Song.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.PLAYLIST_NAME, PlaylistName.class.getCanonicalName()); + handlerToParameter.put(PARAMETERS.MEDIA_CONTROLS, MediaControls.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.SMART_DEVICE, SmartDevice.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.SMART_DEVICE_VALUE, SmartDeviceValue.class.getCanonicalName()); handlerToParameter.put(PARAMETERS.ROOM, Room.class.getCanonicalName()); diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/PlaylistName.java b/src/main/java/net/b07z/sepia/server/assist/parameters/PlaylistName.java index 4c42cb5e..2dedee7a 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/PlaylistName.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/PlaylistName.java @@ -58,17 +58,20 @@ public String extract(String input) { String playlistName; if (language.equals(LANGUAGES.DE)){ //GERMAN - playlistName = NluTools.stringFindFirst(input, "playlist ((mit |)namen|namens|genannt) .*"); + playlistName = NluTools.stringFindFirst(input, "playlist(e|) ((mit |)namen|namens|genannt) .*"); if (!playlistName.isEmpty()){ playlistName = playlistName.replaceAll(".*?\\b((mit |)namen|namens|genannt)\\b", ""); }else{ - playlistName = NluTools.stringFindFirst(input, ".* playlist"); + playlistName = NluTools.stringFindFirst(input, ".* playlist(e|)"); if (!playlistName.isEmpty()){ + playlistName = playlistName.replaceFirst(".*?\\b((von|auf) (der |meiner |einer |))(?.*)( playlist(e|))\\b", "${name}").trim(); playlistName = playlistName.replaceFirst(".*?\\b(starte|oeffne|spiel(e|)|such(e|)|finde|zeig(e|))\\b", "").trim(); - playlistName = playlistName.replaceFirst("^(meine|die|eine)\\b", ""); + playlistName = playlistName.replaceFirst("^(von |auf |)(der|meine(r|)|die|eine(r|))\\b", ""); } } - playlistName = playlistName.replaceAll("\\b(playlist)\\b", "").trim(); + playlistName = playlistName.replaceAll("\\b(playlist(e|))\\b", "").trim(); + //clean actions + playlistName = playlistName.trim().replaceFirst("^(stoppe(n|)|stop|naechste(\\w|)|vorherige(\\w|)|cancel|abbrechen|zurueck|vor)$", ""); }else{ //ENGLISH playlistName = NluTools.stringFindFirst(input, "playlist (called|named|(with (the |)|)name) .*"); @@ -77,11 +80,14 @@ public String extract(String input) { }else{ playlistName = NluTools.stringFindFirst(input, ".* playlist"); if (!playlistName.isEmpty()){ + playlistName = playlistName.replaceFirst(".*?\\b((from|of|on) (the |my |a |))(?.*)( playlist)\\b", "${name}").trim(); playlistName = playlistName.replaceFirst(".*?\\b(start|open|play|search|find|show)\\b", "").trim(); - playlistName = playlistName.replaceFirst("^(my|the|a)\\b", ""); + playlistName = playlistName.replaceFirst("^(on |from |of |)(my|the|a)\\b", ""); } } playlistName = playlistName.replaceAll("\\b(playlist)\\b", "").trim(); + //clean actions + playlistName = playlistName.trim().replaceFirst("^(stop|next|previous|clear|cancel|abort|back|forward)$", ""); } this.found = playlistName; @@ -110,7 +116,12 @@ public String getFound() { @Override public String remove(String input, String found) { - return NluTools.stringRemoveFirst(input, Pattern.quote(found)); + if (language.equals(LANGUAGES.DE)){ + found = "(von |auf |)(der |)" + Pattern.quote(found) + "(| playlist(e|))"; + }else{ + found = "(of |from |on |)(the |)" + Pattern.quote(found) + "(| playlist)"; + } + return NluTools.stringRemoveFirst(input, found); } @Override diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java b/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java index 177f00b1..2a949738 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java @@ -79,11 +79,13 @@ public String extract(String input) { if (!song.isEmpty()){ if (language.equals(LANGUAGES.DE)){ song = song.replaceFirst("^(ein(en|ige|) |den |das |etwas )", "").trim(); + song = song.replaceFirst("^(naechste(\\w|)|vorherige(\\w|))", "").trim(); song = song.replaceFirst("^(song(s|)|lied(er|)|musik|titel|(irgend|)etwas|was|egal was)\\b", "").trim(); song = song.replaceFirst("^((mit (dem |)|)(titel|namen)|namens)\\b", "").trim(); song = song.replaceFirst(".*? (songs|lieder|musik( titel|)|titel)$", "").trim(); }else{ song = song.replaceFirst("^(the |a |any |some )", "").trim(); + song = song.replaceFirst("^(next|previous)", "").trim(); song = song.replaceFirst("^(song(s|)|music|title(s|)|track(s|)|something|anything)\\b", "").trim(); song = song.replaceFirst("^((with (the |)|)(title|name)|named|)\\b", "").trim(); song = song.replaceFirst(".*? (songs|music|titles)$", "").trim(); diff --git a/src/main/java/net/b07z/sepia/server/assist/server/ConfigTestServer.java b/src/main/java/net/b07z/sepia/server/assist/server/ConfigTestServer.java index 02fedd0d..2e73bdf5 100644 --- a/src/main/java/net/b07z/sepia/server/assist/server/ConfigTestServer.java +++ b/src/main/java/net/b07z/sepia/server/assist/server/ConfigTestServer.java @@ -10,6 +10,7 @@ import net.b07z.sepia.server.assist.users.ID; import net.b07z.sepia.server.assist.users.User; import net.b07z.sepia.server.core.tools.DateTime; +import net.b07z.sepia.server.core.tools.Is; import spark.Request; /** @@ -100,6 +101,13 @@ public static NluInput getFakeInput(String text, String language){ input.user = user; return input; } + public static String getFakeUserId(String email){ + if (Is.notNullOrEmpty(email)){ + return testAccountIDs.get(email); + }else{ + return testAccountIDs.get(ConfigTestServer.email_id1); + } + } /** * Make a test token with "real" or "fake" database data. Use one of the test-email IDs to create it. diff --git a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java index 6a1094fb..0b0e1a32 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java @@ -77,15 +77,21 @@ public ServiceInfo getInfo(String language) { + "(.* |)(musik|sound|radio) (lauter|leiser)( .*|)|" + "(.* |)lautstaerke( .*|)|" + "(.* |)(medi(a|en)(-| |)player|medienwiedergabe)( .*|)|" - + "(.* |)(musik|song|lied|medien|media) (anhalten|stoppen|stop|beenden|schliessen)( .*|)" + + "(.* |)(naechste(\\w|)|vorherige(\\w|)) (musik|song|lied|medien|media|titel)( .*|)|" + + "(naechste(\\w|)|vorherige(\\w|)|vor|zurueck|stop(pen|p|)|play|abspielen|lauter|leiser)|" + + "(.* |)(musik|song|lied|medien|media|titel|player) (anhalten|stoppen|stop(p|)|beenden|schliessen)( .*|)|" + + "(.* |)(stoppe|stop(p|)|schliesse)( .* | )(musik|song|lied|medien|media|titel|player|sound)( .*|)" + ")$", DE); info.setCustomTriggerRegX("^(" - + "( .*|)open setting(s|)( .*|)|" + + "(.* |)open setting(s|)( .*|)|" + "(.* |)always(-| |)on( .*|)|" - + "( .*|)(music|sound|radio) (quieter|louder)( .*|)|" - + "(.* |)volume( .*|)|" + + "(.* |)(music|sound|radio) (quieter|louder)( .*|)|" + + "(.* |)(volume|turn (up|down))( .*|)|" + "(.* |)(media(-| |)player)( .*|)|" - + "(.* |)(media|music|song) (stop|close|end)( .*|)" + + "(.* |)(next|previous) (media|music|song|track|title)( .*|)|" + + "(next|previous|back|forward|stop|play|louder|quieter)|" + + "(.* |)(media|music|song|track|title|player|sound) (stop|close|end)( .*|)|" + + "(.* |)(stop|close|end)( .* | )(media|music|song|track|title|player|sound)( .*|)" + ")$", EN); info.setCustomTriggerRegXscoreBoost(2); //boost service a bit to increase priority over similar ones @@ -102,8 +108,9 @@ public ServiceInfo getInfo(String language) { //optional Parameter p3 = new Parameter(PARAMETERS.DATA); Parameter p4 = new Parameter(PARAMETERS.NUMBER); + Parameter p5 = new Parameter(PARAMETERS.MEDIA_CONTROLS); - info.addParameter(p1).addParameter(p2).addParameter(p3).addParameter(p4); + info.addParameter(p1).addParameter(p2).addParameter(p3).addParameter(p4).addParameter(p5); //Default answers info.addSuccessAnswer("ok_0b") diff --git a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java index 0bd47cf6..2428f8a4 100644 --- a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java +++ b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java @@ -1,5 +1,8 @@ package net.b07z.sepia.server.assist.parameters; +import java.util.HashMap; +import java.util.Map; + import net.b07z.sepia.server.assist.interpreters.NluInput; import net.b07z.sepia.server.assist.interpreters.Normalizer; import net.b07z.sepia.server.assist.parameters.Test_Parameters.TestResult; @@ -17,12 +20,15 @@ public class Test_MusicParameters { PARAMETERS.PLAYLIST_NAME, PARAMETERS.MUSIC_ALBUM }; + private static Map errors; + public static void main(String[] args) { long tic = Debugger.tic(); Start.setupServicesAndParameters(); //warm up + errors = new HashMap<>(); System.out.println("-----SENTENCE TESTING------"); String lang = "de"; @@ -53,6 +59,15 @@ public static void main(String[] args) { testMusicParameters("starte Rockmusik mit VLC player", "[, rock, , , , ]", lang); testMusicParameters("starte Rockmusik via Spotify link", "[, rock, , , , ]", lang); testMusicParameters("Öffne eine Rock Playlist bitte", "[, Rock, , , Rock, ]", lang); + testMusicParameters("Starte ein Lied von der Rock Playliste", "[, Rock, , , Rock, ]", lang); + testMusicParameters("Musik anhalten", "[, , , , , ]", lang); + testMusicParameters("Song stoppen", "[, , , , , ]", lang); + testMusicParameters("Musik anhalten", "[, , , , , ]", lang); + testMusicParameters("stoppe Musik", "[, , , , , ]", lang); + testMusicParameters("spiele den nächsten Song", "[, , , , , ]", lang); + testMusicParameters("stoppe den nächsten Song", "[, , , , , ]", lang); + testMusicParameters("spiele den nächsten Song auf der Playlist", "[, , , , , ]", lang); + testMusicParameters("spiele etwas von der Chill Playlist", "[, , , , Chill, ]", lang); lang = "en"; System.out.println("\n----- en -----"); @@ -82,8 +97,25 @@ public static void main(String[] args) { testMusicParameters("start Rock music using VLC player", "[, Rock, , , , ]", lang); testMusicParameters("start Rock music via Spotify link", "[, Rock, , , , ]", lang); testMusicParameters("Search a rock playlist please", "[, rock, , , rock, ]", lang); + testMusicParameters("Play a song from the rock playlist please", "[, rock, , , rock, ]", lang); + testMusicParameters("Music stop", "[, , , , , ]", lang); + testMusicParameters("stop song", "[, , , , , ]", lang); + testMusicParameters("stop music", "[, , , , , ]", lang); + testMusicParameters("play the next song", "[, , , , , ]", lang); + testMusicParameters("stop the next song", "[, , , , , ]", lang); + testMusicParameters("play next song on the playlist", "[, , , , , ]", lang); + testMusicParameters("play something from the Chill playlist", "[, , , , Chill, ]", lang); System.out.println("-----------"); + + //Errors + System.out.println("Collected errors: "); + System.err.println(""); + for (Map.Entry e : errors.entrySet()){ + System.err.println(e.getKey()); + System.err.println(e.getValue()); + System.err.println(""); + } System.out.println("Took: " + Debugger.toc(tic) + "ms"); } @@ -121,6 +153,7 @@ private static boolean testMusicParameters(String text, String shouldBe, String }else{ try{ Thread.sleep(20); }catch(Exception e){} System.err.println("Found: " + res + " - should be: " + shouldBe); + errors.put(text, res + " - " + shouldBe); try{ Thread.sleep(20); }catch(Exception e){} return false; } diff --git a/src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java b/src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java new file mode 100644 index 00000000..4f1117a9 --- /dev/null +++ b/src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java @@ -0,0 +1,150 @@ +package net.b07z.sepia.server.assist.services; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.assistant.LANGUAGES; +import net.b07z.sepia.server.assist.data.Name; +import net.b07z.sepia.server.assist.interpreters.NluInput; +import net.b07z.sepia.server.assist.interpreters.NluInterface; +import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.interviews.AbstractInterview; +import net.b07z.sepia.server.assist.interviews.InterviewInterface; +import net.b07z.sepia.server.assist.interviews.InterviewResult; +import net.b07z.sepia.server.assist.server.Config; +import net.b07z.sepia.server.assist.server.ConfigServices; +import net.b07z.sepia.server.assist.server.ConfigTestServer; +import net.b07z.sepia.server.core.assistant.CMD; +import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.tools.ClassBuilder; +import net.b07z.sepia.server.core.tools.JSON; +import net.b07z.sepia.server.core.tools.JSONWriter; + +public class Test_ClientControls { + + private static List errors = new ArrayList<>(); + + public static void main(String[] args) { + + //load default settings (can add Start.loadConfigFile(serverType) before) + ConfigTestServer.loadAnswersAndParameters(); + String userId = ConfigTestServer.getFakeUserId(null); //default test user 1 + + //Prevent database access for tests + ConfigTestServer.reduceDatabaseAccess(userId); + //... add service specific test-settings here + + //Tests + Map tests; + + boolean doAnswer = false; //just interpret (if you want to test a keywordAnalyzer defined INSIDE service) + String CC = CMD.CLIENT_CONTROLS; + String M = CMD.MUSIC; + String language; + + language = LANGUAGES.EN; + tests = new HashMap<>(); + tests.put("Set the volume to 11", JSON.make("c", CC, "p", JSON.make(PARAMETERS.MEDIA_CONTROLS, ".*"))); + tests.put("Set the volume to eleven", JSON.make("c", CC)); + tests.put("Play the next song", JSON.make("c", CC, "p", JSON.make(PARAMETERS.MEDIA_CONTROLS, ".*"))); + tests.put("Next", JSON.make("c", CC)); + tests.put("Back", JSON.make("c", CC)); + tests.put("Stop music", JSON.make("c", CC, "p", JSON.make(PARAMETERS.MEDIA_CONTROLS, ".*"))); + tests.put("Play music", JSON.make("c", M)); + tests.put("Play stop and go by Fake Artist", JSON.make("c", M)); + + for (Map.Entry t : tests.entrySet()){ + String shouldBeCmd = JSON.getString(t.getValue(), "c"); + JSONObject parameterRegExpMatches = JSON.getJObject(t.getValue(), "p"); + testSentenceViaKeywordAnalyzer(doAnswer, t.getKey(), language, shouldBeCmd, parameterRegExpMatches); + } + + if (!errors.isEmpty()){ + System.out.println("Errors in: "); + for (String s : errors){ + System.out.println(s); + } + } + System.out.println("--- DONE ---"); + } + + private static void testSentenceViaKeywordAnalyzer(boolean doAnswer, String text, String language, String shouldBeCMD, JSONObject parameterRegExpMatches){ + //load "fake" input with some defaults + NluInput input = ConfigTestServer.getFakeInput(text, language); //default test user 1 + + //set some values explicitly + input.user.userName = new Name("Mister", "Tester", "Testy"); + input.setTimeGMT("2019.01.14_13:00:00"); + + //time + //System.out.println("\nChosen UNIX time (s): " + Math.round(input.userTime/1000)); + //System.out.println("Now (for testing): " + DateTimeConverters.getSpeakableDateSpecial(input.userTimeLocal, 5l, Config.defaultSdf, input) + ", " + input.userTimeLocal + "\n"); + + //start with test: + try{ Thread.sleep(20); }catch(Exception e){} + System.out.println("Sentence: " + input.text); + try{ Thread.sleep(20); }catch(Exception e){} + + //create NluResult (shortcut) + NluInterface nlp = (NluInterface) ClassBuilder.construct(Config.keywordAnalyzers.get(input.language)); //NOTE: hard-coded NLU Interface for this test + NluResult nluResult = nlp.interpret(input); + + String cmd = nluResult.getCommand(); + JSONObject resJson = nluResult.getBestResultJSON(); + + if (cmd.equals(shouldBeCMD)){ + boolean allParamsMatch = true; + if (parameterRegExpMatches != null){ + JSONObject params = JSON.getJObject(resJson, "parameters"); + for (Object o : parameterRegExpMatches.keySet()){ + String pToCheck = (String) o; + String actualValue = JSON.getString(params, pToCheck); + String shouldMatch = JSON.getString(parameterRegExpMatches, pToCheck); + if (!actualValue.matches(shouldMatch)){ + allParamsMatch = false; + System.err.println("Mismatch! Expected " + pToCheck + "=" + shouldMatch + " - found: " + actualValue); + break; + } + } + } + if (allParamsMatch){ + System.out.println("Best NLU result:"); + System.out.println(JSONWriter.getPrettyString(resJson)); + }else{ + System.err.println("Best NLU result:"); + System.err.println(JSONWriter.getPrettyString(resJson)); + errors.add(text); + } + }else{ + System.err.println("Best NLU result:"); + System.err.println(JSONWriter.getPrettyString(resJson)); + errors.add(text); + } + + //get answer + if (doAnswer){ + ServiceResult answer; + List services = ConfigServices.getCustomOrSystemServices(input, input.user, cmd); + InterviewInterface interview = new AbstractInterview(); + interview.setCommand(cmd); + interview.setServices(services); + InterviewResult iResult = interview.getMissingParameters(nluResult); + if (iResult.isComplete()){ + answer = interview.getServiceResults(iResult); + }else{ + answer = iResult.getApiComment(); + } + try{ Thread.sleep(20); }catch(Exception e){} + System.out.println(JSONWriter.getPrettyString(answer.getResultJSONObject())); + System.out.println(""); + }else{ + System.out.println(""); + } + try{ Thread.sleep(20); }catch(Exception e){} + } + +} From b43149005ee8a0989ad99178b1e7f60402d5ebe6 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Sat, 18 May 2019 22:57:11 +0200 Subject: [PATCH 23/27] custom_data in input; iTunesApi; more media controls; NLU fixes; --- Xtensions/Assistant/answers/answers_de.txt | 1 + Xtensions/Assistant/answers/answers_en.txt | 1 + Xtensions/ServiceProperties/news-outlets.json | 4 +- .../assist/endpoints/AssistEndpoint.java | 3 + .../server/assist/interpreters/NluInput.java | 32 +- .../interpreters/NluKeywordAnalyzerDE.java | 80 ++--- .../interpreters/NluKeywordAnalyzerEN.java | 80 ++--- .../assist/parameters/ClientFunction.java | 6 +- .../assist/parameters/MediaControls.java | 20 +- .../assist/services/ClientControls.java | 68 +++-- .../server/assist/services/MusicSearch.java | 98 +++++- .../assist/services/WeatherDarkSky.java | 4 +- .../sepia/server/assist/tools/ITunesApi.java | 287 ++++++++++++++++++ .../assist/services/Test_ClientControls.java | 25 +- .../server/assist/tools/Test_ITunesApi.java | 61 ++++ 15 files changed, 651 insertions(+), 119 deletions(-) create mode 100644 src/main/java/net/b07z/sepia/server/assist/tools/ITunesApi.java create mode 100644 src/test/java/net/b07z/sepia/server/assist/tools/Test_ITunesApi.java diff --git a/Xtensions/Assistant/answers/answers_de.txt b/Xtensions/Assistant/answers/answers_de.txt index 3857dc7f..ae6b22ca 100644 --- a/Xtensions/Assistant/answers/answers_de.txt +++ b/Xtensions/Assistant/answers/answers_de.txt @@ -45,6 +45,7 @@ default_open_link_0a;; rep=0|mood=5;; Mist, da gibts scheinbar ein Problem mit default_open_link_1a;; rep=0|mood=5;; Ok, ich öffne den Link für dich. ;;char=neutral default_open_link_1a;; rep=0|mood=5;; Eine Sekunde, ich öffne den Link. ;;char=neutral default_under_construction_0a;; rep=0|mood=5;; Daran wird noch gearbeitet, sorry. ;;char=neutral +default_under_construction_0b;; rep=0|mood=5;; Oh, hab mich vertan, sorry! Daran wird noch gearbeitet. ;;char=neutral default_abort_no_change_0a;; rep=0|mood=5;; Ok, dann lasse ich mal alles so. ;;char=neutral default_not_possible_0a;; rep=0|mood=5;; Sorry aber das ist nicht möglich, glaube ich. ;;char=neutral yes_no_ask_0a;; rep=0|mood=5;; ja oder nein ? ;;char=cool,rude,neutral diff --git a/Xtensions/Assistant/answers/answers_en.txt b/Xtensions/Assistant/answers/answers_en.txt index ebee6d82..5690c18c 100644 --- a/Xtensions/Assistant/answers/answers_en.txt +++ b/Xtensions/Assistant/answers/answers_en.txt @@ -45,6 +45,7 @@ default_open_link_0a;; rep=0|mood=5;; I'm afraid there seems to be a problem w default_open_link_1a;; rep=0|mood=5;; Ok, let me open that link for you. ;;char=neutral default_open_link_1a;; rep=0|mood=5;; One second, opening the link. ;;char=neutral default_under_construction_0a;; rep=0|mood=5;; This is still under construction, sorry. ;;char=neutral +default_under_construction_0b;; rep=0|mood=5;; Oh sorry I forgot! This is still under construction. ;;char=neutral default_abort_no_change_0a;; rep=0|mood=5;; Ok, I'll leave everything as is. ;;char=neutral default_not_possible_0a;; rep=0|mood=5;; Sorry this is not possible, I think. ;;char=neutral yes_no_ask_0a;; rep=0|mood=5;; yes or no ? ;;char=rude,neutral diff --git a/Xtensions/ServiceProperties/news-outlets.json b/Xtensions/ServiceProperties/news-outlets.json index fb93e1c5..f7b7e13e 100644 --- a/Xtensions/ServiceProperties/news-outlets.json +++ b/Xtensions/ServiceProperties/news-outlets.json @@ -19,8 +19,8 @@ { "name": "heise online", "url": "https://www.heise.de/rss/heise-top-atom.xml", "name_html": "heise online" }, { "name": "heise Developer", "url": "https://www.heise.de/developer/rss/news-atom.xml", "name_html": "heise Developer" }, { "name": "heise Make", "url": "https://www.heise.de/make/rss/hardware-hacks-atom.xml", "name_html": "heise Make" }, - { "name": "PCGames", "url": "http://www.pcgames.de/feed.cfm?menu_alias=home", "name_html": "PCGames" }, - { "name": "GameStar", "url": "http://www.pcgames.de/feed.cfm?menu_alias=home", "name_html": "GameStar" }, + { "name": "PCGames", "url": "https://www.pcgames.de/feed.cfm?menu_alias=home", "name_html": "PCGames" }, + { "name": "GameStar", "url": "https://www.gamestar.de/news/rss/news.rss", "name_html": "GameStar" }, { "name": "t3n", "url": "https://t3n.de/rss.xml", "name_html": "t3n" }, { "name": "Business Punk", "url": "https://www.business-punk.com/feed/", "name_html": "Business Punk" }, { "name": "RollingStone", "url": "https://www.rollingstone.com/music/feed/", "name_html": "RollingStone" }, diff --git a/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java b/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java index 861b3a0a..4fc3f21c 100644 --- a/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java +++ b/src/main/java/net/b07z/sepia/server/assist/endpoints/AssistEndpoint.java @@ -60,6 +60,7 @@ public static enum InputParameters { connection, //http or ws (WebSocket), defaults to http, check also: NluInput.isDuplexConnection() msg_id, //duplex connections might want to track the msg id duplex_data, //more duplex data in JSON format + custom_data, //custom data defined by a client as required demomode } @@ -273,6 +274,7 @@ public static NluInput getInput(RequestParameters params){ String connection = params.getString(InputParameters.connection.name()); //http or ws String msg_id = params.getString(InputParameters.msg_id.name()); //msg ID String duplex_data = params.getString(InputParameters.duplex_data.name()); //duplex data + String custom_data = params.getString(InputParameters.custom_data.name()); //custom data string long time = -1; //-answer params: String last_cmd = params.getString(InputParameters.last_cmd.name()); @@ -294,6 +296,7 @@ public static NluInput getInput(RequestParameters params){ if (connection!=null) input.connection = connection; if (msg_id!=null) input.msgId = msg_id; if (duplex_data!=null) input.duplexData = duplex_data; + if (custom_data!=null) input.customData = custom_data; if (env!=null) input.environment = env; else input.environment = "all"; //input.client_info.replaceFirst("_v\\d.*", "").trim(); if (deviceId!=null) input.deviceId = deviceId; if (user_location!=null) input.userLocation = user_location; diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java index 36ff3d68..efdb8d45 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluInput.java @@ -4,6 +4,8 @@ import java.util.List; import java.util.Map; +import org.json.simple.JSONObject; + import net.b07z.sepia.server.assist.answers.ServiceAnswers; import net.b07z.sepia.server.assist.parameters.ParameterResult; import net.b07z.sepia.server.assist.server.Config; @@ -11,6 +13,8 @@ import net.b07z.sepia.server.assist.tools.DateTimeConverters; import net.b07z.sepia.server.assist.users.User; import net.b07z.sepia.server.core.data.CmdMap; +import net.b07z.sepia.server.core.tools.Is; +import net.b07z.sepia.server.core.tools.JSON; /** * Use this to generate the input for any NL-Processor. @@ -28,7 +32,7 @@ public class NluInput { //main input - API param / Description //public Request request; //request data - public String text = ""; //:text: input text/question/query + public String text = ""; //text: input text/question/query public String textRaw = ""; // input text in its raw form, no replacements, no cmd transformation (e.g. for direct commands it tries to retain the text) public String language = "en"; //lang: language used for interpretation and results (ISO 639-1 code) public String context = "default"; //context: context is what the user did/said before to answer queries like "do that again" or "and in Berlin?" @@ -50,9 +54,11 @@ public class NluInput { //... more to come public String deviceId = ""; //device_id: an ID defined by the user to identify a certain device public String msgId = null; //msg_id: an ID to identify request, especially helpful in duplex scenarios - public String duplexData = null; //duplex_data: data helpful to trace back a duplex call and answer or follow-up. Format is JSON, parse when required + public String duplexData = null; //duplex_data: data helpful to trace back a duplex call and answer or follow-up, e.g. the chat-channel-ID. Format is JSON, parse when required public String connection = "http"; //connection: http request or WebSocket connection - has influence on delayed replies public boolean demoMode = false; //demomode: true/false if you want to use the demomode + public String customData = null; //custom_data: a dynamic variable to carry any data that does not fit to the pre-defined stuff. Should be a JSONObject converted to string. + private JSONObject customDataJson = null; //custom data is parsed when needed and the result is stored here. //Stuff to cache during all processes from NLU to service result: @@ -221,6 +227,28 @@ public ServiceAnswers getCachedServiceAnswers(String serviceCommand){ } } + //Custom data + /** + * Get a value of the custom-data object submitted by client. + * @param key - the field in the JSON object + * @return value or null + */ + public Object getCustomDataObject(String key){ + if (customDataJson != null){ + return customDataJson.get(key); + }else if (Is.notNullOrEmpty(customData)){ + JSONObject cd = JSON.parseString(customData); + if (cd != null){ + customDataJson = cd; + return customDataJson.get(key); + }else{ + return null; + } + }else{ + return null; + } + } + //-------helper methods-------- //connection type diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java index 7d69cef9..5e02936e 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java @@ -170,44 +170,8 @@ public NluResult interpret(NluInput input) { possibleParameters.add(pv); } - //fashion shopping - if (NluTools.stringContains(text, "(kaufen|erwerben|mode|fashion|shoppen|shopping|(sind|ist) kaputt)") || - ((NluTools.stringContains(text, FashionItem.fashionItems_de) || NluTools.stringContains(text, FashionBrand.fashionBrandsSearch)) - && !NluTools.stringContains(text, "(ich habe |da sind |es gibt |meine )")) - ){ - possibleCMDs.add(CMD.FASHION); - possibleScore.add(1); index++; - HashMap pv = new HashMap(); //TODO: pass this down to avoid additional checking - FashionShopping fs = new FashionShopping().setup(input, pv); - fs.getParameters(); - possibleScore.set(index, possibleScore.get(index) + fs.getScore()); - possibleParameters.add(pv); - } - - //food - if (NluTools.stringContains(text, "^essen$|mittagessen|" - + "(\\w*essen|nahrung|fruehstueck|lebensmittel|nahrungsmittel) .*\\b(bestellen|kaufen|ordern|liefern|essen)|" - + "(" + Language.languageClasses_de + "|" + FoodClass.foodClasses_de + ") .*\\b(bestellen|kaufen|ordern|liefern|essen)|" - + "(ordere|bestelle|kaufe|gerne|was) .*\\b(\\w*essen|\\w*nahrung|fruehstueck|lebensmittel|nahrungsmittel)|" - + "(ordere|bestelle|kaufe|gerne) .*\\b(" + Language.languageClasses_de + "|" + FoodClass.foodClasses_de + ")|" - + "\\w*hunger|hungrig|appetit|lieferservice(s|)|(was|etwas) zwischen .*\\b(zaehne|beisser)|" - + "(aus|von) .*\\b(\\w*restaurant)") - || NluTools.stringContains(text, FoodItem.foodItems_de) - ){ - possibleCMDs.add(CMD.FOOD); - possibleScore.add(1); index++; - - HashMap pv = new HashMap(); //TODO: pass this down to avoid additional checking - AbstractParameterSearch aps = new AbstractParameterSearch() - .setParameters(PARAMETERS.FOOD_ITEM, PARAMETERS.FOOD_CLASS) - .setup(input, pv); - aps.getParameters(); - possibleScore.set(index, possibleScore.get(index) + aps.getScore()); - possibleParameters.add(pv); - } - //wikipedia/knowledgebase - if (NluTools.stringContains(text, "wiki|wikipedia|information|informationen|" + if (NluTools.stringContains(text, "wiki|wikipedia|information|informationen|wissensdatenbank|" + "wer (ist|sind|war|waren) .*|" + "was (ist|sind|war|waren) .*|" + "wann (ist|sind|war|waren) .*|" @@ -217,6 +181,12 @@ public NluResult interpret(NluInput input) { String this_text = text; possibleCMDs.add(CMD.KNOWLEDGEBASE); possibleScore.add(1); index++; + + //score a bit extra if we start with certain words + if (NluTools.stringContains(this_text, "^((durch|)suche |)(auf |in |)(der |die |)(wikipedia|wissensdatenbank)")){ + possibleScore.set(index, possibleScore.get(index)+1); + } + //kb search term String kb_search = ""; if (NluTools.stringContains(this_text, "wie hoch (ist|sind|war|waren)|wie alt (ist|sind|war|waren)|(wieviele|wie viele) .* (hat|hatte|hatten|ist|sind|war|waren|leben|lebten)")){ @@ -303,6 +273,42 @@ public NluResult interpret(NluInput input) { possibleParameters.add(pv); } + //fashion shopping + if (NluTools.stringContains(text, "(kaufen|erwerben|mode|fashion|shoppen|shopping|(sind|ist) kaputt)") || + ((NluTools.stringContains(text, FashionItem.fashionItems_de) || NluTools.stringContains(text, FashionBrand.fashionBrandsSearch)) + && !NluTools.stringContains(text, "(ich habe |da sind |es gibt |meine )")) + ){ + possibleCMDs.add(CMD.FASHION); + possibleScore.add(1); index++; + HashMap pv = new HashMap(); //TODO: pass this down to avoid additional checking + FashionShopping fs = new FashionShopping().setup(input, pv); + fs.getParameters(); + possibleScore.set(index, possibleScore.get(index) + fs.getScore()); + possibleParameters.add(pv); + } + + //food + if (NluTools.stringContains(text, "^essen$|mittagessen|" + + "(\\w*essen|nahrung|fruehstueck|lebensmittel|nahrungsmittel) .*\\b(bestellen|kaufen|ordern|liefern|essen)|" + + "(" + Language.languageClasses_de + "|" + FoodClass.foodClasses_de + ") .*\\b(bestellen|kaufen|ordern|liefern|essen)|" + + "(ordere|bestelle|kaufe|gerne|was) .*\\b(\\w*essen|\\w*nahrung|fruehstueck|lebensmittel|nahrungsmittel)|" + + "(ordere|bestelle|kaufe|gerne) .*\\b(" + Language.languageClasses_de + "|" + FoodClass.foodClasses_de + ")|" + + "\\w*hunger|hungrig|appetit|lieferservice(s|)|(was|etwas) zwischen .*\\b(zaehne|beisser)|" + + "(aus|von) .*\\b(\\w*restaurant)") + || NluTools.stringContains(text, FoodItem.foodItems_de) + ){ + possibleCMDs.add(CMD.FOOD); + possibleScore.add(1); index++; + + HashMap pv = new HashMap(); //TODO: pass this down to avoid additional checking + AbstractParameterSearch aps = new AbstractParameterSearch() + .setParameters(PARAMETERS.FOOD_ITEM, PARAMETERS.FOOD_CLASS) + .setup(input, pv); + aps.getParameters(); + possibleScore.set(index, possibleScore.get(index) + aps.getScore()); + possibleParameters.add(pv); + } + //flights if (NluTools.stringContains(text, "(flug|fluege|fluegen) (nach|zu|zur|von)|(suche|finde|brauche|zeig mir|buche|wie)\\b.* (flug|fluege|fluegen|fliegen|flugticket(s|)|flugzeug(en|e|))|" + "(flug|fluege) (suchen|finden|buchen|zeigen)|flugsuche|" diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java index 83f07811..3bb52d60 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java @@ -165,44 +165,8 @@ public NluResult interpret(NluInput input) { possibleParameters.add(pv); } - //fashion shopping - if (NluTools.stringContains(text, "(buy|fashion|shopping)") || - ((NluTools.stringContains(text, FashionItem.fashionItems_en) || NluTools.stringContains(text, FashionBrand.fashionBrandsSearch)) - && !NluTools.stringContains(text, "(i have |there are |my )")) - ){ - possibleCMDs.add(CMD.FASHION); - possibleScore.add(1); index++; - Map pv = new HashMap(); //TODO: pass this down to avoid additional checking - FashionShopping fs = new FashionShopping().setup(input, pv); - fs.getParameters(); - possibleScore.set(index, possibleScore.get(index) + fs.getScore()); - possibleParameters.add(pv); - } - - //food - if (NluTools.stringContains(text, "^food$|" - + "(food|foodstuff|meal|breakfast|lunch|dinner|groceries|nourishments) .*\\b(order|buy|deliver|eat)|" - + "(" + Language.languageClasses_en + "|" + FoodClass.foodClasses_en + ") .*\\b(order|buy|deliver|eat)|" - + "(order|buy|deliver|eat|what) .*\\b(food|foodstuff|meal|breakfast|lunch|dinner|groceries|nourishments|eat)|" - + "(order|buy|deliver|eat) .*\\b(" + Language.languageClasses_en + "|" + FoodClass.foodClasses_en + ")|" - + "hunger|hungry|appetite|delivery|" - + "(from) .*\\b(\\w*restaurant(s|))") - || NluTools.stringContains(text, FoodItem.foodItems_en) - ){ - possibleCMDs.add(CMD.FOOD); - possibleScore.add(1); index++; - - Map pv = new HashMap(); //TODO: pass this down to avoid additional checking - AbstractParameterSearch aps = new AbstractParameterSearch() - .setParameters(PARAMETERS.FOOD_ITEM, PARAMETERS.FOOD_CLASS) - .setup(input, pv); - aps.getParameters(); - possibleScore.set(index, possibleScore.get(index) + aps.getScore()); - possibleParameters.add(pv); - } - //wikipedia/knowledgebase - if (NluTools.stringContains(text, "wiki|wikipedia|information|informations|" + if (NluTools.stringContains(text, "wiki|wikipedia|information|informations|knowledge base|" + "who (is|are|was|were) .*|" + "meaning of .*|what (is|was|are|were) .*|" + "when (is|was|are|were) .*|" @@ -211,6 +175,12 @@ public NluResult interpret(NluInput input) { String this_text = text; possibleCMDs.add(CMD.KNOWLEDGEBASE); possibleScore.add(1); index++; + + //score a bit extra if we start with certain words + if (NluTools.stringContains(this_text, "^((search |)(the |)(wikipedia|knowledge base))")){ + possibleScore.set(index, possibleScore.get(index)+1); + } + //kb search term String kb_search=""; if (NluTools.stringContains(this_text, "how (high|large) (is|are|was|were)|how old (is|are|was|were)|how many .* (are|has|were|had|live|lived) ")){ @@ -297,6 +267,42 @@ public NluResult interpret(NluInput input) { possibleParameters.add(pv); } + //fashion shopping + if (NluTools.stringContains(text, "(buy|fashion|shopping)") || + ((NluTools.stringContains(text, FashionItem.fashionItems_en) || NluTools.stringContains(text, FashionBrand.fashionBrandsSearch)) + && !NluTools.stringContains(text, "(i have |there are |my )")) + ){ + possibleCMDs.add(CMD.FASHION); + possibleScore.add(1); index++; + Map pv = new HashMap(); //TODO: pass this down to avoid additional checking + FashionShopping fs = new FashionShopping().setup(input, pv); + fs.getParameters(); + possibleScore.set(index, possibleScore.get(index) + fs.getScore()); + possibleParameters.add(pv); + } + + //food + if (NluTools.stringContains(text, "^food$|" + + "(food|foodstuff|meal|breakfast|lunch|dinner|groceries|nourishments) .*\\b(order|buy|deliver|eat)|" + + "(" + Language.languageClasses_en + "|" + FoodClass.foodClasses_en + ") .*\\b(order|buy|deliver|eat)|" + + "(order|buy|deliver|eat|what) .*\\b(food|foodstuff|meal|breakfast|lunch|dinner|groceries|nourishments|eat)|" + + "(order|buy|deliver|eat) .*\\b(" + Language.languageClasses_en + "|" + FoodClass.foodClasses_en + ")|" + + "hunger|hungry|appetite|delivery|" + + "(from) .*\\b(\\w*restaurant(s|))") + || NluTools.stringContains(text, FoodItem.foodItems_en) + ){ + possibleCMDs.add(CMD.FOOD); + possibleScore.add(1); index++; + + Map pv = new HashMap(); //TODO: pass this down to avoid additional checking + AbstractParameterSearch aps = new AbstractParameterSearch() + .setParameters(PARAMETERS.FOOD_ITEM, PARAMETERS.FOOD_CLASS) + .setup(input, pv); + aps.getParameters(); + possibleScore.set(index, possibleScore.get(index) + aps.getScore()); + possibleParameters.add(pv); + } + //flights if (NluTools.stringContains(text, "(flights|flight) (from|to)|flightsearch|" + "(search|searching|find|i need|show me|book|look|looking)\\b.* (flight|flights|plane|planes|fly|planeticket|flightticket)|" diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java b/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java index d47f98b1..12e4142b 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/ClientFunction.java @@ -119,7 +119,8 @@ public String extract(String input) { if (language.matches(LANGUAGES.DE)){ settings = "einstellung(en|)|setting(s|)|menue|option(en|)"; volume = "lautstaerke|(musik|radio|sound) (lauter|leiser|rauf(\\w+|)|runter(\\w+|)|aufdrehen)"; - media = "medienwiedergabe|medi(a|en)|player|musik|song|lied"; //NOTE: music is used here as well, make sure media is checked first + media = "medi(a|en)|player|musik|song|lied|titel|(medien|)wiedergabe|" //NOTE: music is used here as well, make sure media is checked first + + "^(naechste(\\w|)|vorherige(\\w|)|vor|zurueck|stop(pen|p|)|play|abspielen|lauter|leiser|fortsetzen|weiter)$"; alwaysOn = "always(-| |)on"; meshNode = "mesh(-| |)node"; clexi = "clexi"; @@ -128,7 +129,8 @@ public String extract(String input) { }else{ settings = "setting(s|)|menu(e|)|option(s|)"; volume = "volume|(music|radio|sound|player) (louder|quieter|up|down)|turn (up|down)"; - media = "media|player|music|song|track"; //NOTE: music is used here as well, make sure volume is checked first + media = "media|player|music|song|track|title|playback|" //NOTE: music is used here as well, make sure volume is checked first + + "^(next|previous|back|forward|stop|play|louder|quieter|resume)$"; alwaysOn = "always(-| |)on"; meshNode = "mesh(-| |)node"; clexi = "clexi"; diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/MediaControls.java b/src/main/java/net/b07z/sepia/server/assist/parameters/MediaControls.java index 27c4bfb3..c1d01984 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/MediaControls.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/MediaControls.java @@ -32,6 +32,8 @@ public static enum Type { close, next, previous, + resume, + repeat, volume_up, volume_down, volume_set @@ -47,6 +49,8 @@ public static enum Type { local_de.put("", "schließen"); local_de.put("", "weiter"); local_de.put("", "zurück"); + local_de.put("", "fortsetzen"); + local_de.put("", "wiederholen"); local_de.put("", "lauter"); local_de.put("", "leiser"); local_de.put("", "Lautstärke auf "); @@ -57,6 +61,8 @@ public static enum Type { local_en.put("", "close"); local_en.put("", "next"); local_en.put("", "back"); + local_en.put("", "resume"); + local_en.put("", "repeat"); local_en.put("", "volume up"); local_en.put("", "volume down"); local_en.put("", "volume to "); @@ -119,7 +125,7 @@ public String extract(String input) { return mediaControl; } - String play, pause, stop, close, next, previous, vol_up, vol_down, vol_set; + String play, pause, stop, close, next, previous, resume, repeat, vol_up, vol_down, vol_set; //German if (language.matches(LANGUAGES.DE)){ play = "(spiele(n|)|abspielen|starten|oeffne(n|)|play)(?!.*\\b(naechste(\\w|)|vorherige(\\w|)))"; @@ -128,6 +134,8 @@ public String extract(String input) { close = "schliesse(n|)"; next = "naechste(\\w|)|vorwaerts|vor|next"; previous = "zurueck|vorherige(\\w|)"; + resume = "weiter|fortsetzen"; + repeat = "wiederholen"; vol_set = "lautstaerke (\\w+ |)auf( |$)"; vol_up = "lauter|lautstaerke( .* | )(erhoehen|rauf|hoch|plus|groesser)|(vergroessern|erhoehen|rauf mit|hoch mit|(hoeher|groesser) machen)( der | )(lautstaerke)"; vol_down = "leiser|lautstaerke( .* | )(erniedrigen|runter|niedriger|minus|kleiner)|(verkleinern|erniedrigen|runter mit|(niedriger|kleiner) machen)( der | )(lautstaerke)"; @@ -140,6 +148,8 @@ public String extract(String input) { close = "close"; next = "next|forward"; previous = "back|previous"; + resume = "continue|resume"; + repeat = "repeat"; vol_set = "(set |)(the |)volume( \\w+ | )to( |$)"; vol_up = "louder|(turn |)(the |)volume( .* | )(up|increase|plus)|(increase|(turn |)up)( the | )volume"; vol_down = "quieter|(turn |)(the |)volume( .* | )(down|decrease|minus)|(decrease|(turn |)down)( the | )volume"; @@ -151,6 +161,8 @@ public String extract(String input) { close + "|" + next + "|" + previous + "|" + + resume + "|" + + repeat + "|" + vol_set + "|" + //before up, down! vol_up + "|" + vol_down + "|" + @@ -173,6 +185,12 @@ public String extract(String input) { //PREVIOUS }else if (NluTools.stringContains(extracted, previous)){ mediaControl = "<" + Type.previous + ">"; + //RESUME + }else if (NluTools.stringContains(extracted, resume)){ + mediaControl = "<" + Type.resume + ">"; + //REPEAT + }else if (NluTools.stringContains(extracted, repeat)){ + mediaControl = "<" + Type.repeat + ">"; //VOLUME SET - NOTE: do this before vol_up, vol_down, because user could say "increase volume to 11" }else if (NluTools.stringContains(extracted, vol_set)){ mediaControl = "<" + Type.volume_set + ">"; diff --git a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java index 0b0e1a32..0343eefd 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java @@ -11,6 +11,7 @@ import net.b07z.sepia.server.assist.interviews.InterviewData; import net.b07z.sepia.server.assist.parameters.Action; import net.b07z.sepia.server.assist.parameters.ClientFunction; +import net.b07z.sepia.server.assist.parameters.MediaControls; import net.b07z.sepia.server.assist.services.ServiceBuilder; import net.b07z.sepia.server.assist.services.ServiceInfo; import net.b07z.sepia.server.assist.services.ServiceInterface; @@ -76,11 +77,11 @@ public ServiceInfo getInfo(String language) { + "(.* |)always(-| |)on( .*|)|" + "(.* |)(musik|sound|radio) (lauter|leiser)( .*|)|" + "(.* |)lautstaerke( .*|)|" - + "(.* |)(medi(a|en)(-| |)player|medienwiedergabe)( .*|)|" + + "(.* |)medi(a|en)(-| |)(player|wiedergabe)( .*|)|" + "(.* |)(naechste(\\w|)|vorherige(\\w|)) (musik|song|lied|medien|media|titel)( .*|)|" - + "(naechste(\\w|)|vorherige(\\w|)|vor|zurueck|stop(pen|p|)|play|abspielen|lauter|leiser)|" - + "(.* |)(musik|song|lied|medien|media|titel|player) (anhalten|stoppen|stop(p|)|beenden|schliessen)( .*|)|" - + "(.* |)(stoppe|stop(p|)|schliesse)( .* | )(musik|song|lied|medien|media|titel|player|sound)( .*|)" + + "(naechste(\\w|)|vorherige(\\w|)|vor|zurueck|stop(pen|p|)|play|abspielen|lauter|leiser|fortsetzen|weiter)|" + + "(.* |)(musik|song|lied|medien|media|titel|player|wiedergabe) (anhalten|stoppen|stop(p|)|beenden|schliessen|fortsetzen|weiter|wiederholen)( .*|)|" + + "(.* |)(stoppe|stop(p|)|schliesse)( .* | )(musik|song|lied|medien|media|titel|player|sound|wiedergabe)( .*|)" + ")$", DE); info.setCustomTriggerRegX("^(" + "(.* |)open setting(s|)( .*|)|" @@ -89,29 +90,30 @@ public ServiceInfo getInfo(String language) { + "(.* |)(volume|turn (up|down))( .*|)|" + "(.* |)(media(-| |)player)( .*|)|" + "(.* |)(next|previous) (media|music|song|track|title)( .*|)|" - + "(next|previous|back|forward|stop|play|louder|quieter)|" - + "(.* |)(media|music|song|track|title|player|sound) (stop|close|end)( .*|)|" - + "(.* |)(stop|close|end)( .* | )(media|music|song|track|title|player|sound)( .*|)" + + "(next|previous|back|forward|stop|play|louder|quieter|resume)|" + + "(.* |)(media|music|song|track|title|player|sound|playback) (stop|close|end)( .*|)|" + + "(.* |)(stop|close|end|resume|continue|repeat)( .* | )(media|music|song|track|title|player|sound|playback)( .*|)" + ")$", EN); info.setCustomTriggerRegXscoreBoost(2); //boost service a bit to increase priority over similar ones //Parameters: //required - Parameter p1 = new Parameter(PARAMETERS.ACTION) - .setRequired(true) - .setQuestion("default_ask_action_0a"); - Parameter p2 = new Parameter(PARAMETERS.CLIENT_FUN) + Parameter p1 = new Parameter(PARAMETERS.CLIENT_FUN) .setRequired(true) .setQuestion("client_controls_ask_fun_0a"); //optional - Parameter p3 = new Parameter(PARAMETERS.DATA); - Parameter p4 = new Parameter(PARAMETERS.NUMBER); - Parameter p5 = new Parameter(PARAMETERS.MEDIA_CONTROLS); - + Parameter p2 = new Parameter(PARAMETERS.ACTION)//.setRequired(true) + .setQuestion("default_ask_action_0a"); + Parameter p3 = new Parameter(PARAMETERS.MEDIA_CONTROLS); + Parameter p4 = new Parameter(PARAMETERS.DATA); + Parameter p5 = new Parameter(PARAMETERS.NUMBER); info.addParameter(p1).addParameter(p2).addParameter(p3).addParameter(p4).addParameter(p5); + //either action or media_control must be given + info.getAtLeastOneOf("", p1, p5); + //Default answers info.addSuccessAnswer("ok_0b") .addFailAnswer("error_0a") @@ -131,6 +133,8 @@ public ServiceResult getResult(NluResult nluResult) { //get parameters Parameter actionP = nluResult.getRequiredParameter(PARAMETERS.ACTION); String action = actionP.getValueAsString().replaceAll("^<|>$", "").trim(); + Parameter mediaControlsP = nluResult.getOptionalParameter(PARAMETERS.MEDIA_CONTROLS, ""); + String mediaControls = mediaControlsP.getValueAsString().replaceAll("^<|>$", "").trim(); boolean isActionOpen = (action.equals(Action.Type.show.name()) || action.equals(Action.Type.on.name())); boolean isActionClose = (action.equals(Action.Type.remove.name()) || action.equals(Action.Type.off.name())); boolean isActionIncrease = (action.equals(Action.Type.increase.name()) || action.equals(Action.Type.add.name())); @@ -148,11 +152,11 @@ public ServiceResult getResult(NluResult nluResult) { }catch(Exception e){} } boolean isSettings = controlFun.equals(ClientFunction.Type.settings.name()); - boolean isVolume = controlFun.equals(ClientFunction.Type.volume.name()); boolean isAlwaysOn = controlFun.equals(ClientFunction.Type.alwaysOn.name()); boolean isMeshNode = controlFun.equals(ClientFunction.Type.meshNode.name()); boolean isClexi = controlFun.equals(ClientFunction.Type.clexi.name()); - boolean isMedia = controlFun.equals(ClientFunction.Type.media.name()); + boolean isMedia = controlFun.equals(ClientFunction.Type.media.name()); //NOTE: media and volume can exist simultaneously + boolean isVolume = controlFun.equals(ClientFunction.Type.volume.name()) || mediaControls.startsWith("volume_"); Parameter dataP = nluResult.getOptionalParameter(PARAMETERS.DATA, ""); String data = dataP.getValueAsString(); @@ -173,10 +177,26 @@ public ServiceResult getResult(NluResult nluResult) { }else{ //TODO: implement, ask or fail? } - }else if (isMedia){ - //media support - TODO: limited to stop currently - if (isActionClose){ + }else if (isMedia && !isVolume){ + //media support + if (mediaControls.equals(MediaControls.Type.close.name())){ + actionName = "close"; + }else if (mediaControls.equals(MediaControls.Type.stop.name())){ actionName = "stop"; + }else if (mediaControls.equals(MediaControls.Type.pause.name())){ + actionName = "pause"; + }else if (mediaControls.equals(MediaControls.Type.play.name())){ + actionName = "play"; + }else if (mediaControls.equals(MediaControls.Type.next.name())){ + actionName = "next"; + }else if (mediaControls.equals(MediaControls.Type.previous.name())){ + actionName = "previous"; + }else if (mediaControls.equals(MediaControls.Type.resume.name())){ + actionName = "resume"; + /*}else if (isActionOpen){ + + }else if (isActionClose){*/ + }else{ //TODO: implement, ask or fail? } @@ -198,7 +218,7 @@ public ServiceResult getResult(NluResult nluResult) { } } //volume support - if (!num.isEmpty() && (isActionEdit || isActionIncrease || isActionDecrease)){ + if (!num.isEmpty() && (isActionEdit || isActionIncrease || isActionDecrease || mediaControls.startsWith("volume_"))){ long vol = Converters.obj2LongOrDefault(num, -1l); if (vol > 11){ api.setCustomAnswer("client_controls_volume_exceeded_0a"); @@ -206,14 +226,14 @@ public ServiceResult getResult(NluResult nluResult) { api.setCustomAnswer("client_controls_volume_eleven_0a"); } actionName = ("volume;;" + num); //we take the shortcut here =) - }else if (num.isEmpty() && isActionEdit){ + }else if (num.isEmpty() && (mediaControls.equals(MediaControls.Type.volume_set.name()) || isActionEdit)){ //abort with generic question api.setIncompleteAndAsk(PARAMETERS.NUMBER, "default_ask_parameter_0b"); ServiceResult result = api.buildResult(); return result; - }else if (isActionIncrease){ + }else if (isActionIncrease || mediaControls.equals(MediaControls.Type.volume_up.name())){ actionName = "up"; - }else if (isActionDecrease){ + }else if (isActionDecrease || mediaControls.equals(MediaControls.Type.volume_down.name())){ actionName = "down"; }else{ //TODO: implement, ask or fail? diff --git a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java index 728d7868..89a92f2a 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java @@ -7,6 +7,7 @@ import org.json.simple.JSONObject; import net.b07z.sepia.server.assist.assistant.CmdBuilder; +import net.b07z.sepia.server.assist.assistant.LANGUAGES; import net.b07z.sepia.server.assist.data.Card; import net.b07z.sepia.server.assist.data.Card.ElementType; import net.b07z.sepia.server.assist.data.Parameter; @@ -19,6 +20,7 @@ import net.b07z.sepia.server.assist.services.ServiceInfo; import net.b07z.sepia.server.assist.services.ServiceInterface; import net.b07z.sepia.server.assist.services.ServiceResult; +import net.b07z.sepia.server.assist.tools.ITunesApi; import net.b07z.sepia.server.assist.tools.SpotifyApi; import net.b07z.sepia.server.assist.services.ServiceInfo.Content; import net.b07z.sepia.server.assist.services.ServiceInfo.Type; @@ -87,6 +89,7 @@ public ServiceInfo getInfo(String language) { .addCustomAnswer("what_artist", "music_ask_1b") .addCustomAnswer("what_playlist", "music_ask_1c") .addCustomAnswer("no_music_match", "music_0b") + .addCustomAnswer("missing_api_key", "default_no_access_0a") ; return info; @@ -103,8 +106,6 @@ public ServiceResult getResult(NluResult nluResult) { String service = serviceP.getValueAsString().replaceAll("^<|>$", "").trim(); String serviceLocal = (String) serviceP.getDataFieldOrDefault(InterviewData.VALUE_LOCAL); - boolean isSpotifyService = service.equals(MusicService.Service.spotify.name()) || service.equals(MusicService.Service.spotify_link.name()); - Parameter genreP = nluResult.getOptionalParameter(PARAMETERS.MUSIC_GENRE, ""); String genre = genreP.getValueAsString(); @@ -158,16 +159,24 @@ public ServiceResult getResult(NluResult nluResult) { Platform platform = CLIENTS.getPlatform(nluResult.input.clientInfo); /* if (platform.equals(Platform.browser)){ - }else if (platform.equals(Platform.android)){ - }else if (platform.equals(Platform.ios)){ - }else if (platform.equals(Platform.windows)){ - } */ + //Default music app in client + if (service.isEmpty()){ + Object defaultClientService = nluResult.input.getCustomDataObject("defaultMusicApp"); + if (defaultClientService != null){ + service = (String) defaultClientService; + } + //System.out.println("defaultMusicApp: " + service); //DEBUG + } + boolean isSpotifyService = service.equals(MusicService.Service.spotify.name()) || service.equals(MusicService.Service.spotify_link.name()); + boolean isAppleMusic = service.equals(MusicService.Service.apple_music.name()) || service.equals(MusicService.Service.apple_music_link.name()); + boolean requiresUri = service.contains("_link"); + //Basically this service cannot fail here ... only inside client ... but we'll also try to get some more data: String foundTrack = ""; @@ -181,8 +190,8 @@ public ServiceResult getResult(NluResult nluResult) { String cardIconUrl = Config.urlWebImages + "cards/music_default.png"; String cardBrand = "default"; - //Use YouTube for now - if (service.equals(MusicService.Service.youtube.name()) || (service.isEmpty() && Config.spotifyApi == null)){ + //Use YouTube for URI + if (service.equals(MusicService.Service.youtube.name()) || service.isEmpty()){ //Icon cardIconUrl = Config.urlWebImages + "brands/youtube-logo.png"; cardBrand = "YouTube"; @@ -230,8 +239,8 @@ public ServiceResult getResult(NluResult nluResult) { foundUri = "https://www.youtube.com/results?search_query=" + q; } - //Spotify API (currently used when service is Spotify or platform can only handle URIs) - }else if (isSpotifyService || (service.isEmpty() && Config.spotifyApi != null)){ + //Spotify API + }else if (isSpotifyService){ //we need the API (in early version it was possible to call it without registration) if (Config.spotifyApi != null){ //Icon @@ -292,8 +301,77 @@ public ServiceResult getResult(NluResult nluResult) { //foundUri = foundUri + ":play"; //not supported? breaks link? } } + }else{ + //We need an URI via API call but got none? + if (requiresUri){ + //add some info here about missing key + api.setCustomAnswer("default_no_access_0a"); + + //add button that links to help + api.addAction(ACTIONS.BUTTON_IN_APP_BROWSER); + api.putActionInfo("url", "https://github.com/SEPIA-Framework/sepia-docs/wiki/API-keys"); + api.putActionInfo("title", "Info: API-Keys"); + + //all clear? + api.setStatusOkay(); + + //finally build the API_Result + ServiceResult result = api.buildResult(); + return result; + } + } + + //Apple Music + }else if(isAppleMusic){ + //Icon + cardIconUrl = Config.urlWebImages + "brands/apple-music-logo.png"; + cardBrand = "Apple Music"; + //Search (we use the open iTunes API instead of Apple Music API (because it is too hard to get an Apple Music key) + ITunesApi iTunesApi = new ITunesApi((nluResult.language.equals(LANGUAGES.DE))? "DE" : "US"); //TODO: add more country codes if we need them ... + JSONObject iTunesBestItem = iTunesApi.searchBestMusicItem(song, artist, album, playlistName, genre); + foundUri = JSON.getString(iTunesBestItem, "uri"); + foundType = JSON.getString(iTunesBestItem, "type"); + //get URI and build Card data - TODO: this code is mostly identical to spotify card ... we can combine it ... + if (Is.notNullOrEmpty(foundUri)){ + //get info by type + if (foundType.equals(ITunesApi.TYPE_TRACK)){ + foundTrack = JSON.getString(iTunesBestItem, "name"); + foundArtist = JSON.getString(iTunesBestItem, "primary_artist"); + foundAlbum = JSON.getString(iTunesBestItem, "album"); + cardTitle = "Song: " + foundTrack; + cardSubtitle = (foundAlbum.isEmpty())? foundArtist : (foundArtist + ", " + foundAlbum); + //add play tag to URI + foundUri = foundUri + "&mt=1&app=music"; + + }else if (foundType.equals(ITunesApi.TYPE_ALBUM)){ + foundArtist = JSON.getString(iTunesBestItem, "primary_artist"); + foundAlbum = JSON.getString(iTunesBestItem, "name"); + cardTitle = "Album: " + foundAlbum; + cardSubtitle = foundArtist; + //add play tag to URI + foundUri = foundUri + "&mt=1&app=music"; + + }else if (foundType.equals(ITunesApi.TYPE_ARTIST)){ + foundArtist = JSON.getString(iTunesBestItem, "name"); + JSONArray genres = JSON.getJArray(iTunesBestItem, "genres"); + String genresString = ""; + if (Is.notNullOrEmpty(genres)){ + for (int i=0; i headers = new HashMap<>(); + headers.put("Accept", "application/json"); + headers.put("Content-Type", "application/json"); + //headers.put("Authorization", authString); + + //Call + //System.out.println("URL: " + url); //DEBUG + long tic = System.currentTimeMillis(); + JSONObject res = Connectors.httpGET(url, null, headers); + Statistics.addExternalApiHit("iTunesApi searchBestMusicItem"); + Statistics.addExternalApiTime("iTunesApi searchBestMusicItem", tic); + + //Get first item + if (Connectors.httpSuccess(res)){ + //System.out.println("Result: " + res.toJSONString()); //DEBUG + + JSONArray items = null; + items = JSON.getJArray(res, new String[]{"results"}); + + if (Is.nullOrEmpty(items)){ + if (Is.notNullOrEmpty(res) && res.containsKey("error")){ + JSONObject error = JSON.getJObject(res, "error"); + JSON.put(error, "type", "error"); + return error; + }else{ + return JSON.make( + "message", "no item found for query", + "query", term, + "type", "no_match", + "status", 200 + ); + } + }else{ + //Iterate results + double bestMatchScore = 0.0d; + JSONObject bestMatch = null; + for (Object o : items){ + JSONObject item = (JSONObject) o; + + String kind = JSON.getString(item, "kind"); + String wrapperType = JSON.getString(item, "wrapperType"); + String trackName = JSON.getString(item, "trackName"); + String artistName = JSON.getString(item, "artistName"); + String collectionName = JSON.getString(item, "collectionName"); + String primaryGenreName = JSON.getString(item, "primaryGenreName"); + + //SONG + if (entity.equals(ENTITY_SONG) && (kind.equals("song") || wrapperType.equals("track"))){ + //If we have only a song name trust the first result + if (Is.nullOrEmpty(artist) && Is.nullOrEmpty(album)){ + return JSON.make( + "type", TYPE_TRACK, + "name", trackName, + "uri", JSON.getString(item, "trackViewUrl"), + "primary_artist", artistName, + "album", collectionName + ); + + //If we have an artist or album score the result + }else{ + double score = 0.0d; + if (Is.notNullOrEmpty(artist)){ + score += scoreEntry(artist, artistName); + } + if (Is.notNullOrEmpty(album)){ + score += scoreEntry(album, collectionName); + } + //System.out.println("Score 2: " + score); //DEBUG + if (score > bestMatchScore){ + bestMatchScore = score; + bestMatch = JSON.make( + "type", TYPE_TRACK, + "name", trackName, + "uri", JSON.getString(item, "trackViewUrl"), + "primary_artist", artistName, + "album", collectionName + ); + } + } + + //ALBUM + }else if (entity.equals(ENTITY_ALBUM) && (kind.equals("album") || wrapperType.equals("collection"))){ + double score = 0.0d; + if (Is.notNullOrEmpty(artist)){ + score += scoreEntry(artist, artistName); + }else{ + //If we have only an album name trust the first result + return JSON.make( + "type", TYPE_ALBUM, + "name", collectionName, + "uri", JSON.getString(item, "collectionViewUrl"), + "primary_artist", artistName, + "total_tracks", JSON.getIntegerOrDefault(item, "trackCount", -1) + ); + } + //System.out.println("Score 2: " + score); //DEBUG + if (score > bestMatchScore){ + bestMatchScore = score; + bestMatch = JSON.make( + "type", TYPE_ALBUM, + "name", collectionName, + "uri", JSON.getString(item, "collectionViewUrl"), + "primary_artist", artistName, + "total_tracks", JSON.getIntegerOrDefault(item, "trackCount", -1) + ); + } + + //ARTIST + }else if (entity.equals(ENTITY_ARTIST) && (kind.equals("artist") || wrapperType.equals("artist"))){ + //We have to trust the first result + return JSON.make( + "type", TYPE_ARTIST, + "name", artistName, + "uri", JSON.getString(item, "artistLinkUrl"), + "genres", primaryGenreName + ); + } + } + if (bestMatch != null){ + return bestMatch; + }else{ + return JSON.make( + "message", "no item found for query", + "query", term, + "type", "no_match", + "status", 200 + ); + } + /* + JSONObject firstItem = JSON.getJObject(items, 0); + if (firstItem != null){ + String resType = JSON.getString(firstItem, "type"); + if (resType.equals(TYPE_PLAYLIST)){ + return JSON.make( + "type", TYPE_PLAYLIST, + "name", JSON.getString(firstItem, "name"), + "uri", JSON.getString(firstItem, "uri"), + "owner_display_name", JSON.getObject(firstItem, new String[]{"owner", "display_name"}), + "total_tracks", JSON.getObject(firstItem, new String[]{"tracks", "total"}) + ); + } + */ + } + } + return res; + + }catch (Exception e){ + Statistics.addExternalApiHit("iTunesApi-error searchBestMusicItem"); + return JSON.make("error", e.getMessage(), "status", 500); + } + } + + //Score an entry by comparing it to the search term (converted to lowerCase) + private static double scoreEntry(String search, String found){ + //System.out.println(search + " - " + found); //DEBUG + if (Is.nullOrEmpty(found)){ + return 0.0d; + }else{ + double score = 1.0d - (StringCompare.editDistance(search.toLowerCase(), found.toLowerCase()) / (double) Math.min(search.length(), found.length())); + //System.out.println("Score: " + score); //DEBUG + return score; + } + } +} diff --git a/src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java b/src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java index 4f1117a9..3625f1d1 100644 --- a/src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java +++ b/src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java @@ -51,11 +51,32 @@ public static void main(String[] args) { tests.put("Set the volume to 11", JSON.make("c", CC, "p", JSON.make(PARAMETERS.MEDIA_CONTROLS, ".*"))); tests.put("Set the volume to eleven", JSON.make("c", CC)); tests.put("Play the next song", JSON.make("c", CC, "p", JSON.make(PARAMETERS.MEDIA_CONTROLS, ".*"))); - tests.put("Next", JSON.make("c", CC)); - tests.put("Back", JSON.make("c", CC)); + tests.put("Next", JSON.make("c", CC, "p", JSON.make(PARAMETERS.CLIENT_FUN, ".*"))); + tests.put("Back", JSON.make("c", CC, "p", JSON.make(PARAMETERS.CLIENT_FUN, ".*"))); tests.put("Stop music", JSON.make("c", CC, "p", JSON.make(PARAMETERS.MEDIA_CONTROLS, ".*"))); tests.put("Play music", JSON.make("c", M)); tests.put("Play stop and go by Fake Artist", JSON.make("c", M)); + tests.put("What is Lahmacun", JSON.make("c", CMD.KNOWLEDGEBASE)); + tests.put("What is the best way to work", JSON.make("c", CMD.DIRECTIONS)); + + for (Map.Entry t : tests.entrySet()){ + String shouldBeCmd = JSON.getString(t.getValue(), "c"); + JSONObject parameterRegExpMatches = JSON.getJObject(t.getValue(), "p"); + testSentenceViaKeywordAnalyzer(doAnswer, t.getKey(), language, shouldBeCmd, parameterRegExpMatches); + } + + language = LANGUAGES.DE; + tests = new HashMap<>(); + tests.put("Setze die Lautstärke auf 11", JSON.make("c", CC, "p", JSON.make(PARAMETERS.MEDIA_CONTROLS, ".*"))); + tests.put("Lautstärke auf elf", JSON.make("c", CC)); + tests.put("Starte den nächsten Song", JSON.make("c", CC, "p", JSON.make(PARAMETERS.MEDIA_CONTROLS, ".*"))); + tests.put("Weiter", JSON.make("c", CC, "p", JSON.make(PARAMETERS.CLIENT_FUN, ".*"))); + tests.put("Zurück", JSON.make("c", CC, "p", JSON.make(PARAMETERS.CLIENT_FUN, ".*"))); + tests.put("Musik anhalten", JSON.make("c", CC, "p", JSON.make(PARAMETERS.MEDIA_CONTROLS, ".*"))); + tests.put("Musik spielen", JSON.make("c", M)); + tests.put("Spiele Stop und Weiter von Fake Künstler", JSON.make("c", M)); + tests.put("Was ist Lahmacun", JSON.make("c", CMD.KNOWLEDGEBASE)); + tests.put("Was ist der beste Weg zur Arbeit", JSON.make("c", CMD.DIRECTIONS)); for (Map.Entry t : tests.entrySet()){ String shouldBeCmd = JSON.getString(t.getValue(), "c"); diff --git a/src/test/java/net/b07z/sepia/server/assist/tools/Test_ITunesApi.java b/src/test/java/net/b07z/sepia/server/assist/tools/Test_ITunesApi.java new file mode 100644 index 00000000..596cad7c --- /dev/null +++ b/src/test/java/net/b07z/sepia/server/assist/tools/Test_ITunesApi.java @@ -0,0 +1,61 @@ +package net.b07z.sepia.server.assist.tools; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.server.Start; +import net.b07z.sepia.server.core.tools.Debugger; + +public class Test_ITunesApi { + + public static void main(String[] args) { + + //load custom config + Start.loadSettings(new String[]{"--test"}); + + ITunesApi api = new ITunesApi("US"); + + JSONObject s = api.searchBestMusicItem("Paradise City", "Guns n Roses", "Appetite For Destruction", "", ""); + System.out.println(s.toJSONString()); + + Debugger.sleep(500); + + s = api.searchBestMusicItem("Paradise City", "Guns n Roses", "Greatest Hits", "", ""); + System.out.println(s.toJSONString()); + + Debugger.sleep(500); + + s = api.searchBestMusicItem("", "Guns n Roses", "Appetite For Destruction", "", ""); + System.out.println(s.toJSONString()); + + Debugger.sleep(500); + + s = api.searchBestMusicItem("", "Guns n Roses", "", "", ""); + System.out.println(s.toJSONString()); + + Debugger.sleep(500); + + s = api.searchBestMusicItem("", "", "", "", "Rock"); + System.out.println(s.toJSONString()); + + Debugger.sleep(500); + + s = api.searchBestMusicItem("", "", "", "Party", ""); + System.out.println(s.toJSONString()); + + Debugger.sleep(500); + + s = api.searchBestMusicItem("Foxy Lady", "Jimi Hendrix", "", "", ""); //note the typo in foxy (not foxey) + System.out.println(s.toJSONString()); + + Debugger.sleep(500); + + s = api.searchBestMusicItem("Immer wieder", "Selig", "", "", ""); + System.out.println(s.toJSONString()); + + Debugger.sleep(500); + + s = api.searchBestMusicItem("Wichtig", "Selig", "", "", ""); + System.out.println(s.toJSONString()); + } + +} From 761756ffb8156a5f9ae71fe69ac18492c500e75d Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Mon, 20 May 2019 00:48:20 +0200 Subject: [PATCH 24/27] fixed a bug in ITunesApi and music NLU --- .../sepia/server/assist/parameters/MusicArtist.java | 9 +++++++-- .../net/b07z/sepia/server/assist/parameters/Song.java | 3 ++- .../net/b07z/sepia/server/assist/tools/ITunesApi.java | 10 ++++++++-- .../server/assist/parameters/Test_MusicParameters.java | 1 + .../b07z/sepia/server/assist/tools/Test_ITunesApi.java | 8 +++++++- 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java index d025a83f..092cc0a2 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicArtist.java @@ -74,7 +74,10 @@ public String extract(String input) { if (this.language.matches(LANGUAGES.DE)){ if (!optimizedInput.matches(".*\\b(von|vom) .+ (nach|bis)\\b.*") || NluTools.stringContains(optimizedInput, "spiel(en|e|)|start(en|e|)|oeffne(n|)")){ - creator = NluTools.stringFindFirst(optimizedInput, "(von|vom) .*"); + //one or two matches (e.g. castles made of sand by Jimi Hendrix) + String m1 = NluTools.stringFindFirst(optimizedInput, "(von|vom) .*"); + String m2 = (!m1.isEmpty())? NluTools.stringFindFirst(m1.replaceFirst("\\w+ ", ""), "(von|vom) .*") : ""; + creator = (!m2.isEmpty())? m2 : m1; creator = creator.replaceFirst("^(von|vom) ", ""); creator = creator.replaceFirst("^(der|die|das|dem|den|einer|eine|einem)\\b", "").trim(); creator = creator.replaceFirst("^(artist|musiker|kuenstler)\\b", "").trim(); @@ -85,7 +88,9 @@ public String extract(String input) { }else{ if (!optimizedInput.matches(".*\\b(from|of|by) .+ (to|till|until)\\b.*") || NluTools.stringContains(optimizedInput, "play|start|open")){ - creator = NluTools.stringFindFirst(optimizedInput, "(from|of|by) .*"); + String m1 = NluTools.stringFindFirst(optimizedInput, "(from|of|by) .*"); + String m2 = (!m1.isEmpty())? NluTools.stringFindFirst(m1.replaceFirst("\\w+ ", ""), "(from|of|by) .*") : ""; + creator = (!m2.isEmpty())? m2 : m1; creator = creator.replaceFirst("^(from|of|by) ", ""); creator = creator.replaceFirst("^(the|a|an)\\b", "").trim(); creator = creator.replaceFirst("^(artist|musician)\\b", "").trim(); diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java b/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java index 2a949738..53cd21bc 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/Song.java @@ -70,8 +70,9 @@ public String extract(String input) { optimizedInput = ParameterResult.cleanInputOfFoundParameter(nluInput, PARAMETERS.MUSIC_ALBUM, prMusicAlbum, optimizedInput); } - String song = RegexParameterSearch.get_startable(optimizedInput, this.language); + //TODO: this will fail for 'play castles made of sand by Jimi Hendrix' + //some filters if (song.contains("playlist")){ song = ""; diff --git a/src/main/java/net/b07z/sepia/server/assist/tools/ITunesApi.java b/src/main/java/net/b07z/sepia/server/assist/tools/ITunesApi.java index cc5728e5..05abe5ff 100644 --- a/src/main/java/net/b07z/sepia/server/assist/tools/ITunesApi.java +++ b/src/main/java/net/b07z/sepia/server/assist/tools/ITunesApi.java @@ -231,11 +231,13 @@ public JSONObject searchBestMusicItem(String track, String artist, String album, //ARTIST }else if (entity.equals(ENTITY_ARTIST) && (kind.equals("artist") || wrapperType.equals("artist"))){ //We have to trust the first result + JSONArray genres = new JSONArray(); + JSON.add(genres, primaryGenreName); return JSON.make( "type", TYPE_ARTIST, "name", artistName, "uri", JSON.getString(item, "artistLinkUrl"), - "genres", primaryGenreName + "genres", genres ); } } @@ -279,7 +281,11 @@ private static double scoreEntry(String search, String found){ if (Is.nullOrEmpty(found)){ return 0.0d; }else{ - double score = 1.0d - (StringCompare.editDistance(search.toLowerCase(), found.toLowerCase()) / (double) Math.min(search.length(), found.length())); + found = found.replaceAll("(,|\\.|'|´|`|&|!|\\?)", " ").replaceAll("\\s+", " ").trim().toLowerCase(); + search = search.toLowerCase(); + double score = 1.0d - (StringCompare.editDistance(search, found) / (double) Math.min(search.length(), found.length())); + if (score < 0.0d) score = 0.0d; + if (found.contains(search)) score += (search.split(" ").length / (double) found.split(" ").length); //System.out.println("Score: " + score); //DEBUG return score; } diff --git a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java index 2428f8a4..65c1814a 100644 --- a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java +++ b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_MusicParameters.java @@ -105,6 +105,7 @@ public static void main(String[] args) { testMusicParameters("stop the next song", "[, , , , , ]", lang); testMusicParameters("play next song on the playlist", "[, , , , , ]", lang); testMusicParameters("play something from the Chill playlist", "[, , , , Chill, ]", lang); + testMusicParameters("play castles made of sand by Jimi Hendrix on Apple Music", "[, , Jimi Hendrix, castles made of sand, , ]", lang); System.out.println("-----------"); diff --git a/src/test/java/net/b07z/sepia/server/assist/tools/Test_ITunesApi.java b/src/test/java/net/b07z/sepia/server/assist/tools/Test_ITunesApi.java index 596cad7c..f40d8276 100644 --- a/src/test/java/net/b07z/sepia/server/assist/tools/Test_ITunesApi.java +++ b/src/test/java/net/b07z/sepia/server/assist/tools/Test_ITunesApi.java @@ -18,7 +18,7 @@ public static void main(String[] args) { System.out.println(s.toJSONString()); Debugger.sleep(500); - + /* s = api.searchBestMusicItem("Paradise City", "Guns n Roses", "Greatest Hits", "", ""); System.out.println(s.toJSONString()); @@ -56,6 +56,12 @@ public static void main(String[] args) { s = api.searchBestMusicItem("Wichtig", "Selig", "", "", ""); System.out.println(s.toJSONString()); + + Debugger.sleep(500); + */ + + s = api.searchBestMusicItem("Castles made", "Jimi Hendrix", "", "", ""); + System.out.println(s.toJSONString()); } } From 185bd84d3a640f5f61fbf58173b36528fa6796be Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Mon, 20 May 2019 23:56:47 +0200 Subject: [PATCH 25/27] implemented tracking of recently triggered pro-active events --- .../server/assist/events/EventsManager.java | 51 ++++++++++++++++--- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/b07z/sepia/server/assist/events/EventsManager.java b/src/main/java/net/b07z/sepia/server/assist/events/EventsManager.java index 747e621f..1df8a106 100644 --- a/src/main/java/net/b07z/sepia/server/assist/events/EventsManager.java +++ b/src/main/java/net/b07z/sepia/server/assist/events/EventsManager.java @@ -113,14 +113,47 @@ public static JSONObject buildCommonEvents(NluInput input){ //random messages to entertain the user if (localTimeIsKnown){ - addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.randomMotivationMorning.name(), ((10l - hod) * 60l - (30l - moh)) * 60l * 1000l, - EventDialogs.getMessage(EventDialogs.Type.randomMotivationMorning, input.language)); - addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.haveLunch.name(), ((13l - hod) * 60l - moh) * 60l * 1000l, - EventDialogs.getMessage(EventDialogs.Type.haveLunch, input.language)); - addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.makeCoffebreak.name(), ((16l - hod) * 60l - moh) * 60l * 1000l, - EventDialogs.getMessage(EventDialogs.Type.makeCoffebreak, input.language)); - addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.beActive.name(), ((18l - hod) * 60l - moh) * 60l * 1000l, - EventDialogs.getMessage(EventDialogs.Type.beActive, input.language)); + //check if the client sent a list of recently triggered events + Object recentPaeObj = input.getCustomDataObject("recentPAE"); + JSONObject recentPAE = null; + if (recentPaeObj != null){ + try{ + recentPAE = (JSONObject) recentPaeObj; + //System.out.println("recentPAE: " + recentPAE.toJSONString()); //DEBUG + }catch (Exception e){ + Debugger.println("EventsManager - failed to parse 'recentPAE': " + e.getMessage(), 1); + Debugger.printStackTrace(e, 3); + recentPAE = null; + } + } + //Schedule new ones: + long oldEnough = 1000l * 60l * 60l * 12l; //12h + // + //randomMotivationMorning + if (recentPAE == null || JSON.getLongOrDefault(recentPAE, EventDialogs.Type.randomMotivationMorning.name(), oldEnough) >= oldEnough){ + addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.randomMotivationMorning.name(), ((10l - hod) * 60l - (30l - moh)) * 60l * 1000l, + EventDialogs.getMessage(EventDialogs.Type.randomMotivationMorning, input.language)); + } + //haveLunch + if (recentPAE == null || JSON.getLongOrDefault(recentPAE, EventDialogs.Type.haveLunch.name(), oldEnough) >= oldEnough){ + addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.haveLunch.name(), ((13l - hod) * 60l - moh) * 60l * 1000l, + EventDialogs.getMessage(EventDialogs.Type.haveLunch, input.language)); + } + //makeCoffebreak + if (recentPAE == null || JSON.getLongOrDefault(recentPAE, EventDialogs.Type.makeCoffebreak.name(), oldEnough) >= oldEnough){ + addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.makeCoffebreak.name(), ((16l - hod) * 60l - moh) * 60l * 1000l, + EventDialogs.getMessage(EventDialogs.Type.makeCoffebreak, input.language)); + } + //beActive + if (recentPAE == null || JSON.getLongOrDefault(recentPAE, EventDialogs.Type.beActive.name(), oldEnough) >= oldEnough){ + addScheduledEntertainMessage(actionBuilder, EventDialogs.Type.beActive.name(), ((18l - hod) * 60l - moh) * 60l * 1000l, + EventDialogs.getMessage(EventDialogs.Type.beActive, input.language)); + } + //customTestEvent + /*if (recentPAE == null || JSON.getLongOrDefault(recentPAE, "customTestEvent", 60000) >= 60000){ + System.out.println("sent recentPAE customTestEvent"); //DEBUG + addScheduledEntertainMessage(actionBuilder, "customTestEvent", 20000l, "Hello, this is a test message"); + }*/ } //news buttons @@ -284,6 +317,7 @@ public static void addScheduledEntertainMessage(ServiceBuilder actionBuilder, St actionBuilder.putActionInfo("info", "entertainWhileIdle"); //TODO: one could distinguish messages that are only triggered when the app is not in foreground vs. important notes etc ... actionBuilder.putActionInfo("eventId", eventId); actionBuilder.putActionInfo("triggerIn", triggerDelayMS); + actionBuilder.putActionInfo("created", System.currentTimeMillis()); actionBuilder.putActionInfo("text", message); //actionBuilder.actionInfo_put_info("options", "inputHidden"); } @@ -295,6 +329,7 @@ public static void addScheduledProActiveMessage(ServiceBuilder actionBuilder, St actionBuilder.putActionInfo("info", "proActiveNote"); //TODO: one could distinguish messages that are only triggered when the app is not in foreground vs. important notes etc ... actionBuilder.putActionInfo("eventId", eventId); actionBuilder.putActionInfo("triggerIn", triggerDelayMS); + actionBuilder.putActionInfo("created", System.currentTimeMillis()); actionBuilder.putActionInfo("text", message); //actionBuilder.actionInfo_put_info("options", "inputHidden"); } From 9f6607bab4db4407823d659a3d9e902d81aac507 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Sun, 26 May 2019 21:27:11 +0200 Subject: [PATCH 26/27] youTube embedded support; fixed list param. (and x and x); link card auto trigger; --- .../interpreters/NluKeywordAnalyzerDE.java | 4 +- .../interpreters/NluKeywordAnalyzerEN.java | 4 +- .../server/assist/interpreters/NluTools.java | 6 +- .../server/assist/parameters/Action.java | 2 +- .../assist/parameters/ClientFunction.java | 42 ++++++++ .../server/assist/parameters/ListSubType.java | 4 +- .../assist/parameters/MusicService.java | 18 +++- .../assist/parameters/SearchSection.java | 25 ++++- .../assist/parameters/WebSearchEngine.java | 14 ++- .../assist/parameters/WebSearchRequest.java | 32 ++++-- .../assist/services/ClientControls.java | 14 ++- .../assist/services/DirectionsGoogleMaps.java | 11 +- .../sepia/server/assist/services/Lists.java | 11 +- .../assist/services/LocationSearchBasic.java | 6 +- .../server/assist/services/MusicSearch.java | 66 ++++++++---- .../assist/services/WebsearchBasic.java | 77 ++++++++----- .../assist/parameters/Test_ListItem.java | 6 ++ .../assist/services/ServiceTestTools.java | 101 ++++++++++++++++++ .../assist/services/Test_ClientControls.java | 95 +--------------- .../assist/services/Test_WebSearch.java | 78 ++++++++++++++ 20 files changed, 436 insertions(+), 180 deletions(-) create mode 100644 src/test/java/net/b07z/sepia/server/assist/services/ServiceTestTools.java create mode 100644 src/test/java/net/b07z/sepia/server/assist/services/Test_WebSearch.java diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java index 5e02936e..7e19cf63 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerDE.java @@ -223,11 +223,11 @@ public NluResult interpret(NluInput input) { //web search if (NluTools.stringContains(text, "(websuche|websearch|web suche|" + "(durchsuche|suche|schau|finde|zeig)( mir|)( mal| bitte|)( bitte| mal|)( im| das) (web|internet))|" - + "^google|^bing|^yahoo|^duck duck|^duck duck go|" + + "^google|^bing|^yahoo|^duck duck|^duck duck go|^youtube|" + "^(bild(ern|er|)|rezept(en|e|)|video(s|)|movie(s|)|film(en|e|)|\\w*(-|)aktie(n|)|aktien(wert|kurs)|buecher(n|)|buch)|" + "(wie|wo) (ist|steht|stehen) (der|die) (aktienkurs|aktienwert|aktie(n|)|kurs|wert) (von|vom|der)|(wie|wo) (steht|stehen) .*aktie(n|)|" + "(durchsuche|suche|schau|finde|zeig)( | .* )(im (web|internet))|" - + "(durchsuche|suche|schau|finde|zeig)( | .* )(mit|per|via|auf|ueber|mittels|bei) (google|bing|duck duck go|duck duck|yahoo)|" + + "(durchsuche|suche|schau|finde|zeig)( | .* )(mit|per|via|auf|ueber|mittels|bei) (google|bing|duck duck go|duck duck|yahoo|youtube)|" + "(durchsuche|suche|schau|finde|zeig)( | .* )(bilder(n|)|rezepte(n|)|video(s|)|youtube|movies|filme(n|)|buecher(n|)|(-|)aktie(n|)|aktien(wert|kurs))") //|| NLU_Tools.stringContains(text, "suche(n|)|finde(n|)|zeig(en|)") //|| (NLU_Tools.stringContains(text, "suche(n|)|finde(n|)|zeig(en|)") diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java index 3bb52d60..17105b7c 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluKeywordAnalyzerEN.java @@ -217,11 +217,11 @@ public NluResult interpret(NluInput input) { //web search //TODO: optimize exceptions if (NluTools.stringContains(text, "websearch|web search|search the web|" - + "^google|^bing|^yahoo|^duck duck|^duck duck go|" + + "^google|^bing|^yahoo|^duck duck|^duck duck go|^youtube|" + "^(picture(s|)|recipe(s|)|video(s|)|movie(s|)|film(s|)|share(s|)|stock(s|)|book(s|))|" + "what is the (stock|share) (value|price)|" + "(search|find|show|look|searching|looking)( | .* )((on |)the (web|internet))|" - + "(search|find|show|look|searching|looking)( | .* )(with|on|via|per|over|by) (google|bing|duck duck go|duck duck|yahoo)|" + + "(search|find|show|look|searching|looking)( | .* )(with|on|via|per|over|by) (google|bing|duck duck go|duck duck|yahoo|youtube)|" + "(search|find|show|look|searching|looking)( | .* )(picture(s|)|recipe(s|)|video(s|)|youtube|book(s|)|share(s|)|stock(s|))") //|| NLU_Tools.stringContains(text, "search|find|show|look for|searching for|looking for") //|| (NLU_Tools.stringContains(text, "search|find|show") diff --git a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluTools.java b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluTools.java index ada2eb48..3fca714d 100644 --- a/src/main/java/net/b07z/sepia/server/assist/interpreters/NluTools.java +++ b/src/main/java/net/b07z/sepia/server/assist/interpreters/NluTools.java @@ -191,10 +191,10 @@ public static String getStringAndNextWords(String input, String start, int N){ /** * Capitalize first letter of all words in a sentence. - * @param low - input with low case sentence - * @return sentence with every first letter of a word capital + * @param low - input with lower-case sentence + * @return sentence where ALL words begin with a capital letter */ - public static String capitalizeAll(String low){ + public static String capitalizeAllFirstLetters(String low){ StringBuffer res = new StringBuffer(); String[] strArr = low.split("\\s+"); for (String str : strArr) { diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/Action.java b/src/main/java/net/b07z/sepia/server/assist/parameters/Action.java index a5b5050a..f0474d0f 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/Action.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/Action.java @@ -136,7 +136,7 @@ public String extract(String input) { off = "(mach|schalte|dreh) .*\\b(aus)|" + "^\\w+\\b (aus$)|" + "schliessen|schliesse|deaktivieren|deaktiviere|" - + "beenden|beende|ausschalten|aus schalten|ausmachen|aus machen|ausdrehen|aus drehen|stoppen|stoppe|stop|exit"; + + "beenden|beende|ausschalten|aus schalten|ausmachen|aus machen|ausdrehen|aus drehen|stop(pen|pe|p|)|exit"; pause = "pausieren|pause|anhalten|halte .*\\b(an)"; increase = "(mach|dreh) .*\\b(auf|hoch)|" + "(? local_de = new HashMap<>(); public static HashMap local_en = new HashMap<>(); + public static HashMap buttons_de = new HashMap<>(); + public static HashMap buttons_en = new HashMap<>(); static { local_de.put("", "die Einstellungen"); local_de.put("", "die Lautstärke"); @@ -45,6 +47,13 @@ public static enum Type { local_de.put("", "die Mesh-Node"); local_de.put("", "CLEXI"); local_de.put("", "die Medienwiedergabe"); + //Button names + buttons_de.put("", "Funktion"); + buttons_de.put("", "Lautstärke"); + buttons_de.put("", "Always-On Modus"); + buttons_de.put("", "Mesh-Node"); + buttons_de.put("", "CLEXI"); + buttons_de.put("", "Medienwiedergabe"); local_en.put("", "the settings"); local_en.put("", "the volume"); @@ -52,6 +61,14 @@ public static enum Type { local_en.put("", "the mesh-node"); local_en.put("", "CLEXI"); local_en.put("", "the media player"); + //Button names + buttons_en.put("", "Function"); + buttons_en.put("", "Settings"); + buttons_en.put("", "Volume"); + buttons_en.put("", "Always-On Mode"); + buttons_en.put("", "Mesh-Node"); + buttons_en.put("", "CLEXI"); + buttons_en.put("", "Media Player"); } /** * Translate generalized value. @@ -76,6 +93,31 @@ public static String getLocal(String value, String language){ } return localName; } + /** + * Get localized name of the function for e.g. button names. + * If generalized value is unknown returns empty string, if it is null returns a default name. + * @param value - generalized value + * @param language - ISO language code + */ + public static String getLocalButtonName(String value, String language){ + String localName = ""; + String valueWithBrackets = value; + if (Is.nullOrEmpty(value)){ + valueWithBrackets = ""; + }else if (!value.startsWith("<")){ + valueWithBrackets = "<" + value + ">"; + } + if (language.equals(LANGUAGES.DE)){ + localName = buttons_de.get(valueWithBrackets); + }else if (language.equals(LANGUAGES.EN)){ + localName = buttons_en.get(valueWithBrackets); + } + if (localName == null){ + Debugger.println("ClientFunction.java - getLocalButtonName() has no '" + language + "' version for '" + value + "'", 3); + return ""; + } + return localName; + } //------------------ public User user; diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/ListSubType.java b/src/main/java/net/b07z/sepia/server/assist/parameters/ListSubType.java index ac2d83d7..e501913e 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/ListSubType.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/ListSubType.java @@ -71,7 +71,7 @@ public String extract(String input) { //German if (language.equals(LANGUAGES.DE)){ //old: (\\w+ |)(to do |\\w+(-| |)(\\w+-|)) - genericListWithPrefix = "(\\w+ und |)(\\w+ |)((to do )|(\\w+-\\w+(-| ))|(\\w+(-| |)))(list(en|e|)|zettel|note(s|)|notiz(en|))(?! (\\w+ |)(auf|von|zu(r|)))"; + genericListWithPrefix = "(\\w+ (und|oder) |)+(\\w+ |)((to do )|(\\w+-\\w+(-| ))|(\\w+(-| |)))(list(en|e|)|zettel|note(s|)|notiz(en|))(?! (\\w+ |)(auf|von|zu(r|)))"; extractedRawTerm = NluTools.stringFindFirst(input, genericListWithPrefix); extractedRawTerm = extractedRawTerm.replaceFirst("\\b(auf |von |zu(r|) )\\b", "").trim(); extractedRawTerm = extractedRawTerm.replaceFirst(".*\\b(meine(n|r|m|)|die|der|den|eine|einer|einen|einem|mir)\\b", "").trim(); @@ -81,7 +81,7 @@ public String extract(String input) { //English and other }else{ - genericListWithPrefix = "(\\w+ and |)(\\w+ |)((to do )|(\\w+-\\w+(-| ))|(\\w+(-| )))(list(s|)|note(s|))(?! (\\w+ |)(onto|on|to|at|from))"; + genericListWithPrefix = "(\\w+ (and|or) |)+(\\w+ |)((to do )|(\\w+-\\w+(-| ))|(\\w+(-| )))(list(s|)|note(s|))(?! (\\w+ |)(onto|on|to|at|from))"; extractedRawTerm = NluTools.stringFindFirst(input, genericListWithPrefix); extractedRawTerm = extractedRawTerm.replaceFirst("\\b(on |onto |to |at |from )\\b", "").trim(); extractedRawTerm = extractedRawTerm.replaceFirst(".*\\b(my|a|the)\\b", "").trim(); diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java index e15452a7..c1dea4b6 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/MusicService.java @@ -27,12 +27,15 @@ public class MusicService implements ParameterHandler{ public static enum Service { spotify, spotify_link, + spotify_embedded, apple_music, apple_music_link, + apple_music_embedded, amazon_music, deezer, soundcloud, youtube, + youtube_embedded, vlc_media_player } @@ -41,12 +44,15 @@ public static enum Service { static { musicServices.put("", "Spotify"); musicServices.put("", "Spotify"); + musicServices.put("", "Spotify"); musicServices.put("", "Apple Music"); musicServices.put("", "Apple Music"); + musicServices.put("", "Apple Music"); musicServices.put("", "Amazon Music"); musicServices.put("", "Deezer"); musicServices.put("", "SoundCloud"); musicServices.put("", "YouTube"); + musicServices.put("", "YouTube"); musicServices.put("", "VLC Media Player"); } /** @@ -118,7 +124,7 @@ public String extract(String input) { soundCloud + "|" + youTube + "|" + vlc + - ")( link|)" + ")( link| embedded|)" ); }else{ //OTHER LANGUAGES @@ -131,7 +137,7 @@ public String extract(String input) { soundCloud + "|" + youTube + "|" + vlc + - ")( link|)" + ")( link| embedded|)" ); } @@ -144,6 +150,14 @@ public String extract(String input) { }else if (NluTools.stringContains(service, appleMusic)){ service = "<" + Service.apple_music_link.name() + ">"; } + }else if (NluTools.stringContains(service, "(embedded)$")){ + if (NluTools.stringContains(service, spotify)){ + service = "<" + Service.spotify_embedded.name() + ">"; + }else if (NluTools.stringContains(service, appleMusic)){ + service = "<" + Service.apple_music_embedded.name() + ">"; + }else if (NluTools.stringContains(service, youTube)){ + service = "<" + Service.youtube_embedded.name() + ">"; + } }else if (NluTools.stringContains(service, spotify)){ service = "<" + Service.spotify.name() + ">"; }else if (NluTools.stringContains(service, appleMusic)){ diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/SearchSection.java b/src/main/java/net/b07z/sepia/server/assist/parameters/SearchSection.java index 81bea2ba..aae5363f 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/SearchSection.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/SearchSection.java @@ -16,11 +16,15 @@ public class SearchSection implements ParameterHandler{ //-----data----- + + //TODO: section names should become an enumerator (e.g. used in WebsearchBasic) + public static HashMap sections_de = new HashMap<>(); public static HashMap sections_en = new HashMap<>(); static { sections_de.put("", "Bilder"); sections_de.put("", "Rezepte"); + sections_de.put("", "Musik"); sections_de.put("", "Videos"); sections_de.put("", "Filme"); sections_de.put("", "Aktien"); @@ -28,6 +32,7 @@ public class SearchSection implements ParameterHandler{ sections_en.put("", "pictures"); sections_en.put("", "recipes"); + sections_en.put("", "music"); sections_en.put("", "videos"); sections_en.put("", "movies"); sections_en.put("", "shares"); @@ -83,11 +88,25 @@ public static String getSearchSection(String input, String language){ String type = ""; //German if (language.matches(LANGUAGES.DE)){ - type = NluTools.stringFindFirst(input, "(-|)(bild(ern|er|)|rezept(en|e|)|video(s|)|movie(s|)|film(en|e|)|aktie(n|)|aktien(kurs|wert)|buecher(n|)|buch)"); + type = NluTools.stringFindFirst(input, "(-|)" + + "(bild(ern|er|)|" + + "rezept(en|e|)|" + + "video(s|)|" + + "movie(s|)|film(en|e|)|" + + "musik|lied(ern|er|)|song(s|)|" + + "aktie(n|)|aktien(kurs|wert)|" + + "buecher(n|)|buch)"); //English and other }else{ - type = NluTools.stringFindFirst(input, "(-|)(picture(s|)|recipe(s|)|video(s|)|movie(s|)|film(s|)|share(s|)|stock(s|)|book(s|))"); + type = NluTools.stringFindFirst(input, "(-|)" + + "(picture(s|)|" + + "recipe(s|)|" + + "video(s|)|" + + "movie(s|)|film(s|)|" + + "music|song(s|)|" + + "share(s|)|stock(s|)|" + + "book(s|))"); } //System.out.println("searchType: " + type); //debug return type.replaceFirst("-", ""); @@ -105,6 +124,8 @@ public String extract(String input) { return ""; }else if (NluTools.stringContains(valueFound, "movie(s|)|film(s|)|film(en|e|)")){ return ""; + }else if (NluTools.stringContains(valueFound, "music|song(s|)|musik|lied(ern|er|)")){ + return ""; }else if (NluTools.stringContains(valueFound, "aktie(n|)|aktien(kurs|wert)|share(s|)|stock(s|)")){ return ""; }else if (NluTools.stringContains(valueFound, "buecher(n|)|buch|book(s|)")){ diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchEngine.java b/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchEngine.java index 73c52a08..48db3b03 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchEngine.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchEngine.java @@ -14,14 +14,18 @@ public class WebSearchEngine implements ParameterHandler{ + //Common + public static final String GOOGLE = "google"; + public static final String BING = "bing"; + public static final String YAHOO = "yahoo"; + public static final String DUCK_DUCK_GO = "duck duck go"; + //Specialized + public static final String YOUTUBE = "youtube"; + //-----data----- - /* may cause confusion - public static final String GOOGLE = "Google"; - public static final String YAHOO = "Yahoo"; - public static final String BING = "Bing"; - public static final String DUCK_DUCK_GO = "DuckDuckGo"; */ public static final String names = "(google|bing|duck duck go|duck duck|duckduckgo|yahoo|youtube)"; + //-------------- User user; diff --git a/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchRequest.java b/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchRequest.java index ab7f3dd7..00b543fc 100644 --- a/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchRequest.java +++ b/src/main/java/net/b07z/sepia/server/assist/parameters/WebSearchRequest.java @@ -45,6 +45,9 @@ public String extract(String input) { if (NluTools.stringContains(input, "^" + engine)){ search = input.replaceFirst("^" + engine + "( suche|)\\b", "").trim(); + }else if (NluTools.stringContains(input, "((durch|)suche)( mit(tels|)| auf| ueber| bei| via| per| )" + engine + "( nach| fuer| auf)")){ + search = input.replaceFirst(".*? (nach|fuer|auf)\\b", "").trim(); + }else if (NluTools.stringContains(input, "(suche|finde|zeig|schau|suchen nach) .* (mit|per|via|auf|ueber|mittels|bei) " + engine + "$")){ search = input.replaceFirst(".*?\\b(suche|finde|zeig|schau|suchen nach)\\b", "").trim(); search = search.replaceFirst("(mit|per|via|auf|ueber|mittels|bei) " + engine + "$", "").trim(); @@ -56,7 +59,6 @@ public String extract(String input) { //clean up search = search.replaceFirst("^(mir)", "").trim(); search = search.replaceFirst("^(mal bitte|bitte mal|mal|bitte)", "").trim(); - search = search.replaceFirst("^bei youtube( nach|)", "videos").trim(); search = search.replaceFirst("^(nach|.*\\b(suchen( (im|das) (web|internet) | )nach))\\b", "").trim(); //some adaptations /* @@ -69,6 +71,9 @@ public String extract(String input) { if (NluTools.stringContains(input, "^" + engine)){ search = input.replaceFirst("^" + engine + "( search|)", "").trim(); + }else if (NluTools.stringContains(input, "(search)( with| on| over| by| via| per| )" + engine + "( for| after)")){ + search = input.replaceFirst(".*? (for|after)\\b", "").trim(); + }else if (NluTools.stringContains(input, "(search|find|show|look|searching|looking) .* (with|on|via|per|over|by) " + engine + "$")){ search = input.replaceFirst(".*?\\b(search|find|show|look|searching|looking)", "").trim(); search = search.replaceFirst("(with|on|via|per|over|by) " + engine + "$", "").trim(); @@ -79,7 +84,6 @@ public String extract(String input) { } //clean up search = search.replaceFirst("^(me)", "").trim(); - search = search.replaceFirst("^(with|on|via|per|over|by) youtube( for|)", "videos").trim(); search = search.replaceFirst("^(for)", "").trim(); //some adaptations /* @@ -132,13 +136,29 @@ public String build(String input) { if (input.matches("(?i)^(wie|wo) .* die .*aktie(n|)$")){ reduced = input.replaceFirst("(?i).* die (.*?)(-| |)aktie(n|)", "$1"); }else{ - reduced = input.replaceFirst("(?i)^(bild(ern|er|)|rezept(en|e|)|video(s|)|movie(s|)|film(en|e|)|aktie(n|)|aktien(kurs|wert)|buecher(n|)|buch) (von|vom|ueber|mit|fuer|)|" - + "(?i) (bild(ern|er|)|rezept(en|e|)|video(s|)|movie(s|)|film(en|e|)|(-|)aktie(n|)|buecher(n|)|buch)$|" + reduced = input.replaceFirst("(?i)^(bild(ern|er|)|rezept(en|e|)|" + + "video(s|)|movie(s|)|film(en|e|)|" + + "lied(ern|er|)|musik|song(s|)|" + + "aktie(n|)|aktien(kurs|wert)|buecher(n|)|buch)" + + " (von|vom|ueber|mit|fuer|)|" + + "(?i) (bild(ern|er|)|rezept(en|e|)|" + + "video(s|)|movie(s|)|film(en|e|)|" + + "lied(ern|er|)|musik|song(s|)|" + + "(-|)aktie(n|)|buecher(n|)|buch)" + + "$|" + "(?i)^((wie|wo) (ist|steht|stehen) (der|die) (aktienkurs|aktienwert|aktie(n|)|kurs|wert) (von|vom|der|))", "").trim(); } }else if (language.equals(LANGUAGES.EN)){ - reduced = input.replaceFirst("(?i)^(picture(s|)|recipe(s|)|video(s|)|movie(s|)|film(s|)|share(s|)|stock(s|)|book(s|)) (of|with|by|for|)|" - + "(?i) (picture(s|)|recipe(s|)|video(s|)|movie(s|)|film(s|)|share(s|)|stock(s|)|book(s|))$|" + reduced = input.replaceFirst("(?i)^(picture(s|)|recipe(s|)|" + + "video(s|)|movie(s|)|film(s|)|" + + "song(s|)|music|" + + "share(s|)|stock(s|)|book(s|))" + + " (of|with|by|for|)|" + + "(?i) (picture(s|)|recipe(s|)|" + + "video(s|)|movie(s|)|film(s|)|" + + "song(s|)|music|" + + "share(s|)|stock(s|)|book(s|))" + + "$|" + "(?i)^(what is the (stock|share) (value|price) (of|))", "").trim(); } //build default result diff --git a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java index 0343eefd..a89c0d75 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/ClientControls.java @@ -143,14 +143,7 @@ public ServiceResult getResult(NluResult nluResult) { Parameter controlFunP = nluResult.getRequiredParameter(PARAMETERS.CLIENT_FUN); String controlFun = controlFunP.getValueAsString().replaceAll("^<|>$", "").trim(); - String controlFunLocal = (String) controlFunP.getDataFieldOrDefault(InterviewData.VALUE_LOCAL); - if (Is.nullOrEmpty(controlFunLocal)){ - controlFunLocal = "Control"; - }else{ - try{ - controlFunLocal = NluTools.capitalizeAll(controlFunLocal.split("\\s")[1]); - }catch(Exception e){} - } + boolean isSettings = controlFun.equals(ClientFunction.Type.settings.name()); boolean isAlwaysOn = controlFun.equals(ClientFunction.Type.alwaysOn.name()); boolean isMeshNode = controlFun.equals(ClientFunction.Type.meshNode.name()); @@ -158,6 +151,11 @@ public ServiceResult getResult(NluResult nluResult) { boolean isMedia = controlFun.equals(ClientFunction.Type.media.name()); //NOTE: media and volume can exist simultaneously boolean isVolume = controlFun.equals(ClientFunction.Type.volume.name()) || mediaControls.startsWith("volume_"); + if (isVolume){ + controlFun = ClientFunction.Type.volume.name(); + } + String controlFunLocal = ClientFunction.getLocalButtonName(controlFun, nluResult.language); + Parameter dataP = nluResult.getOptionalParameter(PARAMETERS.DATA, ""); String data = dataP.getValueAsString(); diff --git a/src/main/java/net/b07z/sepia/server/assist/services/DirectionsGoogleMaps.java b/src/main/java/net/b07z/sepia/server/assist/services/DirectionsGoogleMaps.java index 16ff397f..76bcae05 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/DirectionsGoogleMaps.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/DirectionsGoogleMaps.java @@ -397,13 +397,12 @@ public ServiceResult getResult(NluResult nluResult){ api.setCustomAnswer(wpAnswer); } - //make action: browser url call - /* - if (!isInfoAnswer){ - api.actionInfo_add_action(ACTIONS.OPEN_URL); - api.actionInfo_put_info("url", googleMapsURL); //<- first one is Google maps + //make action: browser URL call + if (!isInfoAnswer && !hasOptions){ + api.addAction(ACTIONS.OPEN_IN_APP_BROWSER); + api.putActionInfo("url", (appleMapsURL.isEmpty())? googleMapsURL : appleMapsURL); } - */ + /* //google button api.actionInfo_add_action(ACTIONS.BUTTON_URL); diff --git a/src/main/java/net/b07z/sepia/server/assist/services/Lists.java b/src/main/java/net/b07z/sepia/server/assist/services/Lists.java index 29a55d3f..7ede40dd 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/Lists.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/Lists.java @@ -241,7 +241,16 @@ public ServiceResult getResult(NluResult nluResult){ HashMap filters = new HashMap<>(); //list title - if (!title.isEmpty()) filters.put("title", title); + if (!title.isEmpty()){ + //we trust the database to find multiple lists that fit to the search term (because it uses a OR b OR c for an 'a b c' list) + //so we can remove and, or, ... from search term ... + if (nluResult.language.equals(LANGUAGES.DE)){ + title = title.replaceAll("\\b(und|oder)\\b", " ").replaceAll("\\s+", " "); + }else if (nluResult.language.equals(LANGUAGES.EN)){ + title = title.replaceAll("\\b(and|or)\\b", " ").replaceAll("\\s+", " "); + } + filters.put("title", title); + } //results size and pagination filters.put("resultsFrom", 0); //start at first result diff --git a/src/main/java/net/b07z/sepia/server/assist/services/LocationSearchBasic.java b/src/main/java/net/b07z/sepia/server/assist/services/LocationSearchBasic.java index c70dc002..265fe5ab 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/LocationSearchBasic.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/LocationSearchBasic.java @@ -17,6 +17,7 @@ import net.b07z.sepia.server.assist.server.ConfigServices; import net.b07z.sepia.server.assist.services.ServiceInfo.Content; import net.b07z.sepia.server.assist.services.ServiceInfo.Type; +import net.b07z.sepia.server.core.assistant.ACTIONS; import net.b07z.sepia.server.core.assistant.CLIENTS; import net.b07z.sepia.server.core.assistant.PARAMETERS; import net.b07z.sepia.server.core.tools.Converters; @@ -157,9 +158,10 @@ public ServiceResult getResult(NluResult nluResult){ Debugger.println("Location_Mapsearch - failed to encode URL with: " + end, 1); //e.printStackTrace(); } + //make action to open URL + api.addAction(ACTIONS.OPEN_IN_APP_BROWSER); + api.putActionInfo("url", (appleMapsURL.isEmpty())? googleMapsURL : appleMapsURL); /* - api.actionInfo_add_action(ACTIONS.OPEN_URL); - api.actionInfo_put_info("url", googleMapsURL); //google button api.actionInfo_add_action(ACTIONS.BUTTON_URL); diff --git a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java index 89a92f2a..1366951d 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/MusicSearch.java @@ -42,6 +42,11 @@ */ public class MusicSearch implements ServiceInterface{ + public static final String CARD_TYPE = "musicSearch"; + public static final String CARD_BRAND_YOUTUBE = "YouTube"; + public static final String CARD_BRAND_SPOTIFY = "Spotify"; + public static final String CARD_BRAND_APPLE_MUSIC = "Apple Music"; + //Define some sentences for testing: @Override @@ -173,9 +178,20 @@ public ServiceResult getResult(NluResult nluResult) { } //System.out.println("defaultMusicApp: " + service); //DEBUG } - boolean isSpotifyService = service.equals(MusicService.Service.spotify.name()) || service.equals(MusicService.Service.spotify_link.name()); - boolean isAppleMusic = service.equals(MusicService.Service.apple_music.name()) || service.equals(MusicService.Service.apple_music_link.name()); - boolean requiresUri = service.contains("_link"); + boolean isSpotifyService = service.startsWith(MusicService.Service.spotify.name()); + boolean isAppleMusic = service.startsWith(MusicService.Service.apple_music.name()); + boolean isYouTube = service.startsWith(MusicService.Service.youtube.name()); + boolean requiresUri = service.contains("_link") || service.contains("_embedded"); + String rootService = service.replace("_link", "").replace("_embedded", "").trim(); + + //check if embedding is desired and possible + boolean requestEmbedded = false; + if (isYouTube || service.contains("_embedded")){ //NOTE: YouTube embedding is usually a common feature in the client used for search as well + Object embeddingsObj = nluResult.input.getCustomDataObject("embeddedPlayers"); + if (embeddingsObj != null){ + requestEmbedded = ((JSONArray) embeddingsObj).contains(rootService); + } + } //Basically this service cannot fail here ... only inside client ... but we'll also try to get some more data: @@ -194,7 +210,7 @@ public ServiceResult getResult(NluResult nluResult) { if (service.equals(MusicService.Service.youtube.name()) || service.isEmpty()){ //Icon cardIconUrl = Config.urlWebImages + "brands/youtube-logo.png"; - cardBrand = "YouTube"; + cardBrand = CARD_BRAND_YOUTUBE; cardSubtitle = "YouTube Search"; //Search String q = ""; @@ -245,7 +261,7 @@ public ServiceResult getResult(NluResult nluResult) { if (Config.spotifyApi != null){ //Icon cardIconUrl = Config.urlWebImages + "brands/spotify-logo.png"; - cardBrand = "Spotify"; + cardBrand = CARD_BRAND_SPOTIFY; //Search JSONObject spotifyBestItem = Config.spotifyApi.searchBestItem(song, artist, album, playlistName, genre); foundUri = JSON.getString(spotifyBestItem, "uri"); @@ -325,7 +341,7 @@ public ServiceResult getResult(NluResult nluResult) { }else if(isAppleMusic){ //Icon cardIconUrl = Config.urlWebImages + "brands/apple-music-logo.png"; - cardBrand = "Apple Music"; + cardBrand = CARD_BRAND_APPLE_MUSIC; //Search (we use the open iTunes API instead of Apple Music API (because it is too hard to get an Apple Music key) ITunesApi iTunesApi = new ITunesApi((nluResult.language.equals(LANGUAGES.DE))? "DE" : "US"); //TODO: add more country codes if we need them ... JSONObject iTunesBestItem = iTunesApi.searchBestMusicItem(song, artist, album, playlistName, genre); @@ -401,27 +417,35 @@ public ServiceResult getResult(NluResult nluResult) { JSON.put(controlData, "uri", foundUri); } - //client control action - api.addAction(ACTIONS.CLIENT_CONTROL_FUN); - api.putActionInfo("fun", controlFun); - api.putActionInfo("controlData", controlData); - - //... and action button - api.addAction(ACTIONS.BUTTON_CUSTOM_FUN); - api.putActionInfo("fun", "controlFun;;" + controlFun + ";;" + controlData.toJSONString()); - api.putActionInfo("title", Is.notNullOrEmpty(serviceLocal)? serviceLocal : "Button"); + //Actions (only if its not a card embedding) + if (!requestEmbedded){ + //client control action + api.addAction(ACTIONS.CLIENT_CONTROL_FUN); + api.putActionInfo("fun", controlFun); + api.putActionInfo("controlData", controlData); + + //... and action button + api.addAction(ACTIONS.BUTTON_CUSTOM_FUN); + api.putActionInfo("fun", "controlFun;;" + controlFun + ";;" + controlData.toJSONString()); + api.putActionInfo("title", Is.notNullOrEmpty(serviceLocal)? serviceLocal : "Button"); + } //Cards (or web-search?) if (Is.notNullOrEmpty(foundUri)){ Card card = new Card(Card.TYPE_SINGLE); + JSONObject cardData = JSON.make( + "title", cardTitle, + "desc", cardSubtitle, + "type", CARD_TYPE, + "brand", cardBrand + ); + if (requestEmbedded){ + JSON.put(cardData, "embedded", true); + JSON.put(cardData, "autoplay", true); + } //JSONObject linkCard = card.addElement(ElementType.link, - JSON.make( - "title", cardTitle, - "desc", cardSubtitle, - "type", "musicSearch", - "brand", cardBrand - ), + cardData, null, null, "", foundUri, cardIconUrl, diff --git a/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java b/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java index 68d37925..85799ff9 100644 --- a/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java +++ b/src/main/java/net/b07z/sepia/server/assist/services/WebsearchBasic.java @@ -7,6 +7,7 @@ import java.util.Random; import java.util.stream.Collectors; +import org.json.simple.JSONArray; import org.json.simple.JSONObject; import net.b07z.sepia.server.assist.assistant.LANGUAGES; @@ -16,7 +17,9 @@ import net.b07z.sepia.server.assist.interpreters.NluInput; import net.b07z.sepia.server.assist.interpreters.NluResult; import net.b07z.sepia.server.assist.interviews.InterviewData; +import net.b07z.sepia.server.assist.parameters.MusicService; import net.b07z.sepia.server.assist.parameters.SearchSection; +import net.b07z.sepia.server.assist.parameters.WebSearchEngine; import net.b07z.sepia.server.assist.server.Config; import net.b07z.sepia.server.assist.services.ServiceInfo.Content; import net.b07z.sepia.server.assist.services.ServiceInfo.Type; @@ -33,13 +36,17 @@ */ public class WebsearchBasic implements ServiceInterface{ - //Search engines - public static ArrayList engines = new ArrayList<>(); + public static final String CARD_TYPE = "websearch"; + public static final String CARD_TYPE_SPECIAL_VIDEO = "videoSearch"; + + //Search engines for random-selection + private static ArrayList commonEngines = new ArrayList<>(); static{ - engines.add("google"); - engines.add("bing"); - engines.add("duck duck go"); - engines.add("yahoo"); + commonEngines.add(WebSearchEngine.GOOGLE); + commonEngines.add(WebSearchEngine.BING); + commonEngines.add(WebSearchEngine.DUCK_DUCK_GO); + commonEngines.add(WebSearchEngine.YAHOO); + //NOTE: add only "real" web-search engines here (not specialized things like YouTube) } //--- data --- @@ -133,10 +140,6 @@ public ServiceResult getResult(NluResult nluResult){ String engineIconUrl = getRes[2]; String engineIconUrlRnd = getResRnd[2]; - //make action: in app browser url call and button - api.addAction(ACTIONS.OPEN_IN_APP_BROWSER); - api.putActionInfo("url", search_url); - String title1 = getButtonText(engineName, api.language); //Button 1 /* @@ -145,14 +148,29 @@ public ServiceResult getResult(NluResult nluResult){ api.actionInfo_put_info("title", title1); */ //Card 1 + boolean addUrlAction = true; //this is usually enabled, but if engine is YouTube and embedding is possible we should skip this Card card = new Card(Card.TYPE_SINGLE); + JSONObject cardData = JSON.make( + "title", title1 + ":", + "desc", "\"" + search + "\"", + "type", CARD_TYPE + ); + if (engine.contains(WebSearchEngine.YOUTUBE)){ + JSON.put(cardData, "type", CARD_TYPE_SPECIAL_VIDEO); //overwrite + JSON.put(cardData, "brand", MusicSearch.CARD_BRAND_YOUTUBE); + JSON.put(cardData, "autoplay", false); + //check if embedding is possible + Object embeddingsObj = nluResult.input.getCustomDataObject("embeddedPlayers"); + if (embeddingsObj != null){ + if (((JSONArray) embeddingsObj).contains(MusicService.Service.youtube.name())){ + addUrlAction = false; + JSON.put(cardData, "embedded", true); + } + } + } /*JSONObject linkCard = */ card.addElement(ElementType.link, - JSON.make( - "title", title1 + ":", - "desc", "\"" + search + "\"", - "type", "websearch" - ), + cardData, null, null, "", search_url, engineIconUrl, @@ -160,6 +178,12 @@ public ServiceResult getResult(NluResult nluResult){ //JSON.put(linkCard, "imageBackground", "transparent"); //use any CSS background option you wish api.addCard(card.getJSON()); + //Action + if (addUrlAction){ + api.addAction(ACTIONS.OPEN_IN_APP_BROWSER); + api.putActionInfo("url", search_url); + } + String title2 = getButtonText(engineNameRnd, api.language); //Button 2 /* @@ -174,7 +198,7 @@ public ServiceResult getResult(NluResult nluResult){ JSON.make( "title", title2 + ":", "desc", "\"" + search + "\"", - "type", "websearch" + "type", CARD_TYPE ), null, null, "", search_url_rnd, @@ -212,7 +236,9 @@ public static String[] getWebSearchUrl(String engine, String section, String sea String iconUrl = Config.urlWebImages + "cards/search.png"; engine = engine.toLowerCase(); - if (engine.contains("yahoo")){ + //TODO: section names should become an enumerator in SearchSection parameter + + if (engine.contains(WebSearchEngine.YAHOO)){ engine = "Yahoo"; search_url = "https://search.yahoo.com/search?p="; if (section.equals("pictures")){ @@ -224,7 +250,7 @@ public static String[] getWebSearchUrl(String engine, String section, String sea }else if (section.equals("shares")){ search_url = "https://finance.search.yahoo.com/search;?p="; search = searchReduced; } - }else if (engine.contains("bing")){ + }else if (engine.contains(WebSearchEngine.BING)){ engine = "Bing"; search_url = "https://www.bing.com/search?q="; if (section.equals("pictures")){ @@ -232,7 +258,7 @@ public static String[] getWebSearchUrl(String engine, String section, String sea }else if (section.equals("videos")){ search_url = "https://www.bing.com/videos/search?q="; search = searchReduced; } - }else if (engine.contains("duck duck go")){ + }else if (engine.contains(WebSearchEngine.DUCK_DUCK_GO)){ engine = "DuckDuckGo"; search_url = "https://duckduckgo.com/?kae=d&q="; if (section.equals("pictures")){ @@ -242,15 +268,12 @@ public static String[] getWebSearchUrl(String engine, String section, String sea }else if (section.equals("recipes")){ search_url = "https://duckduckgo.com/?kae=d&ia=recipes&q="; search = searchReduced; } - }else if (engine.contains("youtube")){ + }else if (engine.contains(WebSearchEngine.YOUTUBE)){ engine = "YouTube"; + iconUrl = Config.urlWebImages + "brands/youtube-logo.png"; search_url = "https://www.youtube.com/results?search_query="; - if (section.equals("pictures")){ - search_url = "https://www.google.com/search?q="; search = searchReduced; - }else if (section.equals("videos")){ - search_url = "https://www.youtube.com/results?search_query="; search = searchReduced; - }else if (section.equals("recipes")){ - search_url = "https://www.youtube.com/results?search_query="; search = searchReduced; + if (section.equals("videos") || section.equals("music")){ + search = searchReduced; } }else{ engine = "Google"; @@ -283,7 +306,7 @@ public static String[] getWebSearchUrl(String engine, String section, String sea * @param section - optimize for this section (not yet working) */ public static String getPseudoRandomEngine(String except, String section){ - List enginesToChoose = engines.stream().filter(s -> { + List enginesToChoose = commonEngines.stream().filter(s -> { return !s.equals(except); }).collect(Collectors.toList()); //enginesToChoose.remove(except); diff --git a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_ListItem.java b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_ListItem.java index d74b2ee9..57042cfc 100644 --- a/src/test/java/net/b07z/sepia/server/assist/parameters/Test_ListItem.java +++ b/src/test/java/net/b07z/sepia/server/assist/parameters/Test_ListItem.java @@ -30,6 +30,9 @@ public static void main(String[] args) { texts.add("Setze Milch, Brot, Müsli und Wasser und Pudding auf die Shoppingliste"); texts.add("Setze Milch, Brot,Müsli und Wasser auf die Shoppingliste"); texts.add("Setze 3,1415 auf meine Pi Liste"); + texts.add("Zeig mir die Bugs, Features und Release Liste."); + texts.add("Zeig mir die Bugs und Features und Release Liste."); + texts.add("Zeig mir die Bugs oder Features Liste."); printTestResults(texts, parametersToTest, language); @@ -43,6 +46,9 @@ public static void main(String[] args) { texts.add("Water on shoppinglist"); texts.add("Put milk, water, bread and juice on my supermarket list"); texts.add("Put 3,1415 on my Pi list"); + texts.add("Show me the Bugs, Features and Release List."); + texts.add("Show me the Bugs and Features and Release List."); + texts.add("Show me the Bugs or Features List."); printTestResults(texts, parametersToTest, language); } diff --git a/src/test/java/net/b07z/sepia/server/assist/services/ServiceTestTools.java b/src/test/java/net/b07z/sepia/server/assist/services/ServiceTestTools.java new file mode 100644 index 00000000..7936f699 --- /dev/null +++ b/src/test/java/net/b07z/sepia/server/assist/services/ServiceTestTools.java @@ -0,0 +1,101 @@ +package net.b07z.sepia.server.assist.services; + +import java.util.ArrayList; +import java.util.List; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.data.Name; +import net.b07z.sepia.server.assist.interpreters.NluInput; +import net.b07z.sepia.server.assist.interpreters.NluInterface; +import net.b07z.sepia.server.assist.interpreters.NluResult; +import net.b07z.sepia.server.assist.interviews.AbstractInterview; +import net.b07z.sepia.server.assist.interviews.InterviewInterface; +import net.b07z.sepia.server.assist.interviews.InterviewResult; +import net.b07z.sepia.server.assist.server.Config; +import net.b07z.sepia.server.assist.server.ConfigServices; +import net.b07z.sepia.server.assist.server.ConfigTestServer; +import net.b07z.sepia.server.core.tools.ClassBuilder; +import net.b07z.sepia.server.core.tools.JSON; +import net.b07z.sepia.server.core.tools.JSONWriter; + +public class ServiceTestTools { + + + public static void testSentenceViaKeywordAnalyzer(List errors, boolean doAnswer, String text, String language, String shouldBeCMD, JSONObject parameterRegExpMatches){ + if (errors == null) errors = new ArrayList<>(); + + //load "fake" input with some defaults + NluInput input = ConfigTestServer.getFakeInput(text, language); //default test user 1 + + //set some values explicitly + input.user.userName = new Name("Mister", "Tester", "Testy"); + input.setTimeGMT("2019.01.14_13:00:00"); + + //time + //System.out.println("\nChosen UNIX time (s): " + Math.round(input.userTime/1000)); + //System.out.println("Now (for testing): " + DateTimeConverters.getSpeakableDateSpecial(input.userTimeLocal, 5l, Config.defaultSdf, input) + ", " + input.userTimeLocal + "\n"); + + //start with test: + try{ Thread.sleep(20); }catch(Exception e){} + System.out.println("Sentence: " + input.text); + try{ Thread.sleep(20); }catch(Exception e){} + + //create NluResult (shortcut) + NluInterface nlp = (NluInterface) ClassBuilder.construct(Config.keywordAnalyzers.get(input.language)); //NOTE: hard-coded NLU Interface for this test + NluResult nluResult = nlp.interpret(input); + + String cmd = nluResult.getCommand(); + JSONObject resJson = nluResult.getBestResultJSON(); + + if (cmd.equals(shouldBeCMD)){ + boolean allParamsMatch = true; + if (parameterRegExpMatches != null){ + JSONObject params = JSON.getJObject(resJson, "parameters"); + for (Object o : parameterRegExpMatches.keySet()){ + String pToCheck = (String) o; + String actualValue = JSON.getString(params, pToCheck); + String shouldMatch = JSON.getString(parameterRegExpMatches, pToCheck); + if (!actualValue.matches(shouldMatch)){ + allParamsMatch = false; + System.err.println("Mismatch! Expected " + pToCheck + "=" + shouldMatch + " - found: " + actualValue); + break; + } + } + } + if (allParamsMatch){ + System.out.println("Best NLU result:"); + System.out.println(JSONWriter.getPrettyString(resJson)); + }else{ + System.err.println("Best NLU result:"); + System.err.println(JSONWriter.getPrettyString(resJson)); + errors.add(text); + } + }else{ + System.err.println("Best NLU result:"); + System.err.println(JSONWriter.getPrettyString(resJson)); + errors.add(text); + } + + //get answer + if (doAnswer){ + ServiceResult answer; + List services = ConfigServices.getCustomOrSystemServices(input, input.user, cmd); + InterviewInterface interview = new AbstractInterview(); + interview.setCommand(cmd); + interview.setServices(services); + InterviewResult iResult = interview.getMissingParameters(nluResult); + if (iResult.isComplete()){ + answer = interview.getServiceResults(iResult); + }else{ + answer = iResult.getApiComment(); + } + try{ Thread.sleep(20); }catch(Exception e){} + System.out.println(JSONWriter.getPrettyString(answer.getResultJSONObject())); + System.out.println(""); + }else{ + System.out.println(""); + } + try{ Thread.sleep(20); }catch(Exception e){} + } +} diff --git a/src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java b/src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java index 3625f1d1..8c52a11e 100644 --- a/src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java +++ b/src/test/java/net/b07z/sepia/server/assist/services/Test_ClientControls.java @@ -8,27 +8,17 @@ import org.json.simple.JSONObject; import net.b07z.sepia.server.assist.assistant.LANGUAGES; -import net.b07z.sepia.server.assist.data.Name; -import net.b07z.sepia.server.assist.interpreters.NluInput; -import net.b07z.sepia.server.assist.interpreters.NluInterface; -import net.b07z.sepia.server.assist.interpreters.NluResult; -import net.b07z.sepia.server.assist.interviews.AbstractInterview; -import net.b07z.sepia.server.assist.interviews.InterviewInterface; -import net.b07z.sepia.server.assist.interviews.InterviewResult; -import net.b07z.sepia.server.assist.server.Config; -import net.b07z.sepia.server.assist.server.ConfigServices; import net.b07z.sepia.server.assist.server.ConfigTestServer; import net.b07z.sepia.server.core.assistant.CMD; import net.b07z.sepia.server.core.assistant.PARAMETERS; -import net.b07z.sepia.server.core.tools.ClassBuilder; import net.b07z.sepia.server.core.tools.JSON; -import net.b07z.sepia.server.core.tools.JSONWriter; public class Test_ClientControls { - private static List errors = new ArrayList<>(); - + private static List errors; + public static void main(String[] args) { + errors = new ArrayList<>(); //load default settings (can add Start.loadConfigFile(serverType) before) ConfigTestServer.loadAnswersAndParameters(); @@ -62,7 +52,7 @@ public static void main(String[] args) { for (Map.Entry t : tests.entrySet()){ String shouldBeCmd = JSON.getString(t.getValue(), "c"); JSONObject parameterRegExpMatches = JSON.getJObject(t.getValue(), "p"); - testSentenceViaKeywordAnalyzer(doAnswer, t.getKey(), language, shouldBeCmd, parameterRegExpMatches); + ServiceTestTools.testSentenceViaKeywordAnalyzer(errors, doAnswer, t.getKey(), language, shouldBeCmd, parameterRegExpMatches); } language = LANGUAGES.DE; @@ -81,7 +71,7 @@ public static void main(String[] args) { for (Map.Entry t : tests.entrySet()){ String shouldBeCmd = JSON.getString(t.getValue(), "c"); JSONObject parameterRegExpMatches = JSON.getJObject(t.getValue(), "p"); - testSentenceViaKeywordAnalyzer(doAnswer, t.getKey(), language, shouldBeCmd, parameterRegExpMatches); + ServiceTestTools.testSentenceViaKeywordAnalyzer(errors, doAnswer, t.getKey(), language, shouldBeCmd, parameterRegExpMatches); } if (!errors.isEmpty()){ @@ -92,80 +82,5 @@ public static void main(String[] args) { } System.out.println("--- DONE ---"); } - - private static void testSentenceViaKeywordAnalyzer(boolean doAnswer, String text, String language, String shouldBeCMD, JSONObject parameterRegExpMatches){ - //load "fake" input with some defaults - NluInput input = ConfigTestServer.getFakeInput(text, language); //default test user 1 - - //set some values explicitly - input.user.userName = new Name("Mister", "Tester", "Testy"); - input.setTimeGMT("2019.01.14_13:00:00"); - - //time - //System.out.println("\nChosen UNIX time (s): " + Math.round(input.userTime/1000)); - //System.out.println("Now (for testing): " + DateTimeConverters.getSpeakableDateSpecial(input.userTimeLocal, 5l, Config.defaultSdf, input) + ", " + input.userTimeLocal + "\n"); - - //start with test: - try{ Thread.sleep(20); }catch(Exception e){} - System.out.println("Sentence: " + input.text); - try{ Thread.sleep(20); }catch(Exception e){} - - //create NluResult (shortcut) - NluInterface nlp = (NluInterface) ClassBuilder.construct(Config.keywordAnalyzers.get(input.language)); //NOTE: hard-coded NLU Interface for this test - NluResult nluResult = nlp.interpret(input); - - String cmd = nluResult.getCommand(); - JSONObject resJson = nluResult.getBestResultJSON(); - - if (cmd.equals(shouldBeCMD)){ - boolean allParamsMatch = true; - if (parameterRegExpMatches != null){ - JSONObject params = JSON.getJObject(resJson, "parameters"); - for (Object o : parameterRegExpMatches.keySet()){ - String pToCheck = (String) o; - String actualValue = JSON.getString(params, pToCheck); - String shouldMatch = JSON.getString(parameterRegExpMatches, pToCheck); - if (!actualValue.matches(shouldMatch)){ - allParamsMatch = false; - System.err.println("Mismatch! Expected " + pToCheck + "=" + shouldMatch + " - found: " + actualValue); - break; - } - } - } - if (allParamsMatch){ - System.out.println("Best NLU result:"); - System.out.println(JSONWriter.getPrettyString(resJson)); - }else{ - System.err.println("Best NLU result:"); - System.err.println(JSONWriter.getPrettyString(resJson)); - errors.add(text); - } - }else{ - System.err.println("Best NLU result:"); - System.err.println(JSONWriter.getPrettyString(resJson)); - errors.add(text); - } - - //get answer - if (doAnswer){ - ServiceResult answer; - List services = ConfigServices.getCustomOrSystemServices(input, input.user, cmd); - InterviewInterface interview = new AbstractInterview(); - interview.setCommand(cmd); - interview.setServices(services); - InterviewResult iResult = interview.getMissingParameters(nluResult); - if (iResult.isComplete()){ - answer = interview.getServiceResults(iResult); - }else{ - answer = iResult.getApiComment(); - } - try{ Thread.sleep(20); }catch(Exception e){} - System.out.println(JSONWriter.getPrettyString(answer.getResultJSONObject())); - System.out.println(""); - }else{ - System.out.println(""); - } - try{ Thread.sleep(20); }catch(Exception e){} - } } diff --git a/src/test/java/net/b07z/sepia/server/assist/services/Test_WebSearch.java b/src/test/java/net/b07z/sepia/server/assist/services/Test_WebSearch.java new file mode 100644 index 00000000..2041a96c --- /dev/null +++ b/src/test/java/net/b07z/sepia/server/assist/services/Test_WebSearch.java @@ -0,0 +1,78 @@ +package net.b07z.sepia.server.assist.services; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.simple.JSONObject; + +import net.b07z.sepia.server.assist.assistant.LANGUAGES; +import net.b07z.sepia.server.assist.server.ConfigTestServer; +import net.b07z.sepia.server.core.assistant.CMD; +import net.b07z.sepia.server.core.assistant.PARAMETERS; +import net.b07z.sepia.server.core.tools.JSON; + +public class Test_WebSearch { + + private static List errors; + + public static void main(String[] args) { + errors = new ArrayList<>(); + + //load default settings (can add Start.loadConfigFile(serverType) before) + ConfigTestServer.loadAnswersAndParameters(); + String userId = ConfigTestServer.getFakeUserId(null); //default test user 1 + + //Prevent database access for tests + ConfigTestServer.reduceDatabaseAccess(userId); + //... add service specific test-settings here + + //Tests + Map tests; + + boolean doAnswer = false; //just interpret (if you want to test a keywordAnalyzer defined INSIDE service) + String WS = CMD.WEB_SEARCH; + String MS = CMD.MUSIC; + String language; + + language = LANGUAGES.EN; + tests = new HashMap<>(); + tests.put("Search the web for X", JSON.make("c", WS, "p", JSON.make(PARAMETERS.WEBSEARCH_REQUEST, "X"))); + tests.put("Search YouTube for X", JSON.make("c", WS, "p", JSON.make(PARAMETERS.WEBSEARCH_REQUEST, "X"))); + tests.put("Search Google for X", JSON.make("c", WS, "p", JSON.make(PARAMETERS.WEBSEARCH_REQUEST, "X"))); + tests.put("Search music of X on YouTube", JSON.make("c", WS, "p", JSON.make(PARAMETERS.WEBSEARCH_REQUEST, "X"))); + tests.put("play music of X on YouTube", JSON.make("c", MS, "p", JSON.make(PARAMETERS.MUSIC_ARTIST, "X"))); + tests.put("Search music of X on Spotify", JSON.make("c", MS, "p", JSON.make(PARAMETERS.MUSIC_ARTIST, "X"))); + + for (Map.Entry t : tests.entrySet()){ + String shouldBeCmd = JSON.getString(t.getValue(), "c"); + JSONObject parameterRegExpMatches = JSON.getJObject(t.getValue(), "p"); + ServiceTestTools.testSentenceViaKeywordAnalyzer(errors, doAnswer, t.getKey(), language, shouldBeCmd, parameterRegExpMatches); + } + + language = LANGUAGES.DE; + tests = new HashMap<>(); + tests.put("Suche im Web nach X", JSON.make("c", WS, "p", JSON.make(PARAMETERS.WEBSEARCH_REQUEST, "X"))); + tests.put("Suche auf YouTube nach X", JSON.make("c", WS, "p", JSON.make(PARAMETERS.WEBSEARCH_REQUEST, "X"))); + tests.put("Suche auf Google nach X", JSON.make("c", WS, "p", JSON.make(PARAMETERS.WEBSEARCH_REQUEST, "X"))); + tests.put("Suche Musik von X auf YouTube", JSON.make("c", WS, "p", JSON.make(PARAMETERS.WEBSEARCH_REQUEST, "X"))); + tests.put("Spiele Musik von X auf YouTube", JSON.make("c", MS, "p", JSON.make(PARAMETERS.MUSIC_ARTIST, "X"))); + tests.put("Suche Musik von X auf Spotify", JSON.make("c", MS, "p", JSON.make(PARAMETERS.MUSIC_ARTIST, "X"))); + + for (Map.Entry t : tests.entrySet()){ + String shouldBeCmd = JSON.getString(t.getValue(), "c"); + JSONObject parameterRegExpMatches = JSON.getJObject(t.getValue(), "p"); + ServiceTestTools.testSentenceViaKeywordAnalyzer(errors, doAnswer, t.getKey(), language, shouldBeCmd, parameterRegExpMatches); + } + + if (!errors.isEmpty()){ + System.out.println("Errors in: "); + for (String s : errors){ + System.out.println(s); + } + } + System.out.println("--- DONE ---"); + } + +} From c4122e19ff515660284b2c2b702624538862dec5 Mon Sep 17 00:00:00 2001 From: Florian Quirin Date: Fri, 31 May 2019 20:11:49 +0200 Subject: [PATCH 27/27] use ws server 1.1.1; NLU and service improvements to media control --- pom.xml | 2 +- .../sepia/server/assist/parameters/Action.java | 4 ++-- .../server/assist/parameters/MediaControls.java | 10 +++++----- .../server/assist/services/ClientControls.java | 16 +++++++--------- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 132971b3..836984b7 100644 --- a/pom.xml +++ b/pom.xml @@ -106,7 +106,7 @@ net.b07z.sepia.websockets sepia-websockets - 1.1.0 + 1.1.1