diff --git a/config-example.json b/config-example.json index 19a98c2..0b570a4 100644 --- a/config-example.json +++ b/config-example.json @@ -1,43 +1,44 @@ { - "VERSION": "1.9 testing", - "DESCRIPTION": "a minimalist search utility bot for discord, designed by taciturasa.", - "REPO": "https://github.com/taciturasa/searchbot-discord", - "SERVER": "https://discord.gg/4BpReNV", - "TOKEN": "", - "CACHE": false, - "PREFIX": ["search!"], - - "MAINTENANCE": false, - "CASE_INSENSITIVE": true, - "PREFIXLESS_DMS": true, - "MENTION_ASSIST": true, - "CUSTOM_HELP": true, - - "PERMS": 378944, - "BLOCKED": [], - - "BOTLISTS": { - "DBL": "", - "DBOTS": "", - "BOD": "", - "DBLCOM": "", - "BLSPACE": "", - "TABFT_LINK": "", - "DAD": "" - }, - - "HOOKS": { - "INFO_HOOK": "", - "WARN_HOOK": "", - "ERROR_HOOK": "", - "DEBUG_HOOK": "" - }, - - "RETHINK": { - "DB": "", - "USERNAME": "", - "PASSWORD": "", - "HOST": "", - "PORT": null - } - } \ No newline at end of file + "VERSION": "1.9 testing", + "DESCRIPTION": "a minimalist search utility bot for discord, designed by taciturasa.", + "REPO": "https://github.com/taciturasa/searchbot-discord", + "SERVER": "https://discord.gg/4BpReNV", + "TOKEN": "", + "CACHE": false, + "PREFIX": ["search!"], + + "MAINTENANCE": false, + "CASE_INSENSITIVE": true, + "PREFIXLESS_DMS": true, + "MENTION_ASSIST": true, + "CUSTOM_HELP": true, + "REMOVE_MENTIONS": true, + + "PERMS": 378944, + "BLOCKED": [], + + "BOTLISTS": { + "DBL": "", + "DBOTS": "", + "BOD": "", + "DBLCOM": "", + "BLSPACE": "", + "TABFT_LINK": "", + "DAD": "" + }, + + "HOOKS": { + "INFO_HOOK": "", + "WARN_HOOK": "", + "ERROR_HOOK": "", + "DEBUG_HOOK": "" + }, + + "RETHINK": { + "DB": "", + "USERNAME": "", + "PASSWORD": "", + "HOST": "", + "PORT": null + } +} diff --git a/extensions/core.py b/extensions/core.py index 1e293d7..d630e9d 100644 --- a/extensions/core.py +++ b/extensions/core.py @@ -64,9 +64,7 @@ class Core(commands.Cog): "for debug and maintenance purposes. " "These logs are shared with nobody " "other than those who help develop this bot. " - "If you do not agree to this, please remove this bot. " - "You may contact the devs at any " - "option listed in `info` for deletion requests._\n\n" + "If you do not agree to this, please remove this bot._\n\n" "_You may recall this message at any time with `tutorial`._" ) @@ -121,8 +119,6 @@ class Core(commands.Cog): # Footer msg += ( - "_When in doubt, you may email the creator of the codebase at " - "`polyjitter@fastmail.com`._\n" f"_See {prefix}`help` for help, `invite` to add the bot, " "and `stats` for statistics._" ) @@ -316,26 +312,29 @@ Guild count: {len(self.bot.guilds)} guild_msg: str = self._create_tutorial(guild) channel: Optional[discord.TextChannel] = None owner: discord.Member = guild.owner - + # Tutorial Message # Get text channels text_channels = [] for c in guild.channels: if type(c) is discord.TextChannel: text_channels.append(c) - + break + # Sets channel to general if it exists for c in guild.channels: if c.name == 'general': channel = c - + break + # XXX This looks like garbage # Else posts in first open channel if not channel: for c in guild.channels: if c.permissions_for(guild.me).send_messages: channel = c - + break + # Send tutorial message if channel: await channel.send(guild_msg) @@ -346,8 +345,8 @@ Guild count: {len(self.bot.guilds)} "Please give me send message permissions in the channels " "You wish to use me in!_" ) - - await owner.send(guild_msg) + + await guild.owner.send(guild_msg) return # Ends here if there are no good channels to send to # Owner Disclosure @@ -361,14 +360,12 @@ Guild count: {len(self.bot.guilds)} f"I have sent a tutorial message to `{channel.name}` " "describing how I may be used.\n\n" "If you do not wish to have me there, " - "simply kick me from the server. You may contact the devs " - "at any " - "option listed in `info` for deletion requests.\n\n" + "simply kick me from the server.\n\n" "_Thanks for your time!_" ) # Send owner disclosure - await owner.send(owner_msg) + await guild.owner.send(owner_msg) def setup(bot): diff --git a/extensions/models/search_source.py b/extensions/models/search_source.py new file mode 100644 index 0000000..aa12820 --- /dev/null +++ b/extensions/models/search_source.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- + +# search source +# Provides paginator sources for the search cog. + +"""Search Source File""" + +from typing import Callable, List, Tuple, Optional, Any + +import discord +from discord.ext import menus +import html2text +import re + +FetcherArgs = Tuple[Any] +Fetcher = Callable[..., List] + +# Markdown converter +tomd = html2text.HTML2Text() +tomd.ignore_links = True +tomd.ignore_images = True +tomd.ignore_tables = True +tomd.ignore_emphasis = True +tomd.body_width = 0 + + +# TODO Change around value names, make it general +class Result: + """A class that holds the general data for a search result. + + Parameters: + + title (str): Title of the content. + + url (str): The direct link to the content. + + desc (str): The content's description. + + source (Optional[str]): The source site. Defaults to url. + + image (Optional[str]): The content's image. + """ + + def __init__(self, title: str, url: str, + desc: str = "No description provided.", + source: Optional[str] = None, image: Optional[str] = None): + self.url = url + if title in [None, ""]: + self.title = "Unknown" + else: + self.title = title + self.desc = desc + self.source = source + self.image = image + + def __repr__(self): + fmt = f"" + return fmt + + +class NormalSource(menus.AsyncIteratorPageSource): + def __init__(self, query: str, fetcher: FetcherArgs, per_page: int, + header: str = "", footer: str = ""): + self.header = header + self.footer = footer + self.query = query + + super().__init__(self._generate(fetcher), per_page=per_page) + + async def _generate(self, fetcher: Fetcher): + offset = 0 + per_request = 10 + # TODO put the generation in the fetcher itself + # Qwant: image - media, source - url, title - title + while results := await fetcher( + offset, per_request, self.query + ): + results + for r in results: + yield r + offset += per_request + + async def format_page(self, menu, entries): + start = menu.current_page * self.per_page + + # Escapes all nasties for displaying + query_display = discord.utils.escape_mentions(self.query) + query_display = discord.utils.escape_markdown(query_display) + + # Return if no results + try: + entries[0] + except IndexError: + return f"No results found for `{query_display}`." + + # Gets the first entry's stuff + first_title = tomd.handle(entries[0].title).rstrip('\n') + first_url = entries[0].url + if start == 0: + first_desc = tomd.handle(entries[0].desc).rstrip('\n') + first = f"**{first_title}** {first_url}\n{first_desc}\n\n" + else: + first = f"**{first_title}** {first_url}\n" + + # Builds the substring for each of the other results. + other_results: List[str] = [] + + for e in entries[1:5]: + title = tomd.handle(e.title).rstrip('\n') + url = e.url + other_results.append(f"**{title}** {url}") + + other_msg = "\n".join(other_results) + + # Builds message + msg = f"{first}{other_msg}" + msg = re.sub( + r'(https?://(?:www\.)?[-a-zA-Z0-9@:%._+~#=]+\.' + r'[a-zA-Z0-9()]+\b[-a-zA-Z0-9()@:%_+.~#?&/=]*)', + r'<\1>', + msg + ) + + content = ( + f"{self.header}\n\n" + f"Showing results *{start} - {start + 5}* " + f"for `{self.query}`.\n\n" + f"{msg}\n\n" + f"{self.footer}" + ) + + return content + + +class ImageSource(menus.AsyncIteratorPageSource): + def __init__(self, query: str, fetcher: FetcherArgs, args: FetcherArgs, + header: str = "", footer: str = ""): + self.header = header + self.footer = footer + self.query = query + super().__init__(self._generate(fetcher, args), per_page=1) + + async def _generate(self, fetcher: Fetcher, fetch_args: FetcherArgs): + offset = 0 + per_request = 10 + # TODO put the generation in the fetcher itself + # Qwant: image - media, source - url, title - title + while results := await fetcher( + offset, per_request, self.query, *fetch_args + ): + results + for r in results: + yield r + offset += per_request + + async def format_page(self, menu, entry): + start = menu.current_page * self.per_page + + content = ( + f"{self.header}\n\n" + f"Showing image result `{start}` for `{self.query}``.\n\n" + f"<{entry.image}>" + f"{self.footer}\n\n" + ) + + embed = discord.Embed( + title=entry.title, + url=entry.image, + description=entry.source + ) + embed.set_image(url=entry.image) + + return { + "content": content, + "embed": embed + } diff --git a/main.py b/main.py index ac920b7..82e7434 100644 --- a/main.py +++ b/main.py @@ -53,6 +53,10 @@ class Bot(commands.Bot): self.custom_help: bool = self.config['CUSTOM_HELP'] self.mention_assist: bool = self.config['MENTION_ASSIST'] self.prefixless_dms: bool = self.config['PREFIXLESS_DMS'] + if self.config['REMOVE_MENTIONS']: + self.allowed_mentions = discord.AllowedMentions.none() + else: + self.allowed_mentions = discord.AllowedMentions.all() # RethinkDB if self.config['RETHINK']['DB']: @@ -306,4 +310,4 @@ async def on_command_error(ctx, error): # NOTE Bot Entry Point # Starts the bot print("Connecting...\n") -bot.run(bot.config['TOKEN']) +bot.run(bot.config['TOKEN']) \ No newline at end of file