From 275e42197c5fd0c231de3d5daf4eb796cb16efe1 Mon Sep 17 00:00:00 2001 From: vlofgren Date: Mon, 30 May 2022 17:26:44 +0200 Subject: [PATCH] Added rudimentary !bang-support --- .../wmsa/edge/EdgeSearchE2ETest.java | 8 ++ .../wmsa/edge/search/EdgeSearchService.java | 4 + .../edge/search/command/CommandEvaluator.java | 16 ++-- .../search/command/commands/BangCommand.java | 73 +++++++++++++++++++ .../command/{ => commands}/BrowseCommand.java | 4 +- .../{ => commands}/ConvertCommand.java | 5 +- .../{ => commands}/DefinitionCommand.java | 5 +- .../command/{ => commands}/SearchCommand.java | 13 ++-- .../{ => commands}/SiteSearchCommand.java | 19 ++--- .../search/exceptions/RedirectException.java | 14 ++++ .../command/commands/BangCommandTest.java | 38 ++++++++++ 11 files changed, 175 insertions(+), 24 deletions(-) create mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommand.java rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/{ => commands}/BrowseCommand.java (94%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/{ => commands}/ConvertCommand.java (86%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/{ => commands}/DefinitionCommand.java (90%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/{ => commands}/SearchCommand.java (83%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/{ => commands}/SiteSearchCommand.java (91%) create mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/exceptions/RedirectException.java create mode 100644 marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommandTest.java diff --git a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeSearchE2ETest.java b/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeSearchE2ETest.java index 0475d0e9..af43e462 100644 --- a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeSearchE2ETest.java +++ b/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeSearchE2ETest.java @@ -233,4 +233,12 @@ public class EdgeSearchE2ETest extends E2ETestBase { Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("eval")); } + @Test + public void testBang() throws IOException { + var driver = chrome.getWebDriver(); + + driver.get("http://proxyNginx/search?query=!g test"); + + Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("bang")); + } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchService.java index bc318ddf..a486e63d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchService.java +++ b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchService.java @@ -17,6 +17,7 @@ import nu.marginalia.wmsa.edge.index.client.EdgeIndexClient; import nu.marginalia.wmsa.edge.search.command.CommandEvaluator; import nu.marginalia.wmsa.edge.search.command.ResponseType; import nu.marginalia.wmsa.edge.search.command.SearchParameters; +import nu.marginalia.wmsa.edge.search.exceptions.RedirectException; import nu.marginalia.wmsa.edge.search.query.model.EdgeUserSearchParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -165,6 +166,9 @@ public class EdgeSearchService extends Service { try { return searchCommandEvaulator.eval(ctx, params, humanQuery); } + catch (RedirectException ex) { + response.redirect(ex.newUrl); + } catch (Exception ex) { logger.error("Error", ex); serveError(ctx, response); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/CommandEvaluator.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/CommandEvaluator.java index ba6c94c8..9110969a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/CommandEvaluator.java +++ b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/CommandEvaluator.java @@ -2,14 +2,15 @@ package nu.marginalia.wmsa.edge.search.command; import com.google.inject.Inject; import nu.marginalia.wmsa.configuration.server.Context; +import nu.marginalia.wmsa.edge.search.command.commands.*; import java.util.ArrayList; import java.util.List; public class CommandEvaluator { - List commands = new ArrayList<>(); - + private final List commands = new ArrayList<>(); + private final SearchCommand search; @Inject public CommandEvaluator( @@ -17,13 +18,16 @@ public class CommandEvaluator { ConvertCommand convert, DefinitionCommand define, SiteSearchCommand site, + BangCommand bang, SearchCommand search ) { commands.add(browse); commands.add(convert); commands.add(define); commands.add(site); - commands.add(search); + commands.add(bang); + + this.search = search; } public Object eval(Context ctx, SearchParameters parameters, String query) { @@ -33,8 +37,10 @@ public class CommandEvaluator { return ret.get(); } } - // Search command *should* always evaluate - throw new IllegalStateException("Search Command returned Optional.empty()"); + + // Always process the search command last + return search.process(ctx, parameters, query) + .orElseThrow(() -> new IllegalStateException("Search Command returned Optional.empty()!") /* This Should Not be Possibleā„¢ */ ); } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommand.java new file mode 100644 index 00000000..c5517246 --- /dev/null +++ b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommand.java @@ -0,0 +1,73 @@ +package nu.marginalia.wmsa.edge.search.command.commands; + +import com.google.inject.Inject; +import nu.marginalia.wmsa.configuration.server.Context; +import nu.marginalia.wmsa.edge.assistant.screenshot.ScreenshotService; +import nu.marginalia.wmsa.edge.data.dao.EdgeDataStoreDao; +import nu.marginalia.wmsa.edge.data.dao.task.EdgeDomainBlacklist; +import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.wmsa.edge.model.EdgeId; +import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; +import nu.marginalia.wmsa.edge.search.command.SearchParameters; +import nu.marginalia.wmsa.edge.search.exceptions.RedirectException; +import nu.marginalia.wmsa.edge.search.model.BrowseResultSet; +import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; +import nu.marginalia.wmsa.renderer.mustache.RendererFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +public class BangCommand implements SearchCommandInterface { + private final Map bangsToPattern = new HashMap<>(); + + @Inject + public BangCommand() + { + bangsToPattern.put("!g", "https://www.google.com/search?q=%s"); + bangsToPattern.put("!ddg", "https://duckduckgo.com/search?q=%s"); + } + + @Override + public Optional process(Context ctx, SearchParameters parameters, String query) { + + for (var entry : bangsToPattern.entrySet()) { + String key = entry.getKey(); + matchBangPattern(query, entry.getKey(), entry.getValue()); + + } + + return Optional.empty(); + } + + private void matchBangPattern(String query, String bangKey, String urlPattern) { + for (int idx = query.indexOf(bangKey); idx >= 0; idx = query.indexOf(bangKey, idx + 1)) { + + if (idx > 0) { // Don't match "search term!b", require either "!b term" or "search term !b" + if (!Character.isSpaceChar(query.charAt(idx-1))) { + continue; + } + } + int nextIdx = idx + bangKey.length(); + + if (nextIdx >= query.length()) { // allow "search term !b" + redirect(urlPattern, query.substring(0, idx)); + } + else if (Character.isSpaceChar(query.charAt(nextIdx))) { // skip matches on pattern "!bsearch term" for !b + redirect(urlPattern, query.substring(0, idx).stripTrailing() + " " + query.substring(nextIdx).stripLeading()); + } + } + } + + private void redirect(String pattern, String terms) { + var url = String.format(pattern, URLEncoder.encode(terms.trim(), StandardCharsets.UTF_8)); + throw new RedirectException(url); + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/BrowseCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BrowseCommand.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/BrowseCommand.java rename to marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BrowseCommand.java index bed9bd39..4e711069 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/BrowseCommand.java +++ b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BrowseCommand.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.search.command; +package nu.marginalia.wmsa.edge.search.command.commands; import com.google.inject.Inject; import nu.marginalia.wmsa.configuration.server.Context; @@ -7,6 +7,8 @@ import nu.marginalia.wmsa.edge.data.dao.EdgeDataStoreDao; import nu.marginalia.wmsa.edge.data.dao.task.EdgeDomainBlacklist; import nu.marginalia.wmsa.edge.model.EdgeDomain; import nu.marginalia.wmsa.edge.model.EdgeId; +import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; +import nu.marginalia.wmsa.edge.search.command.SearchParameters; import nu.marginalia.wmsa.edge.search.model.BrowseResultSet; import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; import nu.marginalia.wmsa.renderer.mustache.RendererFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/ConvertCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/ConvertCommand.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/ConvertCommand.java rename to marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/ConvertCommand.java index 87f5558c..ff90ddf8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/ConvertCommand.java +++ b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/ConvertCommand.java @@ -1,8 +1,11 @@ -package nu.marginalia.wmsa.edge.search.command; +package nu.marginalia.wmsa.edge.search.command.commands; import com.google.inject.Inject; import nu.marginalia.wmsa.configuration.server.Context; import nu.marginalia.wmsa.edge.search.UnitConversion; +import nu.marginalia.wmsa.edge.search.command.ResponseType; +import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; +import nu.marginalia.wmsa.edge.search.command.SearchParameters; import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; import nu.marginalia.wmsa.renderer.mustache.RendererFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/DefinitionCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/DefinitionCommand.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/DefinitionCommand.java rename to marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/DefinitionCommand.java index a813b1b9..63c6a981 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/DefinitionCommand.java +++ b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/DefinitionCommand.java @@ -1,11 +1,14 @@ -package nu.marginalia.wmsa.edge.search.command; +package nu.marginalia.wmsa.edge.search.command.commands; import com.google.inject.Inject; import lombok.SneakyThrows; import nu.marginalia.wmsa.configuration.server.Context; import nu.marginalia.wmsa.edge.assistant.client.AssistantClient; import nu.marginalia.wmsa.edge.assistant.dict.DictionaryResponse; +import nu.marginalia.wmsa.edge.search.command.ResponseType; +import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; +import nu.marginalia.wmsa.edge.search.command.SearchParameters; import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; import nu.marginalia.wmsa.renderer.mustache.RendererFactory; import org.slf4j.Logger; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SearchCommand.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchCommand.java rename to marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SearchCommand.java index 6f80d9d5..f0d7c704 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchCommand.java +++ b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SearchCommand.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.search.command; +package nu.marginalia.wmsa.edge.search.command.commands; import com.google.inject.Inject; import nu.marginalia.wmsa.configuration.server.Context; @@ -6,6 +6,9 @@ import nu.marginalia.wmsa.edge.data.dao.EdgeDataStoreDao; import nu.marginalia.wmsa.edge.data.dao.task.EdgeDomainBlacklist; import nu.marginalia.wmsa.edge.search.EdgeSearchOperator; import nu.marginalia.wmsa.edge.search.UnitConversion; +import nu.marginalia.wmsa.edge.search.command.ResponseType; +import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; +import nu.marginalia.wmsa.edge.search.command.SearchParameters; import nu.marginalia.wmsa.edge.search.model.DecoratedSearchResults; import nu.marginalia.wmsa.edge.search.query.model.EdgeUserSearchParameters; import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; @@ -17,10 +20,10 @@ import java.util.Optional; import java.util.concurrent.Future; public class SearchCommand implements SearchCommandInterface { - private EdgeDomainBlacklist blacklist; - private EdgeDataStoreDao dataStoreDao; - private EdgeSearchOperator searchOperator; - private UnitConversion unitConversion; + private final EdgeDomainBlacklist blacklist; + private final EdgeDataStoreDao dataStoreDao; + private final EdgeSearchOperator searchOperator; + private final UnitConversion unitConversion; private final MustacheRenderer searchResultsRenderer; private final MustacheRenderer searchResultsRendererGmi; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SiteSearchCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SiteSearchCommand.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SiteSearchCommand.java rename to marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SiteSearchCommand.java index 21fa9829..60520aa9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SiteSearchCommand.java +++ b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SiteSearchCommand.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.search.command; +package nu.marginalia.wmsa.edge.search.command.commands; import com.google.inject.Inject; import nu.marginalia.wmsa.configuration.server.Context; @@ -8,6 +8,9 @@ import nu.marginalia.wmsa.edge.index.model.IndexBlock; import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; import nu.marginalia.wmsa.edge.search.EdgeSearchOperator; import nu.marginalia.wmsa.edge.search.EdgeSearchProfile; +import nu.marginalia.wmsa.edge.search.command.ResponseType; +import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; +import nu.marginalia.wmsa.edge.search.command.SearchParameters; import nu.marginalia.wmsa.edge.search.model.DecoratedSearchResultSet; import nu.marginalia.wmsa.edge.search.model.DecoratedSearchResults; import nu.marginalia.wmsa.edge.search.model.DomainInformation; @@ -27,7 +30,6 @@ import java.util.function.Predicate; import java.util.regex.Pattern; public class SiteSearchCommand implements SearchCommandInterface { - private EdgeDomainBlacklist blacklist; private final EdgeDataStoreDao dataStoreDao; private final EdgeSearchOperator searchOperator; private DomainInformationService domainInformationService; @@ -39,21 +41,18 @@ public class SiteSearchCommand implements SearchCommandInterface { private final Predicate queryPatternPredicate = Pattern.compile("^site:[.A-Za-z\\-0-9]+$").asPredicate(); @Inject public SiteSearchCommand( - EdgeDomainBlacklist blacklist, + DomainInformationService domainInformationService, EdgeDataStoreDao dataStoreDao, RendererFactory rendererFactory, - EdgeSearchOperator searchOperator, - DomainInformationService domainInformationService) + EdgeSearchOperator searchOperator) throws IOException { - this.blacklist = blacklist; this.dataStoreDao = dataStoreDao; + this.searchOperator = searchOperator; + this.domainInformationService = domainInformationService; siteInfoRenderer = rendererFactory.renderer("edge/site-info"); siteInfoRendererGmi = rendererFactory.renderer("edge/site-info-gmi"); - - this.searchOperator = searchOperator; - this.domainInformationService = domainInformationService; } @Override @@ -63,9 +62,7 @@ public class SiteSearchCommand implements SearchCommandInterface { } var results = siteInfo(ctx, query); - var domain = results.getDomain(); - logger.info("Domain: {}", domain); DecoratedSearchResultSet resultSet; Path screenshotPath = null; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/exceptions/RedirectException.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/exceptions/RedirectException.java new file mode 100644 index 00000000..fc551964 --- /dev/null +++ b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/exceptions/RedirectException.java @@ -0,0 +1,14 @@ +package nu.marginalia.wmsa.edge.search.exceptions; + +public class RedirectException extends RuntimeException { + public final String newUrl; + + public RedirectException(String newUrl) { + this.newUrl = newUrl; + } + + @Override + public StackTraceElement[] getStackTrace() { + return new StackTraceElement[0]; + } +} diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommandTest.java b/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommandTest.java new file mode 100644 index 00000000..b2b4c90b --- /dev/null +++ b/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommandTest.java @@ -0,0 +1,38 @@ +package nu.marginalia.wmsa.edge.search.command.commands; + +import nu.marginalia.wmsa.edge.search.exceptions.RedirectException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.fail; + +class BangCommandTest { + + @Test + public void testBang() { + var bc = new BangCommand(); + + expectRedirectUrl("https://www.google.com/search?q=search+terms", () -> bc.process(null, null, "search terms !g")); + expectNoRedirect(() -> bc.process(null, null, "search terms!g")); + expectNoRedirect(() -> bc.process(null, null, "!gsearch terms")); + expectRedirectUrl("https://www.google.com/search?q=search+terms", () -> bc.process(null, null, "!g search terms")); + } + + void expectNoRedirect(Runnable op) { + try { + op.run(); + } + catch (RedirectException ex) { + fail("Expected no redirection, but got " + ex.newUrl); + } + } + void expectRedirectUrl(String expectedUrl, Runnable op) { + try { + op.run(); + fail("Didn't intercept exception"); + } + catch (RedirectException ex) { + Assertions.assertEquals(expectedUrl, ex.newUrl, "Unexpected redirect"); + } + } +} \ No newline at end of file