""" This plugin provides DuckDuckGo search functionality using the DuckDuckGo Instant Answer API. """ import logging import requests import json import simplematrixbotlib as botlib from urllib.parse import quote, urlencode import html DDG_API_URL = "https://api.duckduckgo.com/" DDG_SEARCH_URL = "https://html.duckduckgo.com/html/" 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") args = match.args() if len(args) < 1: await show_usage(room, bot) return subcommand = args[0].lower() if subcommand == "search": if len(args) < 2: await bot.api.send_text_message(room.room_id, "Usage: !ddg search ") 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 ") 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 ") 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 ") 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 ") 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 ") 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 ") 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 = """ 🦆 DuckDuckGo Search Commands !ddg <query> - Instant answer search (default) !ddg search <query> - Web search with results !ddg instant <query> - Instant answer with detailed info !ddg image <query> - Image search !ddg news <query> - News search !ddg video <query> - Video search !ddg bang <!bang query> - Use DuckDuckGo bangs !ddg define <word> - Word definitions !ddg calc <expression> - Calculator !ddg weather [location] - Weather information !ddg help - Show this help Examples:!ddg python programming!ddg search matrix protocol!ddg image cute cats!ddg bang !w matrix!ddg define serendipity!ddg calc 2+2*5!ddg weather London Popular Bangs:!w - Wikipedia • !g - Google • !yt - YouTube • !aw - ArchWiki • !gh - GitHub • !so - 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 = """ 🦆 DuckDuckGo Bangs Usage: !ddg bang <!bang query> Popular Bangs:!ddg bang !w matrix - Search Wikipedia • !ddg bang !g python - Search Google • !ddg bang !yt music - Search YouTube • !ddg bang !aw arch - Search ArchWiki • !ddg bang !gh repository - Search GitHub • !ddg bang !so error - Search Stack Overflow • !ddg bang !amazon book - Search Amazon • !ddg bang !imdb movie - Search IMDb • !ddg bang !reddit topic - Search Reddit • !ddg bang !tw tweet - Search Twitter More Bangs:!ddg - DuckDuckGo • !bing - Bing • !ddgimages - DuckDuckGo Images • !npm - npm packages • !cpp - C++ reference • !python - Python docs • !rust - Rust docs • !mdn - MDN Web Docs Thousands of bangs available! See: https://duckduckgo.com/bangs """ 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"🦆 DuckDuckGo: {html.escape(query)}

" f"API temporarily unavailable. Search on DuckDuckGo" ) return data = response.json() output = f"🦆 DuckDuckGo: {html.escape(query)}

" # Handle different answer types if data.get('AbstractText'): # Wikipedia-style answer output += f"📚 {data.get('Heading', 'Definition')}
" output += f"{html.escape(data['AbstractText'])}
" if data.get('AbstractURL'): output += f"Read more on {data.get('AbstractSource', 'Wikipedia')}
" elif data.get('Answer'): # Direct answer output += f"💡 Answer
" output += f"{html.escape(data['Answer'])}
" elif data.get('Definition'): # Definition output += f"📖 Definition
" output += f"{html.escape(data['Definition'])}
" if data.get('DefinitionSource'): output += f"Source: {data['DefinitionSource']}
" elif data.get('Results'): # List of results output += f"🔍 Results
" for result in data['Results'][:3]: output += f"• {html.escape(result.get('Text', 'Result'))}
" elif data.get('RelatedTopics'): # Related topics output += f"🔗 Related Topics
" for topic in data['RelatedTopics'][:3]: if isinstance(topic, dict) and topic.get('FirstURL'): output += f"• {html.escape(topic.get('Text', 'Topic'))}
" elif isinstance(topic, dict) and topic.get('Name'): output += f"• {html.escape(topic['Name'])}
" else: # No instant answer found, show search results output += "🔍 No instant answer found.
" # Add search link search_url = f"https://duckduckgo.com/?q={quote(query)}" output += f"
View all results on DuckDuckGo" 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"🦆 DuckDuckGo: {html.escape(query)}

" f"Error accessing API. Search on DuckDuckGo" ) 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"🔍 DuckDuckGo Search: {html.escape(query)}

" f"API temporarily unavailable. Search on DuckDuckGo" ) return data = response.json() output = f"🔍 DuckDuckGo Search: {html.escape(query)}

" results_shown = 0 # Show instant answer if available if data.get('AbstractText') and results_shown < limit: output += f"💡 {data.get('Heading', 'Instant Answer')}
" abstract = data['AbstractText'][:200] + "..." if len(data['AbstractText']) > 200 else data['AbstractText'] output += f"{html.escape(abstract)}
" if data.get('AbstractURL'): output += f"Read more
" output += "
" results_shown += 1 # Show web results if data.get('Results') and results_shown < limit: output += "🌐 Web Results
" for result in data['Results'][:limit - results_shown]: output += f"• {html.escape(result.get('Text', 'Result'))}
" results_shown += 1 # Show related topics if data.get('RelatedTopics') and results_shown < limit: output += "🔗 Related Topics
" for topic in data['RelatedTopics'][:limit - results_shown]: if isinstance(topic, dict) and topic.get('FirstURL'): output += f"• {html.escape(topic.get('Text', 'Topic'))}
" results_shown += 1 # Add search link search_url = f"https://duckduckgo.com/?q={quote(query)}" output += f"
View all results on DuckDuckGo" 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"🔍 DuckDuckGo Search: {html.escape(query)}

" f"Error accessing API. Search on DuckDuckGo" ) 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"🖼️ DuckDuckGo Images: {html.escape(query)}

" f"API temporarily unavailable. Search images on DuckDuckGo" ) return data = response.json() output = f"🖼️ DuckDuckGo Images: {html.escape(query)}

" if data.get('Results'): output += "📸 Image Results
" for image in data['Results'][:3]: output += f"• {html.escape(image.get('Title', 'Image'))}
" if image.get('Width') and image.get('Height'): output += f" Size: {image['Width']}×{image['Height']}
" else: output += "No image results found.
" # Add search link search_url = f"https://duckduckgo.com/?q={quote(query)}&iax=images&ia=images" output += f"
View all images on DuckDuckGo" 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"🖼️ DuckDuckGo Images: {html.escape(query)}

" f"Error accessing API. Search images on DuckDuckGo" ) 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"📰 DuckDuckGo News: {html.escape(query)}

" f"View news on DuckDuckGo" ) 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"🎬 DuckDuckGo Videos: {html.escape(query)}

" f"View videos on DuckDuckGo" ) 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"🎯 DuckDuckGo Bang: {html.escape(bang)}
" output += f"Description: {description}
" if ' ' in bang_query: output += f"Query: {html.escape(bang_query.split(' ', 1)[1])}

" output += f"Search with {html.escape(bang)} on DuckDuckGo" 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"📖 Definition: {html.escape(word)}

" f"Get definition on DuckDuckGo" ) 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"🧮 Calculator: {html.escape(expression)}

" f"Calculate on DuckDuckGo" ) 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: if not location: location = "current location" search_url = f"https://duckduckgo.com/?q=weather+{quote(location)}" await bot.api.send_markdown_message( room.room_id, f"🌤️ Weather: {html.escape(location)}

" f"Get weather on DuckDuckGo" ) except Exception as e: await bot.api.send_text_message(room.room_id, f"Error getting weather: {str(e)}")