260 lines
9.6 KiB
Python
260 lines
9.6 KiB
Python
import subprocess
|
|
import urllib.parse
|
|
from typing import List
|
|
|
|
from modules.launcher.plugin_base import PluginBase
|
|
from modules.launcher.result import Result
|
|
|
|
|
|
class WebSearchPlugin(PluginBase):
|
|
"""
|
|
Web search plugin that supports multiple search engines.
|
|
"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.display_name = "Web Search"
|
|
self.description = "Search the web using various search engines"
|
|
self.current_trigger = "" # Track the current active trigger
|
|
|
|
# Define search engines with their URLs and icons
|
|
self.search_engines = {
|
|
"google": {
|
|
"name": "Google",
|
|
"url": "https://www.google.com/search?q={}",
|
|
"icon": "google-symbolic",
|
|
"description": "Search with Google",
|
|
},
|
|
"duckduckgo": {
|
|
"name": "DuckDuckGo",
|
|
"url": "https://duckduckgo.com/?q={}",
|
|
"icon": "preferences-desktop-search",
|
|
"description": "Search with DuckDuckGo (privacy-focused)",
|
|
},
|
|
"youtube": {
|
|
"name": "YouTube",
|
|
"url": "https://www.youtube.com/results?search_query={}",
|
|
"icon": "youtube-symbolic",
|
|
"description": "Search videos on YouTube",
|
|
},
|
|
"github": {
|
|
"name": "GitHub",
|
|
"url": "https://github.com/search?q={}",
|
|
"icon": "github-desktop-symbolic",
|
|
"description": "Search repositories on GitHub",
|
|
},
|
|
"stackoverflow": {
|
|
"name": "Stack Overflow",
|
|
"url": "https://stackoverflow.com/search?q={}",
|
|
"icon": "stack",
|
|
"description": "Search programming questions on Stack Overflow",
|
|
},
|
|
"wikipedia": {
|
|
"name": "Wikipedia",
|
|
"url": "https://en.wikipedia.org/wiki/Special:Search?search={}",
|
|
"icon": "search-menus-symbolic",
|
|
"description": "Search articles on Wikipedia",
|
|
},
|
|
"reddit": {
|
|
"name": "Reddit",
|
|
"url": "https://www.reddit.com/search/?q={}",
|
|
"icon": "reddit-symbolic",
|
|
"description": "Search discussions on Reddit",
|
|
},
|
|
"linkedin": {
|
|
"name": "LinkedIn",
|
|
"url": "https://www.linkedin.com/search/results/all/?keywords={}",
|
|
"icon": "link",
|
|
"description": "Search professionals and jobs on LinkedIn",
|
|
},
|
|
}
|
|
|
|
# Default search engine
|
|
self.default_engine = "google"
|
|
|
|
def initialize(self):
|
|
"""Initialize the web search plugin."""
|
|
# Set up triggers for web search - only main triggers, not individual search engines
|
|
triggers = ["?"]
|
|
|
|
# Don't add individual search engine triggers to avoid cluttering trigger keywords
|
|
# Search engines can still be used within web search context (e.g., "web google cats")
|
|
|
|
self.set_triggers(triggers)
|
|
|
|
def get_active_trigger(self, query_string: str) -> str:
|
|
"""Override to track which trigger was activated."""
|
|
trigger = super().get_active_trigger(query_string)
|
|
if trigger:
|
|
self.current_trigger = trigger.strip()
|
|
return trigger
|
|
|
|
def cleanup(self):
|
|
"""Cleanup the web search plugin."""
|
|
pass
|
|
|
|
def query(self, query_string: str) -> List[Result]:
|
|
"""Process web search queries."""
|
|
results = []
|
|
|
|
if not query_string.strip():
|
|
# Show available search engines when no query
|
|
return self._get_search_engine_list()
|
|
|
|
# Check if the query is a URL
|
|
if self._is_url(query_string):
|
|
results.append(self._create_url_result(query_string))
|
|
return results
|
|
|
|
# Parse query to check if it starts with a search engine name
|
|
engine_name, search_query = self._parse_engine_query(query_string)
|
|
|
|
if engine_name and engine_name in self.search_engines:
|
|
# Specific search engine specified in query (e.g., "google cats")
|
|
if search_query:
|
|
# Search with specific engine
|
|
results.append(self._create_search_result(engine_name, search_query))
|
|
else:
|
|
# Show specific engine info
|
|
results.append(self._create_engine_info_result(engine_name))
|
|
else:
|
|
# General search - offer multiple engines
|
|
search_query = query_string.strip()
|
|
|
|
# Add default search engine first
|
|
results.append(
|
|
self._create_search_result(self.default_engine, search_query)
|
|
)
|
|
|
|
# Add other popular search engines
|
|
popular_engines = ["duckduckgo", "youtube", "github"]
|
|
for engine in popular_engines:
|
|
if engine != self.default_engine:
|
|
results.append(self._create_search_result(engine, search_query))
|
|
|
|
return results
|
|
|
|
def _parse_engine_query(self, query: str) -> tuple[str, str]:
|
|
"""Parse query to extract search engine and search terms."""
|
|
query = query.strip().lower()
|
|
|
|
for engine in self.search_engines.keys():
|
|
if query.startswith(f"{engine} "):
|
|
return engine, query[len(engine) :].strip()
|
|
elif query == engine:
|
|
return engine, ""
|
|
|
|
return "", query
|
|
|
|
def _is_url(self, text: str) -> bool:
|
|
"""Check if the text is a URL."""
|
|
text = text.strip().lower()
|
|
return text.startswith(("http://", "https://", "www.")) or (
|
|
"." in text and " " not in text and len(text) > 3
|
|
)
|
|
|
|
def _create_url_result(self, url: str) -> Result:
|
|
"""Create a result for opening a URL directly."""
|
|
# Add protocol if missing
|
|
if not url.startswith(("http://", "https://")):
|
|
if url.startswith("www."):
|
|
url = "https://" + url
|
|
else:
|
|
url = "https://" + url
|
|
|
|
return Result(
|
|
title=f"Open {url}",
|
|
subtitle="Open this URL in your default browser",
|
|
icon_name="link",
|
|
action=lambda u=url: self._open_url(u),
|
|
relevance=1.0,
|
|
plugin_name=self.display_name,
|
|
data={"type": "url", "url": url},
|
|
)
|
|
|
|
def _open_url(self, url: str):
|
|
"""Open a URL directly in the default browser."""
|
|
try:
|
|
subprocess.Popen(
|
|
["xdg-open", url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
|
)
|
|
except Exception as e:
|
|
print(f"Failed to open URL: {e}")
|
|
|
|
def _get_search_engine_list(self) -> List[Result]:
|
|
"""Get list of available search engines."""
|
|
results = []
|
|
|
|
for engine_id, engine_info in self.search_engines.items():
|
|
result = Result(
|
|
title=engine_info["name"],
|
|
subtitle=engine_info["description"],
|
|
icon_name=engine_info["icon"],
|
|
action=lambda e=engine_id: self._show_engine_help(e),
|
|
relevance=1.0 if engine_id == self.default_engine else 0.8,
|
|
plugin_name=self.display_name,
|
|
data={"type": "engine_info", "engine": engine_id},
|
|
)
|
|
results.append(result)
|
|
|
|
return results
|
|
|
|
def _create_search_result(self, engine_id: str, query: str) -> Result:
|
|
"""Create a search result for a specific engine and query."""
|
|
engine_info = self.search_engines[engine_id]
|
|
|
|
return Result(
|
|
title=f"Search '{query}' on {engine_info['name']}",
|
|
subtitle=f"{engine_info['description']} - {query}",
|
|
icon_name=engine_info["icon"],
|
|
action=lambda e=engine_id, q=query: self._perform_search(e, q),
|
|
relevance=1.0 if engine_id == self.default_engine else 0.9,
|
|
plugin_name=self.display_name,
|
|
data={"type": "search", "engine": engine_id, "query": query},
|
|
)
|
|
|
|
def _create_engine_info_result(self, engine_id: str) -> Result:
|
|
"""Create an info result for a specific search engine."""
|
|
engine_info = self.search_engines[engine_id]
|
|
|
|
return Result(
|
|
title=f"{engine_info['name']} Search",
|
|
subtitle=f"{engine_info['description']} - Type your search query",
|
|
icon_name=engine_info["icon"],
|
|
action=lambda: None, # No action for info result
|
|
relevance=1.0,
|
|
plugin_name=self.display_name,
|
|
data={"type": "engine_ready", "engine": engine_id},
|
|
)
|
|
|
|
def _perform_search(self, engine_id: str, query: str):
|
|
"""Perform a web search using the specified engine."""
|
|
if not query.strip():
|
|
return
|
|
|
|
engine_info = self.search_engines.get(engine_id)
|
|
if not engine_info:
|
|
return
|
|
|
|
# URL encode the search query
|
|
encoded_query = urllib.parse.quote_plus(query)
|
|
search_url = engine_info["url"].format(encoded_query)
|
|
|
|
try:
|
|
# Open the search URL in the default browser
|
|
subprocess.Popen(
|
|
["xdg-open", search_url],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
except Exception as e:
|
|
print(f"Failed to open search URL: {e}")
|
|
|
|
def _show_engine_help(self, engine_id: str):
|
|
"""Show help for a specific search engine."""
|
|
engine_info = self.search_engines.get(engine_id)
|
|
if engine_info:
|
|
print(
|
|
f"Search engine: {engine_info['name']} - {engine_info['description']}"
|
|
)
|