Added rudimentary !bang-support

This commit is contained in:
vlofgren 2022-05-30 17:26:44 +02:00
parent cfd01c7dbe
commit 275e42197c
11 changed files with 175 additions and 24 deletions

View File

@ -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"));
}
}

View File

@ -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);

View File

@ -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<SearchCommandInterface> commands = new ArrayList<>();
private final List<SearchCommandInterface> 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™ */ );
}
}

View File

@ -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<String, String> 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<Object> 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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<DecoratedSearchResults> searchResultsRenderer;
private final MustacheRenderer<DecoratedSearchResults> searchResultsRendererGmi;

View File

@ -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<String> 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;

View File

@ -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];
}
}

View File

@ -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");
}
}
}