Skip to content

Commit

Permalink
feat: support suggestion
Browse files Browse the repository at this point in the history
  • Loading branch information
Daydreamer-riri committed Mar 26, 2024
1 parent cf56e1c commit 9ab6d55
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@
<None Update="Images\Reload.light.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\Suggestion.dark.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\Suggestion.light.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
97 changes: 79 additions & 18 deletions Community.PowerToys.Run.Plugin.WebSearchShortcut/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Windows.Controls;
using System.Windows.Input;
using Community.PowerToys.Run.Plugin.WebSearchShortcut.Models;
using Community.PowerToys.Run.Plugin.WebSearchShortcut.Suggestion;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Wox.Infrastructure;
Expand All @@ -20,7 +21,7 @@ namespace Community.PowerToys.Run.Plugin.WebSearchShortcut
/// <summary>
/// Main class of this plugin that implement all used interfaces.
/// </summary>
public class Main : IPlugin, IContextMenu, ISettingProvider, IDisposable
public class Main : IPlugin, IDelayedExecutionPlugin, IContextMenu, ISettingProvider, IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="Main"/> class.
Expand Down Expand Up @@ -69,7 +70,8 @@ internal Main(WebSearchShortcutSettings settings, IWebSearchShortcutStorage webS
{
{ "Search", @"Images\Search.light.png" },
{ "Config", @"Images\Config.light.png" },
{ "Reload", @"Images\Reload.light.png" }
{ "Reload", @"Images\Reload.light.png" },
{ "Suggestion", @"Images\Suggestion.light.png" },
};

private bool Disposed { get; set; }
Expand All @@ -80,6 +82,8 @@ internal Main(WebSearchShortcutSettings settings, IWebSearchShortcutStorage webS

private IWebSearchShortcutStorage WebSearchShortcutStorage { get; }

private Suggestions suggestion = new();

/// <summary>
/// Return a filtered list, based on the given query.
/// </summary>
Expand All @@ -100,6 +104,7 @@ public List<Result> Query(Query query)
[
new()
{
QueryTextDisplay = args,
Title = "Reload",
SubTitle = "Reload data from config file",
IcoPath = IconPath["Reload"],
Expand All @@ -118,6 +123,7 @@ public List<Result> Query(Query query)
[
new()
{
QueryTextDisplay = args,
Title = "Open Config File",
SubTitle = "Open the config file in the default editor",
IcoPath = IconPath["Config"],
Expand Down Expand Up @@ -152,22 +158,7 @@ public List<Result> Query(Query query)
return [];
}

string searchQuery = WebUtility.UrlEncode(tokens[1]);
string arguments = item.Url.Replace("%s", searchQuery);
return [
new Result
{
QueryTextDisplay = args,
IcoPath = item.IconPath ?? IconPath["Search"],
Title = $"{item.Name}{tokens[1]}",
SubTitle = $"Search for {tokens[1]} using {item.Name}",
ProgramArguments = arguments,
Action = _ => OpenInBrowser(arguments),
Score = 1000,
ToolTipData = new ToolTipData("Open", $"{arguments}"),
ContextData = item,
}
];
return [GetResultForSearch(item, tokens[1], query), .. SuggestionsCache];

Result GetResultForSelect(Item item) => new()
{
Expand All @@ -187,6 +178,76 @@ public List<Result> Query(Query query)
};
}

public List<Result> Query(Query query, bool delayedExecution)
{
if (query?.Search is null || !delayedExecution)
{
return ResetSuggestionsCache();
}
var tokens = query.Search.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
if (tokens.Length == 1)
{
return ResetSuggestionsCache();
}
var item = WebSearchShortcutStorage.GetRecord(tokens[0]);
if (item is null || string.IsNullOrEmpty(item.SuggestionProvider))
{
return ResetSuggestionsCache();
}
var suggestions = suggestion.QuerySuggestionsAsync(item.SuggestionProvider, tokens[1]).Result;
if (suggestions.Count == 0)
{
return ResetSuggestionsCache();
}
var res = suggestions.Select(s => GetResultForSuggestion(item, s, query)).ToList();
SuggestionsCache = [..res];

res.Insert(0, GetResultForSearch(item, tokens[1], query));

return res;

List<Result> ResetSuggestionsCache()
{
SuggestionsCache = [];
return [];
}
}

private List<Result> SuggestionsCache { get; set; } = [];

private static Result GetResultForSearch(Item item, string search, Query query)
{
string searchQuery = WebUtility.UrlEncode(search);
string arguments = item.Url.Replace("%s", searchQuery);
return new Result
{
QueryTextDisplay = query.Search,
IcoPath = item.IconPath ?? IconPath["Search"],
Title = $"{item.Name}{search}",
SubTitle = $"Search for {search} using {item.Name}",
ProgramArguments = arguments,
Action = _ => OpenInBrowser(arguments),
Score = 1000,
ToolTipData = new ToolTipData("Open", $"{arguments}"),
ContextData = item,
};
}

private static Result GetResultForSuggestion(Item item, string suggest, Query query)
{
var search = query.Search.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1];
return new Result
{
QueryTextDisplay = query.Search.Replace(search, suggest),
IcoPath = IconPath["Suggestion"],
Title = suggest,
SubTitle = $"Search for {suggest} using {item.Name}",
Action = _ => OpenInBrowser(item.Url.Replace("%s", WebUtility.UrlEncode(suggest))),
ContextData = item,
Score = 99,
};
}

public List<ContextMenuResult> LoadContextMenus(Result result)
{
if (result?.ContextData is string)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public partial class Item

public string Url { get; set; } = string.Empty;

public string? SuggestionProvider { get; set; }

public string Domain {
get
{
Expand Down
4 changes: 2 additions & 2 deletions Community.PowerToys.Run.Plugin.WebSearchShortcut/Storage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ public void Load()
{
var initData = new Dictionary<string, Item>
{
{ "Google", new Item { Url = "https://www.google.com/search?q=%s" } },
{ "Bing", new Item { Url = "https://www.bing.com/search?q=%s" } },
{ "Google", new Item { Url = "https://www.google.com/search?q=%s", SuggestionProvider = "Google" } },
{ "Bing", new Item { Url = "https://www.bing.com/search?q=%s", SuggestionProvider = "Bing" } },
{ "GitHub", new Item { Url = "https://www.github.com/search?q=%s", Keyword = "gh" } },
};

Expand Down
33 changes: 33 additions & 0 deletions Community.PowerToys.Run.Plugin.WebSearchShortcut/Suggestion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Community.PowerToys.Run.Plugin.WebSearchShortcut.Suggestion
{
public interface IWebSearchShortcutSuggestionsProvider
{
Task<List<string>> QuerySuggestionsAsync(string query);
}

public class SuggestionsItem(string title, string? description)
{
public string Title { get; } = title;
public string? Description { get; } = description;
}

public class Suggestions
{
public Task<List<string>> QuerySuggestionsAsync(string name, string query)
{
var provider = SuggestionProviders[name];
if (provider != null)
{
return provider.QuerySuggestionsAsync(query);
}
return Task.FromResult(new List<string>());
}
public Dictionary<string, IWebSearchShortcutSuggestionsProvider> SuggestionProviders { get; set; } = new(){
{ Google.Name, new Google() },
{ Bing.Name, new Bing() },
};
}
}
21 changes: 0 additions & 21 deletions Community.PowerToys.Run.Plugin.WebSearchShortcut/Suggestions.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net.Http;
using System.Text.Json;
using Wox.Plugin.Logger;

namespace Community.PowerToys.Run.Plugin.WebSearchShortcut.Suggestion
{
public class Bing : IWebSearchShortcutSuggestionsProvider
{
static public string Name => "Bing";

private HttpClient Http { get; } = new HttpClient();

public async Task<List<string>> QuerySuggestionsAsync(string query)
{
try
{
const string api = "https://api.bing.com/qsonhs.aspx?q=";

await using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query)).ConfigureAwait(false);

using var json = await JsonDocument.ParseAsync(resultStream);
var root = json.RootElement.GetProperty("AS");

if (root.GetProperty("FullResults").GetInt32() == 0)
return new List<string>();

return root.GetProperty("Results")
.EnumerateArray()
.SelectMany(r => r.GetProperty("Suggests")
.EnumerateArray()
.Select(s => s.GetProperty("Txt").GetString()))
.Where(s => s != null)
.Select(s => s!)
.ToList();

}
catch (Exception e) when (e is HttpRequestException or { InnerException: TimeoutException })
{
Log.Error($"{e.Message}", typeof(Bing));
return [];
}
}

public override string ToString()
{
return "Bing";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net.Http;
using System.Text.Json;
using Wox.Plugin.Logger;

namespace Community.PowerToys.Run.Plugin.WebSearchShortcut.Suggestion
{
public class Google : IWebSearchShortcutSuggestionsProvider
{
static public string Name => "Google";

private HttpClient Http { get; } = new HttpClient();

public async Task<List<string>> QuerySuggestionsAsync(string query)
{
try
{
const string api = "https://www.google.com/complete/search?output=chrome&q=";

await using var resultStream = await Http.GetStreamAsync(api + Uri.EscapeDataString(query)).ConfigureAwait(false);

using var json = await JsonDocument.ParseAsync(resultStream);

var results = json.RootElement.EnumerateArray().ElementAt(1);

return results
.EnumerateArray()
.Select(o => o.GetString())
.Where(s => s != null)
.Select(s => s!)
.ToList();
}
catch (Exception e)
{
Log.Error($"{e.Message}", typeof(Google));
return [];
}
}

public override string ToString()
{
return "Google";
}
}
}

0 comments on commit 9ab6d55

Please sign in to comment.