admin plugin and roomstats plugin added. cron fixed and ddg fixed

This commit is contained in:
2026-05-07 15:28:50 -05:00
parent 4b10c13b29
commit 10a6028037
7 changed files with 1673 additions and 529 deletions
+302 -467
View File
@@ -1,510 +1,345 @@
#!/usr/bin/env python3
"""
This plugin provides DuckDuckGo search functionality using the DuckDuckGo Instant Answer API.
DuckDuckGo search plugin (ddgs library). Results are shown inside collapsible details boxes.
"""
import asyncio
import logging
import requests
import json
from html import escape
import simplematrixbotlib as botlib
from urllib.parse import quote, urlencode
import html
from ddgs import DDGS
DDG_API_URL = "https://api.duckduckgo.com/"
DDG_SEARCH_URL = "https://html.duckduckgo.com/html/"
logger = logging.getLogger("ddg")
# ---------------------------------------------------------------------------
# Async search wrapper
# ---------------------------------------------------------------------------
async def _async_search(func, *args, **kwargs):
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, lambda: func(*args, **kwargs))
# ---------------------------------------------------------------------------
# Command handler
# ---------------------------------------------------------------------------
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle DuckDuckGo search commands.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (Bot): The bot object.
prefix (str): The command prefix.
config (dict): Configuration parameters.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("ddg"):
logging.info("Received !ddg command")
if not (match.is_not_from_this_bot() and match.prefix() and match.command("ddg")):
return
args = match.args()
args = match.args()
if not args:
await send_help(room, bot)
return
if len(args) < 1:
await show_usage(room, bot)
subcommand = args[0].lower()
# ---- Instant answer (default) ----
if subcommand in ("instant", "i"):
query = " ".join(args[1:]) if len(args) > 1 else ""
if not query:
await bot.api.send_text_message(room.room_id, "Usage: !ddg instant <query>")
return
await instant_answer(room, bot, query)
subcommand = args[0].lower()
if subcommand == "search":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg search <query>")
return
query = ' '.join(args[1:])
await ddg_search(room, bot, query)
elif subcommand == "instant":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg instant <query>")
return
query = ' '.join(args[1:])
await ddg_instant_answer(room, bot, query)
elif subcommand == "image":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg image <query>")
return
query = ' '.join(args[1:])
await ddg_image_search(room, bot, query)
elif subcommand == "news":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg news <query>")
return
query = ' '.join(args[1:])
await ddg_news_search(room, bot, query)
elif subcommand == "video":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg video <query>")
return
query = ' '.join(args[1:])
await ddg_video_search(room, bot, query)
elif subcommand == "bang":
if len(args) < 2:
await show_bang_help(room, bot)
return
bang_query = ' '.join(args[1:])
await ddg_bang_search(room, bot, bang_query)
elif subcommand == "define":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg define <word>")
return
word = ' '.join(args[1:])
await ddg_definition(room, bot, word)
elif subcommand == "calc":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg calc <expression>")
return
expression = ' '.join(args[1:])
await ddg_calculator(room, bot, expression)
elif subcommand == "weather":
location = ' '.join(args[1:]) if len(args) > 1 else ""
await ddg_weather(room, bot, location)
elif subcommand == "help":
await show_usage(room, bot)
else:
# Default to instant answer search
query = ' '.join(args)
await ddg_instant_answer(room, bot, query)
async def show_usage(room, bot):
"""Display DuckDuckGo command usage."""
usage = """
<strong>🦆 DuckDuckGo Search Commands</strong>
<strong>!ddg &lt;query&gt;</strong> - Instant answer search (default)
<strong>!ddg search &lt;query&gt;</strong> - Web search with results
<strong>!ddg instant &lt;query&gt;</strong> - Instant answer with detailed info
<strong>!ddg image &lt;query&gt;</strong> - Image search
<strong>!ddg news &lt;query&gt;</strong> - News search
<strong>!ddg video &lt;query&gt;</strong> - Video search
<strong>!ddg bang &lt;!bang query&gt;</strong> - Use DuckDuckGo bangs
<strong>!ddg define &lt;word&gt;</strong> - Word definitions
<strong>!ddg calc &lt;expression&gt;</strong> - Calculator
<strong>!ddg weather [location]</strong> - Weather information
<strong>!ddg help</strong> - Show this help
<strong>Examples:</strong>
• <code>!ddg python programming</code>
• <code>!ddg search matrix protocol</code>
• <code>!ddg image cute cats</code>
• <code>!ddg bang !w matrix</code>
• <code>!ddg define serendipity</code>
• <code>!ddg calc 2+2*5</code>
• <code>!ddg weather London</code>
<strong>Popular Bangs:</strong>
• <code>!w</code> - Wikipedia
• <code>!g</code> - Google
• <code>!yt</code> - YouTube
• <code>!aw</code> - ArchWiki
• <code>!gh</code> - GitHub
• <code>!so</code> - Stack Overflow
"""
await bot.api.send_markdown_message(room.room_id, usage)
async def show_bang_help(room, bot):
"""Display DuckDuckGo bang help."""
bang_help = """
<strong>🦆 DuckDuckGo Bangs</strong>
<strong>Usage:</strong> <code>!ddg bang &lt;!bang query&gt;</code>
<strong>Popular Bangs:</strong>
• <code>!ddg bang !w matrix</code> - Search Wikipedia
• <code>!ddg bang !g python</code> - Search Google
• <code>!ddg bang !yt music</code> - Search YouTube
• <code>!ddg bang !aw arch</code> - Search ArchWiki
• <code>!ddg bang !gh repository</code> - Search GitHub
• <code>!ddg bang !so error</code> - Search Stack Overflow
• <code>!ddg bang !amazon book</code> - Search Amazon
• <code>!ddg bang !imdb movie</code> - Search IMDb
• <code>!ddg bang !reddit topic</code> - Search Reddit
• <code>!ddg bang !tw tweet</code> - Search Twitter
<strong>More Bangs:</strong>
• <code>!ddg</code> - DuckDuckGo
• <code>!bing</code> - Bing
• <code>!ddgimages</code> - DuckDuckGo Images
• <code>!npm</code> - npm packages
• <code>!cpp</code> - C++ reference
• <code>!python</code> - Python docs
• <code>!rust</code> - Rust docs
• <code>!mdn</code> - MDN Web Docs
<em>Thousands of bangs available! See: https://duckduckgo.com/bangs</em>
"""
await bot.api.send_markdown_message(room.room_id, bang_help)
async def ddg_instant_answer(room, bot, query):
"""Get DuckDuckGo instant answer."""
try:
params = {
'q': query,
'format': 'json',
'no_html': '1',
'skip_disambig': '1',
'no_redirect': '1'
}
logging.info(f"Fetching DuckDuckGo instant answer for: {query}")
response = requests.get(DDG_API_URL, params=params, timeout=10)
if response.status_code != 200:
# If API fails, provide direct search link
search_url = f"https://duckduckgo.com/?q={quote(query)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🦆 DuckDuckGo: {html.escape(query)}</strong><br><br>"
f"API temporarily unavailable. <a href='{search_url}'>Search on DuckDuckGo</a>"
)
# ---- Web search ----
elif subcommand == "search":
query = " ".join(args[1:]) if len(args) > 1 else ""
if not query:
await bot.api.send_text_message(room.room_id, "Usage: !ddg search <query>")
return
await web_search(room, bot, query)
data = response.json()
output = f"<strong>🦆 DuckDuckGo: {html.escape(query)}</strong><br><br>"
# Handle different answer types
if data.get('AbstractText'):
# Wikipedia-style answer
output += f"<strong>📚 {data.get('Heading', 'Definition')}</strong><br>"
output += f"{html.escape(data['AbstractText'])}<br>"
if data.get('AbstractURL'):
output += f"<a href='{data['AbstractURL']}'>Read more on {data.get('AbstractSource', 'Wikipedia')}</a><br>"
elif data.get('Answer'):
# Direct answer
output += f"<strong>💡 Answer</strong><br>"
output += f"{html.escape(data['Answer'])}<br>"
elif data.get('Definition'):
# Definition
output += f"<strong>📖 Definition</strong><br>"
output += f"{html.escape(data['Definition'])}<br>"
if data.get('DefinitionSource'):
output += f"<em>Source: {data['DefinitionSource']}</em><br>"
elif data.get('Results'):
# List of results
output += f"<strong>🔍 Results</strong><br>"
for result in data['Results'][:3]:
output += f"• <a href='{result.get('FirstURL', '#')}'>{html.escape(result.get('Text', 'Result'))}</a><br>"
elif data.get('RelatedTopics'):
# Related topics
output += f"<strong>🔗 Related Topics</strong><br>"
for topic in data['RelatedTopics'][:3]:
if isinstance(topic, dict) and topic.get('FirstURL'):
output += f"• <a href='{topic['FirstURL']}'>{html.escape(topic.get('Text', 'Topic'))}</a><br>"
elif isinstance(topic, dict) and topic.get('Name'):
output += f"{html.escape(topic['Name'])}<br>"
else:
# No instant answer found, show search results
output += "<strong>🔍 No instant answer found.</strong><br>"
# Add search link
search_url = f"https://duckduckgo.com/?q={quote(query)}"
output += f"<br><a href='{search_url}'>View all results on DuckDuckGo</a>"
await bot.api.send_markdown_message(room.room_id, output)
except Exception as e:
# Fallback to direct search link
search_url = f"https://duckduckgo.com/?q={quote(query)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🦆 DuckDuckGo: {html.escape(query)}</strong><br><br>"
f"Error accessing API. <a href='{search_url}'>Search on DuckDuckGo</a>"
)
logging.error(f"Error in ddg_instant_answer: {e}")
async def ddg_search(room, bot, query):
"""Perform web search with multiple results."""
try:
await ddg_web_search(room, bot, query, limit=5)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error performing search: {str(e)}")
async def ddg_web_search(room, bot, query, limit=5):
"""Perform web search and return results."""
try:
params = {
'q': query,
'format': 'json'
}
response = requests.get(DDG_API_URL, params=params, timeout=10)
if response.status_code != 200:
# Fallback to direct search
search_url = f"https://duckduckgo.com/?q={quote(query)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🔍 DuckDuckGo Search: {html.escape(query)}</strong><br><br>"
f"API temporarily unavailable. <a href='{search_url}'>Search on DuckDuckGo</a>"
)
# ---- Image search ----
elif subcommand == "image":
query = " ".join(args[1:]) if len(args) > 1 else ""
if not query:
await bot.api.send_text_message(room.room_id, "Usage: !ddg image <query>")
return
await image_search(room, bot, query)
data = response.json()
output = f"<strong>🔍 DuckDuckGo Search: {html.escape(query)}</strong><br><br>"
results_shown = 0
# Show instant answer if available
if data.get('AbstractText') and results_shown < limit:
output += f"<strong>💡 {data.get('Heading', 'Instant Answer')}</strong><br>"
abstract = data['AbstractText'][:200] + "..." if len(data['AbstractText']) > 200 else data['AbstractText']
output += f"{html.escape(abstract)}<br>"
if data.get('AbstractURL'):
output += f"<a href='{data['AbstractURL']}'>Read more</a><br>"
output += "<br>"
results_shown += 1
# Show web results
if data.get('Results') and results_shown < limit:
output += "<strong>🌐 Web Results</strong><br>"
for result in data['Results'][:limit - results_shown]:
output += f"• <a href='{result.get('FirstURL', '#')}'>{html.escape(result.get('Text', 'Result'))}</a><br>"
results_shown += 1
# Show related topics
if data.get('RelatedTopics') and results_shown < limit:
output += "<strong>🔗 Related Topics</strong><br>"
for topic in data['RelatedTopics'][:limit - results_shown]:
if isinstance(topic, dict) and topic.get('FirstURL'):
output += f"• <a href='{topic['FirstURL']}'>{html.escape(topic.get('Text', 'Topic'))}</a><br>"
results_shown += 1
# Add search link
search_url = f"https://duckduckgo.com/?q={quote(query)}"
output += f"<br><a href='{search_url}'>View all results on DuckDuckGo</a>"
await bot.api.send_markdown_message(room.room_id, output)
except Exception as e:
# Fallback to direct search
search_url = f"https://duckduckgo.com/?q={quote(query)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🔍 DuckDuckGo Search: {html.escape(query)}</strong><br><br>"
f"Error accessing API. <a href='{search_url}'>Search on DuckDuckGo</a>"
)
logging.error(f"Error in ddg_web_search: {e}")
async def ddg_image_search(room, bot, query):
"""Perform image search."""
try:
params = {
'q': query,
'format': 'json',
'iax': 'images',
'ia': 'images'
}
response = requests.get(DDG_API_URL, params=params, timeout=10)
if response.status_code != 200:
search_url = f"https://duckduckgo.com/?q={quote(query)}&iax=images&ia=images"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🖼️ DuckDuckGo Images: {html.escape(query)}</strong><br><br>"
f"API temporarily unavailable. <a href='{search_url}'>Search images on DuckDuckGo</a>"
)
# ---- News search ----
elif subcommand == "news":
query = " ".join(args[1:]) if len(args) > 1 else ""
if not query:
await bot.api.send_text_message(room.room_id, "Usage: !ddg news <query>")
return
await news_search(room, bot, query)
data = response.json()
# ---- Video search ----
elif subcommand == "video":
query = " ".join(args[1:]) if len(args) > 1 else ""
if not query:
await bot.api.send_text_message(room.room_id, "Usage: !ddg video <query>")
return
await video_search(room, bot, query)
output = f"<strong>🖼️ DuckDuckGo Images: {html.escape(query)}</strong><br><br>"
# ---- Bang search ----
elif subcommand == "bang":
bang_query = " ".join(args[1:]) if len(args) > 1 else ""
if not bang_query:
await bang_help(room, bot)
return
await bang_search(room, bot, bang_query)
if data.get('Results'):
output += "<strong>📸 Image Results</strong><br>"
for image in data['Results'][:3]:
output += f"• <a href='{image.get('Image', '#')}'>{html.escape(image.get('Title', 'Image'))}</a><br>"
if image.get('Width') and image.get('Height'):
output += f" Size: {image['Width']}×{image['Height']}<br>"
else:
output += "No image results found.<br>"
# ---- Definitions ----
elif subcommand == "define":
word = " ".join(args[1:]) if len(args) > 1 else ""
if not word:
await bot.api.send_text_message(room.room_id, "Usage: !ddg define <word>")
return
await definition(room, bot, word)
# Add search link
search_url = f"https://duckduckgo.com/?q={quote(query)}&iax=images&ia=images"
output += f"<br><a href='{search_url}'>View all images on DuckDuckGo</a>"
# ---- Calculator ----
elif subcommand == "calc":
expr = " ".join(args[1:]) if len(args) > 1 else ""
if not expr:
await bot.api.send_text_message(room.room_id, "Usage: !ddg calc <expression>")
return
await calculator(room, bot, expr)
await bot.api.send_markdown_message(room.room_id, output)
except Exception as e:
search_url = f"https://duckduckgo.com/?q={quote(query)}&iax=images&ia=images"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🖼️ DuckDuckGo Images: {html.escape(query)}</strong><br><br>"
f"Error accessing API. <a href='{search_url}'>Search images on DuckDuckGo</a>"
)
async def ddg_news_search(room, bot, query):
"""Perform news search."""
try:
search_url = f"https://duckduckgo.com/?q={quote(query)}&iar=news"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>📰 DuckDuckGo News: {html.escape(query)}</strong><br><br>"
f"<a href='{search_url}'>View news on DuckDuckGo</a>"
)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error performing news search: {str(e)}")
async def ddg_video_search(room, bot, query):
"""Perform video search."""
try:
search_url = f"https://duckduckgo.com/?q={quote(query)}&iar=videos"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🎬 DuckDuckGo Videos: {html.escape(query)}</strong><br><br>"
f"<a href='{search_url}'>View videos on DuckDuckGo</a>"
)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error performing video search: {str(e)}")
async def ddg_bang_search(room, bot, bang_query):
"""Perform search using DuckDuckGo bangs."""
try:
# Create search URL directly - this is more reliable than API for bangs
search_url = f"https://duckduckgo.com/?q={quote(bang_query)}"
# Common bangs with descriptions
bang_descriptions = {
'!w': 'Wikipedia',
'!g': 'Google',
'!yt': 'YouTube',
'!aw': 'ArchWiki',
'!gh': 'GitHub',
'!so': 'Stack Overflow',
'!amazon': 'Amazon',
'!imdb': 'IMDb',
'!reddit': 'Reddit',
'!tw': 'Twitter'
}
# Extract bang for description
bang = bang_query.split(' ')[0] if ' ' in bang_query else bang_query
description = bang_descriptions.get(bang, 'Site-specific search')
output = f"<strong>🎯 DuckDuckGo Bang: {html.escape(bang)}</strong><br>"
output += f"<strong>Description:</strong> {description}<br>"
if ' ' in bang_query:
output += f"<strong>Query:</strong> {html.escape(bang_query.split(' ', 1)[1])}<br><br>"
output += f"<a href='{search_url}'>Search with {html.escape(bang)} on DuckDuckGo</a>"
await bot.api.send_markdown_message(room.room_id, output)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error with bang search: {str(e)}")
async def ddg_definition(room, bot, word):
"""Get word definition."""
try:
search_url = f"https://duckduckgo.com/?q=define+{quote(word)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>📖 Definition: {html.escape(word)}</strong><br><br>"
f"<a href='{search_url}'>Get definition on DuckDuckGo</a>"
)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error getting definition: {str(e)}")
async def ddg_calculator(room, bot, expression):
"""Use DuckDuckGo as a calculator."""
try:
search_url = f"https://duckduckgo.com/?q={quote(expression)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🧮 Calculator: {html.escape(expression)}</strong><br><br>"
f"<a href='{search_url}'>Calculate on DuckDuckGo</a>"
)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error with calculator: {str(e)}")
async def ddg_weather(room, bot, location):
"""Get weather information."""
try:
# ---- Weather ----
elif subcommand == "weather":
location = " ".join(args[1:]) if len(args) > 1 else ""
if not location:
location = "current location"
await weather(room, bot, location)
search_url = f"https://duckduckgo.com/?q=weather+{quote(location)}"
# ---- Help ----
elif subcommand == "help":
await send_help(room, bot)
# ---- Default: treat as instant answer ----
else:
query = " ".join(args)
await instant_answer(room, bot, query)
# ==============================
# Result functions (all wrapped in <details>)
# ==============================
async def instant_answer(room, bot, query):
"""Top web result wrapped in a collapsible box."""
try:
with DDGS() as ddgs:
results = await _async_search(ddgs.text, query, max_results=1)
except Exception as e:
logger.error(f"DDG instant answer error: {e}")
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🌤️ Weather: {html.escape(location)}</strong><br><br>"
f"<a href='{search_url}'>Get weather on DuckDuckGo</a>"
f"🦆 <strong>DuckDuckGo: {escape(query)}</strong><br><br>Error fetching results. Try again later."
)
return
content = ""
if results:
r = results[0]
title = escape(r.get("title", "Result"))
body = escape(r.get("body", ""))
content = f"💡 <strong>{title}</strong><br>{body[:300]}…<br><a href='{r['href']}'>Read more</a>"
else:
search_url = f"https://duckduckgo.com/?q={escape(query)}"
content = f"No results found.<br>🔍 <a href='{search_url}'>Search on DuckDuckGo</a>"
msg = f"""<details>
<summary>🦆 DuckDuckGo: {escape(query)}</summary>
{content}
</details>"""
await bot.api.send_markdown_message(room.room_id, msg)
async def web_search(room, bot, query):
try:
with DDGS() as ddgs:
results = await _async_search(ddgs.text, query, max_results=5)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error getting weather: {str(e)}")
logger.error(f"DDG web search error: {e}")
await bot.api.send_text_message(room.room_id, f"Error: {e}")
return
if not results:
await bot.api.send_text_message(room.room_id, f"No results for '{query}'.")
return
items = ""
for r in results:
title = escape(r.get("title", "Result"))
body = escape(r.get("body", ""))
items += f"• <a href='{r['href']}'>{title}</a><br> {body[:200]}…<br><br>"
msg = f"""<details>
<summary>🔍 Search: {escape(query)}</summary>
{items}
</details>"""
await bot.api.send_markdown_message(room.room_id, msg)
async def image_search(room, bot, query):
try:
with DDGS() as ddgs:
results = await _async_search(ddgs.images, query, max_results=3)
except Exception as e:
logger.error(f"DDG image error: {e}")
await bot.api.send_text_message(room.room_id, f"Error: {e}")
return
if not results:
await bot.api.send_text_message(room.room_id, f"No images for '{query}'.")
return
items = ""
for img in results:
title = escape(img.get("title", "Image"))
items += f"• <a href='{img['image']}'>{title}</a>"
if img.get("width") and img.get("height"):
items += f" ({img['width']}×{img['height']})"
items += "<br>"
search_url = f"https://duckduckgo.com/?q={escape(query)}&iax=images&ia=images"
items += f"<br>🔍 <a href='{search_url}'>View all images</a>"
msg = f"""<details>
<summary>🖼️ Images: {escape(query)}</summary>
{items}
</details>"""
await bot.api.send_markdown_message(room.room_id, msg)
async def news_search(room, bot, query):
try:
with DDGS() as ddgs:
results = await _async_search(ddgs.news, query, max_results=3)
except Exception as e:
logger.error(f"DDG news error: {e}")
await bot.api.send_text_message(room.room_id, f"Error: {e}")
return
if not results:
await bot.api.send_text_message(room.room_id, f"No news for '{query}'.")
return
items = ""
for n in results:
title = escape(n.get("title", "Article"))
body = escape(n.get("body", ""))
items += f"• <a href='{n['url']}'>{title}</a><br> {body[:200]}…<br><br>"
msg = f"""<details>
<summary>📰 News: {escape(query)}</summary>
{items}
</details>"""
await bot.api.send_markdown_message(room.room_id, msg)
async def video_search(room, bot, query):
try:
with DDGS() as ddgs:
results = await _async_search(ddgs.videos, query, max_results=3)
except Exception as e:
logger.error(f"DDG video error: {e}")
await bot.api.send_text_message(room.room_id, f"Error: {e}")
return
if not results:
await bot.api.send_text_message(room.room_id, f"No videos for '{query}'.")
return
items = ""
for v in results:
title = escape(v.get("title", "Video"))
items += f"• <a href='{v['content']}'>{title}</a><br>"
search_url = f"https://duckduckgo.com/?q={escape(query)}&iar=videos"
items += f"<br>🔍 <a href='{search_url}'>View all videos</a>"
msg = f"""<details>
<summary>🎬 Videos: {escape(query)}</summary>
{items}
</details>"""
await bot.api.send_markdown_message(room.room_id, msg)
async def bang_search(room, bot, bang_query):
search_url = f"https://duckduckgo.com/?q={escape(bang_query)}"
content = f"🔗 <a href='{search_url}'>Search with {escape(bang_query)} on DuckDuckGo</a>"
msg = f"""<details>
<summary>🎯 Bang: {escape(bang_query)}</summary>
{content}
</details>"""
await bot.api.send_markdown_message(room.room_id, msg)
async def definition(room, bot, word):
await instant_answer(room, bot, f"define {word}")
async def calculator(room, bot, expr):
await instant_answer(room, bot, expr)
async def weather(room, bot, location):
await instant_answer(room, bot, f"weather {location}")
# ---------------------------------------------------------------------------
# Plugin Metadata
# Help messages (no details wrapper kept readable)
# ---------------------------------------------------------------------------
async def bang_help(room, bot):
msg = """
<strong>🎯 DuckDuckGo Bangs</strong><br>
Usage: <code>!ddg bang !bang query</code><br><br>
<strong>Popular bangs:</strong><br>
• <code>!w</code> Wikipedia
• <code>!g</code> Google
• <code>!yt</code> YouTube
• <code>!aw</code> ArchWiki
• <code>!gh</code> GitHub
• <code>!so</code> Stack Overflow
• <code>!reddit</code> Reddit
<br>
<a href="https://duckduckgo.com/bangs">Full list here</a>
"""
await bot.api.send_markdown_message(room.room_id, msg)
__version__ = "1.0.0"
async def send_help(room, bot):
help_msg = """
<strong>🦆 DuckDuckGo Commands</strong><br>
<code>!ddg &lt;query&gt;</code> Top result (collapsible)<br>
<code>!ddg search &lt;query&gt;</code> 5 web results<br>
<code>!ddg image &lt;query&gt;</code> 3 images<br>
<code>!ddg news &lt;query&gt;</code> 3 news articles<br>
<code>!ddg video &lt;query&gt;</code> 3 videos<br>
<code>!ddg bang &lt;!bang query&gt;</code> Bang redirect<br>
<code>!ddg define &lt;word&gt;</code> Definition<br>
<code>!ddg calc &lt;expr&gt;</code> Calculator<br>
<code>!ddg weather [city]</code> Weather<br>
<code>!ddg help</code> This help
"""
await bot.api.send_markdown_message(room.room_id, help_msg)
# ---------------------------------------------------------------------------
# Plugin metadata
# ---------------------------------------------------------------------------
__version__ = "2.1.0"
__author__ = "Funguy Bot"
__description__ = "DuckDuckGo search"
__description__ = "DuckDuckGo search collapsible results (ddgs library, no API key)"
__help__ = """
<details>
<summary><strong>!ddg</strong> DuckDuckGo search and instant answers</summary>
<summary><strong>!ddg</strong> DuckDuckGo search (web, images, news, etc.)</summary>
<ul>
<li><code>!ddg &lt;query&gt;</code> Instant answer (default)</li>
<li><code>!ddg search &lt;query&gt;</code> Web search results</li>
<li><code>!ddg instant &lt;query&gt;</code> Detailed instant answer</li>
<li><code>!ddg image &lt;query&gt;</code> Image search</li>
<li><code>!ddg news &lt;query&gt;</code> News search</li>
<li><code>!ddg video &lt;query&gt;</code> Video search</li>
<li><code>!ddg bang &lt;!bang query&gt;</code> Use DuckDuckGo bangs</li>
<li><code>!ddg define &lt;word&gt;</code> Word definition</li>
<li><code>!ddg &lt;query&gt;</code> Top web result snippet (collapsible)</li>
<li><code>!ddg search &lt;query&gt;</code> 5 web results</li>
<li><code>!ddg image &lt;query&gt;</code> 3 images</li>
<li><code>!ddg news &lt;query&gt;</code> 3 news articles</li>
<li><code>!ddg video &lt;query&gt;</code> 3 videos</li>
<li><code>!ddg bang &lt;!bang query&gt;</code> Bang redirect</li>
<li><code>!ddg define &lt;word&gt;</code> Definition</li>
<li><code>!ddg calc &lt;expression&gt;</code> Calculator</li>
<li><code>!ddg weather [location]</code> Weather information</li>
<li><code>!ddg help</code> Show detailed help</li>
<li><code>!ddg weather [location]</code> Weather</li>
</ul>
<p>No API key required.</p>
<p>Uses <code>ddgs</code> library. No API key required.</p>
</details>
"""