From 982010f804a9a6fecce39516b38d0dbd7893d1b8 Mon Sep 17 00:00:00 2001 From: bobloy Date: Wed, 29 Aug 2018 09:41:08 -0400 Subject: [PATCH 001/127] nudepy bug, waiting for merge --- nudity/info..json | 4 +- nudity/nudity.py | 97 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/nudity/info..json b/nudity/info..json index 4384930..e3d64e4 100644 --- a/nudity/info..json +++ b/nudity/info..json @@ -10,7 +10,9 @@ "description": "Keep track of when users were last seen online", "hidden": true, "install_msg": "Thank you for installing LastSeen. Get started with `[p]help LastSeen`", - "requirements": ["nudepy"], + "requirements": [ + "nudepy" + ], "short": "Last seen tracker", "tags": [ "bobloy", diff --git a/nudity/nudity.py b/nudity/nudity.py index be30ee4..d7c4fe8 100644 --- a/nudity/nudity.py +++ b/nudity/nudity.py @@ -1,8 +1,8 @@ -from datetime import datetime +from io import BytesIO import discord -import nude -from nude import Nude +from PIL import Image +from nude import is_nude from redbot.core import Config from redbot.core import commands from redbot.core.bot import Red @@ -13,16 +13,13 @@ class Nudity: V3 Cog Template """ - online_status = discord.Status.online - - offline_status = discord.Status.offline - def __init__(self, bot: Red): self.bot = bot self.config = Config.get_conf(self, identifier=9811198108111121, force_registration=True) default_guild = { - "enabled": False + "enabled": False, + "channel_id": None } self.config.register_guild(**default_guild) @@ -34,12 +31,92 @@ class Nudity: await self.config.guild(ctx.guild).enabled.set(not is_on) await ctx.send("Nude checking is now set to {}".format(not is_on)) + @commands.command() + async def nsfwchannel(self, ctx: commands.Context, channel: discord.TextChannel = None): + if channel is None: + await self.config.guild(ctx.guild).channel_id.set(None) + await ctx.send("NSFW Channel cleared") + else: + if not channel.is_nsfw(): + await ctx.send("This channel isn't NSFW!") + return + else: + await self.config.guild(ctx.guild).channel_id.set(channel.id) + await ctx.send("NSFW channel has been set to {}".format(channel.mention)) + + async def get_nsfw_channel(self, guild: discord.Guild): + channel_id = self.config.guild(guild).channel_id() + + if channel_id is None: + return None + else: + return await guild.get_channel(channel_id=channel_id) + + async def nsfw(self, message: discord.Message, image: BytesIO): + content = message.content + guild: discord.Guild = message.guild + if not content: + content = "*`None`*" + try: + await message.delete() + except discord.Forbidden: + await message.channel.send("NSFW Image detected!") + return + + embed = discord.Embed(title="NSFW Image Detected") + embed.add_field(name="Original Message", value=content) + + await message.channel.send(embed=embed) + + nsfw_channel = await self.get_nsfw_channel(guild) + + if nsfw_channel is None: + return + else: + await nsfw_channel.send("NSFW Image from {}".format(message.channel.mention), file=image) + async def on_message(self, message: discord.Message): - is_on = await self.config.guild(message.guild).enabled() + if not message.attachments: + return + + if message.guild is None: + return + + try: + is_on = await self.config.guild(message.guild).enabled() + except AttributeError: + return + if not is_on: return - if not message.attachments: + channel: discord.TextChannel = message.channel + + if channel.is_nsfw(): return + attachment = message.attachments[0] + + # async with aiohttp.ClientSession() as session: + # img = await fetch_img(session, attachment.url) + + temp = BytesIO() + print("Pre attachment save") + await attachment.save(temp) + print("Pre Image open") + temp = Image.open(temp) + + print("Pre nude check") + if is_nude(temp): + print("Is nude") + await message.add_reaction("❌") + await self.nsfw(message, temp) + else: + print("Is not nude") + await message.add_reaction("✅") +# async def fetch_img(session, url): +# with aiohttp.Timeout(10): +# async with session.get(url) as response: +# assert response.status == 200 +# return await response.read() From 9a3a62f451c379aa2350d81353a5ca230b477c87 Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 9 Oct 2018 10:08:37 -0400 Subject: [PATCH 002/127] updates --- nudity/__init__.py | 3 ++- nudity/info..json | 2 +- nudity/nudity.py | 12 ++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/nudity/__init__.py b/nudity/__init__.py index 7d32df6..09d9dbf 100644 --- a/nudity/__init__.py +++ b/nudity/__init__.py @@ -2,4 +2,5 @@ from .nudity import Nudity def setup(bot): - bot.add_cog(Nudity(bot)) + n = Nudity(bot) + bot.add_cog(n) diff --git a/nudity/info..json b/nudity/info..json index d5d6e2d..1d66cf7 100644 --- a/nudity/info..json +++ b/nudity/info..json @@ -17,4 +17,4 @@ "utils", "tools" ] -} \ No newline at end of file +} diff --git a/nudity/nudity.py b/nudity/nudity.py index d7c4fe8..a7290a5 100644 --- a/nudity/nudity.py +++ b/nudity/nudity.py @@ -17,14 +17,11 @@ class Nudity: self.bot = bot self.config = Config.get_conf(self, identifier=9811198108111121, force_registration=True) - default_guild = { - "enabled": False, - "channel_id": None - } + default_guild = {"enabled": False, "channel_id": None} self.config.register_guild(**default_guild) - @commands.command(aliases=['togglenudity'], name='nudity') + @commands.command(aliases=["togglenudity"], name="nudity") async def nudity(self, ctx: commands.Context): """Toggle nude-checking on or off""" is_on = await self.config.guild(ctx.guild).enabled() @@ -73,7 +70,9 @@ class Nudity: if nsfw_channel is None: return else: - await nsfw_channel.send("NSFW Image from {}".format(message.channel.mention), file=image) + await nsfw_channel.send( + "NSFW Image from {}".format(message.channel.mention), file=image + ) async def on_message(self, message: discord.Message): if not message.attachments: @@ -115,6 +114,7 @@ class Nudity: print("Is not nude") await message.add_reaction("✅") + # async def fetch_img(session, url): # with aiohttp.Timeout(10): # async with session.get(url) as response: From 41b2ce7391e021e19d96045e913cb42a8cb1ebbf Mon Sep 17 00:00:00 2001 From: bobloy Date: Wed, 29 Apr 2020 11:47:39 -0400 Subject: [PATCH 003/127] Event seeds happen way too frequently (#83) --- planttycoon/planttycoon.py | 122 +++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 46 deletions(-) diff --git a/planttycoon/planttycoon.py b/planttycoon/planttycoon.py index f88fd6f..1ab7eba 100644 --- a/planttycoon/planttycoon.py +++ b/planttycoon/planttycoon.py @@ -1,5 +1,6 @@ import asyncio import collections +import copy import datetime import json import time @@ -29,7 +30,9 @@ class Gardener: "Badges: {}\n" "Points: {}\n" "Products: {}\n" - "Current: {}".format(self.user, self.badges, self.points, self.products, self.current) + "Current: {}".format( + self.user, self.badges, self.points, self.products, self.current + ) ) def __repr__(self): @@ -117,7 +120,8 @@ async def _withdraw_points(gardener: Gardener, amount): class PlantTycoon(commands.Cog): """Grow your own plants! Be sure to take proper care of it.""" - def __init__(self, bot: Red): + def __init__(self, bot: Red, *args, **kwargs): + super().__init__(*args, **kwargs) self.bot = bot self.config = Config.get_conf(self, identifier=80108971101168412199111111110) @@ -184,12 +188,35 @@ class PlantTycoon(commands.Cog): with plant_path.open() as json_data: self.plants = json.load(json_data) + await self._load_event_seeds() + with product_path.open() as json_data: self.products = json.load(json_data) for product in self.products: print("PlantTycoon: Loaded {}".format(product)) + async def _load_event_seeds(self): + self.plants["all_plants"] = copy.deepcopy(self.plants["plants"]) + plant_options = self.plants["all_plants"] + + d = datetime.date.today() + month = d.month + if month == 1: + plant_options.append(self.plants["event"]["January"]) + elif month == 2: + plant_options.append(self.plants["event"]["February"]) + elif month == 3: + plant_options.append(self.plants["event"]["March"]) + elif month == 4: + plant_options.append(self.plants["event"]["April"]) + elif month == 10: + plant_options.append(self.plants["event"]["October"]) + elif month == 11: + plant_options.append(self.plants["event"]["November"]) + elif month == 12: + plant_options.append(self.plants["event"]["December"]) + async def _gardener(self, user: discord.User) -> Gardener: # @@ -219,12 +246,17 @@ class PlantTycoon(commands.Cog): degradation = ( 100 / (gardener.current["time"] / 60) - * (self.defaults["degradation"]["base_degradation"] + gardener.current["degradation"]) + * ( + self.defaults["degradation"]["base_degradation"] + + gardener.current["degradation"] + ) ) + modifiers d = collections.namedtuple("degradation", "degradation time modifiers") - return d(degradation=degradation, time=gardener.current["time"], modifiers=modifiers) + return d( + degradation=degradation, time=gardener.current["time"], modifiers=modifiers + ) # async def _get_member(self, user_id): # @@ -253,7 +285,10 @@ class PlantTycoon(commands.Cog): await self._load_plants_products() product = product.lower() product_category = product_category.lower() - if product in self.products and self.products[product]["category"] == product_category: + if ( + product in self.products + and self.products[product]["category"] == product_category + ): if product in gardener.products: if gardener.products[product] > 0: gardener.current["health"] += self.products[product]["health"] @@ -324,9 +359,13 @@ class PlantTycoon(commands.Cog): ``{0}prune``: Prune your plant.\n""" em = discord.Embed( - title=title, description=description.format(prefix), color=discord.Color.green() + title=title, + description=description.format(prefix), + color=discord.Color.green(), + ) + em.set_thumbnail( + url="https://image.prntscr.com/image/AW7GuFIBSeyEgkR2W3SeiQ.png" ) - em.set_thumbnail(url="https://image.prntscr.com/image/AW7GuFIBSeyEgkR2W3SeiQ.png") em.set_footer( text="This cog was made by SnappyDragon18 and PaddoInWonderland. Inspired by The Lorax (2012)." ) @@ -349,32 +388,7 @@ class PlantTycoon(commands.Cog): gardener = await self._gardener(author) if not gardener.current: - d = datetime.date.today() - month = d.month - - # - # Event Plant Check start - # - plant_options = self.plants["plants"] - - if month == 1: - plant_options.append(self.plants["event"]["January"]) - elif month == 2: - plant_options.append(self.plants["event"]["February"]) - elif month == 3: - plant_options.append(self.plants["event"]["March"]) - elif month == 4: - plant_options.append(self.plants["event"]["April"]) - elif month == 10: - plant_options.append(self.plants["event"]["October"]) - elif month == 11: - plant_options.append(self.plants["event"]["November"]) - elif month == 12: - plant_options.append(self.plants["event"]["December"]) - - # - # Event Plant Check end - # + plant_options = self.plants["all_plants"] plant = choice(plant_options) plant["timestamp"] = int(time.time()) @@ -425,7 +439,9 @@ class PlantTycoon(commands.Cog): em = discord.Embed(color=discord.Color.green()) # , description='\a\n') avatar = author.avatar_url if author.avatar else author.default_avatar_url - em.set_author(name="Gardening profile of {}".format(author.name), icon_url=avatar) + em.set_author( + name="Gardening profile of {}".format(author.name), icon_url=avatar + ) em.add_field(name="**Thneeds**", value=str(gardener.points)) if not gardener.current: em.add_field(name="**Currently growing**", value="None") @@ -485,14 +501,16 @@ class PlantTycoon(commands.Cog): tick = "" tock = "" tick_tock = 0 - for plant in self.plants["plants"]: + for plant in self.plants["all_plants"]: if tick_tock == 0: tick += "**{}**\n".format(plant["name"]) tick_tock = 1 else: tock += "**{}**\n".format(plant["name"]) tick_tock = 0 - em = discord.Embed(title="All plants that are growable", color=discord.Color.green()) + em = discord.Embed( + title="All plants that are growable", color=discord.Color.green() + ) em.add_field(name="\a", value=tick) em.add_field(name="\a", value=tock) await ctx.send(embed=em) @@ -506,7 +524,7 @@ class PlantTycoon(commands.Cog): await self._load_plants_products() t = False plant = None - for p in self.plants["plants"]: + for p in self.plants["all_plants"]: if p["name"].lower() == plantname.lower().strip('"'): plant = p t = True @@ -514,13 +532,18 @@ class PlantTycoon(commands.Cog): if t: em = discord.Embed( - title="Plant statistics of {}".format(plant["name"]), color=discord.Color.green() + title="Plant statistics of {}".format(plant["name"]), + color=discord.Color.green(), ) em.set_thumbnail(url=plant["image"]) em.add_field(name="**Name**", value=plant["name"]) em.add_field(name="**Rarity**", value=plant["rarity"].capitalize()) - em.add_field(name="**Grow Time**", value="{0:.1f} minutes".format(plant["time"] / 60)) - em.add_field(name="**Damage Threshold**", value="{}%".format(plant["threshold"])) + em.add_field( + name="**Grow Time**", value="{0:.1f} minutes".format(plant["time"] / 60) + ) + em.add_field( + name="**Damage Threshold**", value="{}%".format(plant["threshold"]) + ) em.add_field(name="**Badge**", value=plant["badge"]) em.add_field(name="**Reward**", value="{} τ".format(plant["reward"])) else: @@ -572,7 +595,8 @@ class PlantTycoon(commands.Cog): author = ctx.author if product is None: em = discord.Embed( - title="All gardening supplies that you can buy:", color=discord.Color.green() + title="All gardening supplies that you can buy:", + color=discord.Color.green(), ) for pd in self.products: em.add_field( @@ -606,7 +630,8 @@ class PlantTycoon(commands.Cog): message = "You bought {}.".format(product.lower()) else: message = "You don't have enough Thneeds. You have {}, but need {}.".format( - gardener.points, self.products[product.lower()]["cost"] * amount + gardener.points, + self.products[product.lower()]["cost"] * amount, ) else: message = "I don't have this product." @@ -625,11 +650,14 @@ class PlantTycoon(commands.Cog): plural = "s" if withdraw_points: await bank.deposit_credits(author, amount) - message = "{} Thneed{} successfully exchanged for credits.".format(amount, plural) + message = "{} Thneed{} successfully exchanged for credits.".format( + amount, plural + ) await gardener.save_gardener() else: - message = "You don't have enough Thneed{}. " "You have {}, but need {}.".format( - plural, gardener.points, amount + message = ( + "You don't have enough Thneed{}. " + "You have {}, but need {}.".format(plural, gardener.points, amount) ) em = discord.Embed(description=message, color=discord.Color.green()) @@ -721,7 +749,9 @@ class PlantTycoon(commands.Cog): degradation = await self._degradation(gardener) now = int(time.time()) timestamp = gardener.current["timestamp"] - degradation_count = (now - timestamp) // (self.defaults["timers"]["degradation"] * 60) + degradation_count = (now - timestamp) // ( + self.defaults["timers"]["degradation"] * 60 + ) degradation_count -= gardener.current["degrade_count"] gardener.current["health"] -= degradation.degradation * degradation_count gardener.points += self.defaults["points"]["growing"] * degradation_count From a4a787830af89e74ad615486dfea56ea99c1bfab Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 30 Apr 2020 14:23:33 -0400 Subject: [PATCH 004/127] Fix leaver get_embed_color issue (#84) * Fix embed color * Gotta await it --- leaver/leaver.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/leaver/leaver.py b/leaver/leaver.py index 13ff0e0..83e8f1f 100644 --- a/leaver/leaver.py +++ b/leaver/leaver.py @@ -15,7 +15,9 @@ class Leaver(Cog): def __init__(self, bot: Red): self.bot = bot - self.config = Config.get_conf(self, identifier=9811198108111121, force_registration=True) + self.config = Config.get_conf( + self, identifier=9811198108111121, force_registration=True + ) default_guild = {"channel": ""} self.config.register_guild(**default_guild) @@ -41,9 +43,15 @@ class Leaver(Cog): if channel != "": channel = guild.get_channel(channel) - out = "{}{} has left the server".format(member, member.nick if member.nick is not None else "") + out = "{}{} has left the server".format( + member, member.nick if member.nick is not None else "" + ) if await self.bot.embed_requested(channel, member): - await channel.send(embed=discord.Embed(description=out, color=self.bot.color)) + await channel.send( + embed=discord.Embed( + description=out, color=(await self.bot.get_embed_color(channel)) + ) + ) else: await channel.send(out) else: From 9ef5836fa80138b8cd71229ac6c484bf3570ec8a Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 30 Apr 2020 21:02:45 -0400 Subject: [PATCH 005/127] WIP fix to aiohttp payload error --- lovecalculator/lovecalculator.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lovecalculator/lovecalculator.py b/lovecalculator/lovecalculator.py index 3a83cab..2038ccc 100644 --- a/lovecalculator/lovecalculator.py +++ b/lovecalculator/lovecalculator.py @@ -22,18 +22,19 @@ class LoveCalculator(Cog): x = lover.display_name y = loved.display_name - url = "https://www.lovecalculator.com/love.php?name1={}&name2={}".format( - x.replace(" ", "+"), y.replace(" ", "+") - ) + url = f"https://www.lovecalculator.com/love.php?name1={x}&name2={y}" + async with aiohttp.ClientSession() as session: async with session.get(url) as response: - soup_object = BeautifulSoup(await response.text(), "html.parser") - try: - description = ( - soup_object.find("div", attrs={"class": "result__score"}).get_text().strip() - ) - except: - description = "Dr. Love is busy right now" + resp = await response.text() + + soup_object = BeautifulSoup(resp, "html.parser") + try: + description = soup_object.find("div", attrs={"class": "result__score"}).get_text().strip() + img = soup_object.find("img", attrs={"class": "result__image"})['src'] + except: + description = "Dr. Love is busy right now" + img = None try: z = description[:2] @@ -48,5 +49,5 @@ class LoveCalculator(Cog): title = "Dr. Love has left a note for you." description = emoji + " " + description + " " + emoji - em = discord.Embed(title=title, description=description, color=discord.Color.red()) + em = discord.Embed(title=title, description=description, color=discord.Color.red(), url=img) await ctx.send(embed=em) From 063f18121983cb76d7fa740904531870a1bd6cda Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 30 Apr 2020 21:17:17 -0400 Subject: [PATCH 006/127] conf to config and deprecated yaml load (#85) --- audiotrivia/audiotrivia.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/audiotrivia/audiotrivia.py b/audiotrivia/audiotrivia.py index b5bd1cc..394e1d1 100644 --- a/audiotrivia/audiotrivia.py +++ b/audiotrivia/audiotrivia.py @@ -153,7 +153,8 @@ class AudioTrivia(Trivia): "The trivia list was parsed successfully, however it appears to be empty!" ) return - settings = await self.conf.guild(ctx.guild).all() + + settings = await self.config.guild(ctx.guild).all() audiosettings = await self.audioconf.guild(ctx.guild).all() config = trivia_dict.pop("CONFIG", None) if config and settings["allow_override"]: @@ -201,7 +202,7 @@ class AudioTrivia(Trivia): with path.open(encoding="utf-8") as file: try: - dict_ = yaml.load(file) + dict_ = yaml.load(file, Loader=yaml.SafeLoader) except yaml.error.YAMLError as exc: raise InvalidListError("YAML parsing failed.") from exc else: From 6a23a9a6e143bb48af6e80b3fa6fbbc468ca0feb Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 28 May 2020 09:38:08 -0400 Subject: [PATCH 007/127] Audiotrivia develop (#87) * conf to config and deprecated yaml load * Add hockey goal horns --- audiotrivia/data/lists/hockeygoalhorns.yaml | 125 ++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 audiotrivia/data/lists/hockeygoalhorns.yaml diff --git a/audiotrivia/data/lists/hockeygoalhorns.yaml b/audiotrivia/data/lists/hockeygoalhorns.yaml new file mode 100644 index 0000000..dd825e8 --- /dev/null +++ b/audiotrivia/data/lists/hockeygoalhorns.yaml @@ -0,0 +1,125 @@ +AUTHOR: Lazar +https://www.youtube.com/watch?v=6OejNXrGkK0&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq: +- Anaheim +- Ducks +- Anaheim Ducks +https://www.youtube.com/watch?v=RbUxSPoU9Yg&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=2: +- Arizona +- Coyotes +- Arizona Coyotes +https://www.youtube.com/watch?v=DsI0PgWADks&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=4: +- Boston +- Bruins +- Boston Bruins +https://www.youtube.com/watch?v=hjFTd3MJOHc&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=5: +- Buffalo +- Sabres +- Buffalo Sabres +https://www.youtube.com/watch?v=sn1PliBCRDY&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=6: +- Calgary +- Flames +- Calgary Flames +https://www.youtube.com/watch?v=3exZm6Frd18&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=7: +- Carolina +- Hurricanes +- Carolina Hurricanes +https://www.youtube.com/watch?v=sBeXPMkqR80&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=8: +- Chicago +- Blackhawks +- Chicago Blackhawks +https://www.youtube.com/watch?v=MARxzs_vCPI&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=9: +- Colorado +- Avalanche +- Colorado Avalanche +https://www.youtube.com/watch?v=6yYbQfOWw4k&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=10: +- Columbus +- Blue Jackets +- Columbus Blue Jackets +https://www.youtube.com/watch?v=Af8_9NP5lyw&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=11: +- Dallas +- Stars +- Dallas Stars +https://www.youtube.com/watch?v=JflfvLvY7ks&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=13: +- Detroit +- Red wings +- Detroit Red Wings +https://www.youtube.com/watch?v=xc422k5Tcqc&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=15: +- Edmonton +- Oilers +- Edmonton Oilers +https://www.youtube.com/watch?v=Dm1bjUB9HLE&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=16: +- Florida +- Panthers +- Florida Panthers +https://www.youtube.com/watch?v=jSgd3aIepY4&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=17: +- Los Angeles +- Kings +- Los Angeles Kings +https://www.youtube.com/watch?v=4Pj8hWPR9VI&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=18: +- Minnesota +- Wild +- Minnesota Wild +https://www.youtube.com/watch?v=rRGlUFWEBMk&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=19: +- Montreal +- Canadiens +- Montreal Canadiens +https://www.youtube.com/watch?v=fHTehdlMwWQ&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=20: +- Nashville +- Predators +- Nashville Predators +https://www.youtube.com/watch?v=4q0eNg-AbrQ&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=21: +- New Jersey +- Devils +- New Jersey Devils +https://www.youtube.com/watch?v=ZC514zGrL80&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=23: +- New York +- Islanders +- New York Islanders +https://www.youtube.com/watch?v=Zzfks2A2n38&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=24: +- New York +- Rangers +- New York Rangers +https://www.youtube.com/watch?v=fHlWxPRNVBc&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=25: +- Ottawa +- Senators +- Ottawa Senators +https://www.youtube.com/watch?v=0LsXpMiVD1E&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=26: +- Philadelphia +- Flyers +- Philadelphia Flyers +https://www.youtube.com/watch?v=Llw3adcNuzI&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=27: +- Pittsburgh +- Penguins +- Pittsburgh Penguins +https://www.youtube.com/watch?v=NZqSBkmpbLw&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=28: +- San Jose +- Sharks +- San Jose Sharks +https://www.youtube.com/watch?v=Q23TDOJsY1s&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=29: +- St. Louis +- Blues +- St. Louis Blues +https://www.youtube.com/watch?v=bdhDXxM20iM&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=30: +- Tampa Bay +- Lightning +- Tampa Bay Lightning +https://www.youtube.com/watch?v=2cyekaemZgs&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=31: +- Toronto +- Maple Leafs +- Toronto Maple Leafs +https://www.youtube.com/watch?v=CPozN-ZHpAo&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=32: +- Vancouver +- Canucks +- Vancouver Canucks +https://www.youtube.com/watch?v=zheGI316WXg&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=33: +- Vegas +- Golden Knights +- Vegas Golden Knights +https://www.youtube.com/watch?v=BH_CC1RxtfU&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=34: +- Washington +- Capitals +- Washington Capitals +https://www.youtube.com/watch?v=3gcahU_i9WE&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=35: +- Winnipeg +- Jets +- Winnipeg Jets From 0bb967ab2254e1915be056e03152dfec63aeea9a Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 28 May 2020 22:42:06 -0400 Subject: [PATCH 008/127] update to 3.3.8 --- audiotrivia/audiotrivia.py | 8 +- audiotrivia/data/lists/hockeygoalhorns.yaml | 125 -------------------- audiotrivia/data/lists/nhlgoalhorns.yaml | 125 ++++++++++++++++++++ 3 files changed, 130 insertions(+), 128 deletions(-) delete mode 100644 audiotrivia/data/lists/hockeygoalhorns.yaml create mode 100644 audiotrivia/data/lists/nhlgoalhorns.yaml diff --git a/audiotrivia/audiotrivia.py b/audiotrivia/audiotrivia.py index 394e1d1..586f5b7 100644 --- a/audiotrivia/audiotrivia.py +++ b/audiotrivia/audiotrivia.py @@ -5,13 +5,15 @@ from typing import List import lavalink import yaml from redbot.cogs.audio import Audio +from redbot.cogs.audio.core.utilities import validation from redbot.cogs.trivia import LOG from redbot.cogs.trivia.trivia import InvalidListError, Trivia from redbot.core import commands, Config, checks from redbot.core.bot import Red from redbot.core.data_manager import cog_data_path from redbot.core.utils.chat_formatting import box -from redbot.cogs.audio.utils import userlimit +# from redbot.cogs.audio.utils import userlimit + from .audiosession import AudioSession @@ -106,7 +108,7 @@ class AudioTrivia(Trivia): try: if not ctx.author.voice.channel.permissions_for( ctx.me - ).connect or userlimit(ctx.author.voice.channel): + ).connect or self.audio.is_vc_full(ctx.author.voice.channel): return await ctx.send("I don't have permission to connect to your channel.") await lavalink.connect(ctx.author.voice.channel) lavaplayer = lavalink.get_player(ctx.guild.id) @@ -117,7 +119,7 @@ class AudioTrivia(Trivia): lavaplayer = lavalink.get_player(ctx.guild.id) lavaplayer.store("channel", ctx.channel.id) # What's this for? I dunno - await self.audio._data_check(ctx) + await self.audio.set_player_settings(ctx) if not ctx.author.voice or ctx.author.voice.channel != lavaplayer.channel: return await ctx.send( diff --git a/audiotrivia/data/lists/hockeygoalhorns.yaml b/audiotrivia/data/lists/hockeygoalhorns.yaml deleted file mode 100644 index dd825e8..0000000 --- a/audiotrivia/data/lists/hockeygoalhorns.yaml +++ /dev/null @@ -1,125 +0,0 @@ -AUTHOR: Lazar -https://www.youtube.com/watch?v=6OejNXrGkK0&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq: -- Anaheim -- Ducks -- Anaheim Ducks -https://www.youtube.com/watch?v=RbUxSPoU9Yg&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=2: -- Arizona -- Coyotes -- Arizona Coyotes -https://www.youtube.com/watch?v=DsI0PgWADks&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=4: -- Boston -- Bruins -- Boston Bruins -https://www.youtube.com/watch?v=hjFTd3MJOHc&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=5: -- Buffalo -- Sabres -- Buffalo Sabres -https://www.youtube.com/watch?v=sn1PliBCRDY&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=6: -- Calgary -- Flames -- Calgary Flames -https://www.youtube.com/watch?v=3exZm6Frd18&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=7: -- Carolina -- Hurricanes -- Carolina Hurricanes -https://www.youtube.com/watch?v=sBeXPMkqR80&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=8: -- Chicago -- Blackhawks -- Chicago Blackhawks -https://www.youtube.com/watch?v=MARxzs_vCPI&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=9: -- Colorado -- Avalanche -- Colorado Avalanche -https://www.youtube.com/watch?v=6yYbQfOWw4k&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=10: -- Columbus -- Blue Jackets -- Columbus Blue Jackets -https://www.youtube.com/watch?v=Af8_9NP5lyw&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=11: -- Dallas -- Stars -- Dallas Stars -https://www.youtube.com/watch?v=JflfvLvY7ks&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=13: -- Detroit -- Red wings -- Detroit Red Wings -https://www.youtube.com/watch?v=xc422k5Tcqc&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=15: -- Edmonton -- Oilers -- Edmonton Oilers -https://www.youtube.com/watch?v=Dm1bjUB9HLE&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=16: -- Florida -- Panthers -- Florida Panthers -https://www.youtube.com/watch?v=jSgd3aIepY4&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=17: -- Los Angeles -- Kings -- Los Angeles Kings -https://www.youtube.com/watch?v=4Pj8hWPR9VI&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=18: -- Minnesota -- Wild -- Minnesota Wild -https://www.youtube.com/watch?v=rRGlUFWEBMk&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=19: -- Montreal -- Canadiens -- Montreal Canadiens -https://www.youtube.com/watch?v=fHTehdlMwWQ&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=20: -- Nashville -- Predators -- Nashville Predators -https://www.youtube.com/watch?v=4q0eNg-AbrQ&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=21: -- New Jersey -- Devils -- New Jersey Devils -https://www.youtube.com/watch?v=ZC514zGrL80&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=23: -- New York -- Islanders -- New York Islanders -https://www.youtube.com/watch?v=Zzfks2A2n38&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=24: -- New York -- Rangers -- New York Rangers -https://www.youtube.com/watch?v=fHlWxPRNVBc&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=25: -- Ottawa -- Senators -- Ottawa Senators -https://www.youtube.com/watch?v=0LsXpMiVD1E&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=26: -- Philadelphia -- Flyers -- Philadelphia Flyers -https://www.youtube.com/watch?v=Llw3adcNuzI&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=27: -- Pittsburgh -- Penguins -- Pittsburgh Penguins -https://www.youtube.com/watch?v=NZqSBkmpbLw&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=28: -- San Jose -- Sharks -- San Jose Sharks -https://www.youtube.com/watch?v=Q23TDOJsY1s&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=29: -- St. Louis -- Blues -- St. Louis Blues -https://www.youtube.com/watch?v=bdhDXxM20iM&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=30: -- Tampa Bay -- Lightning -- Tampa Bay Lightning -https://www.youtube.com/watch?v=2cyekaemZgs&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=31: -- Toronto -- Maple Leafs -- Toronto Maple Leafs -https://www.youtube.com/watch?v=CPozN-ZHpAo&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=32: -- Vancouver -- Canucks -- Vancouver Canucks -https://www.youtube.com/watch?v=zheGI316WXg&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=33: -- Vegas -- Golden Knights -- Vegas Golden Knights -https://www.youtube.com/watch?v=BH_CC1RxtfU&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=34: -- Washington -- Capitals -- Washington Capitals -https://www.youtube.com/watch?v=3gcahU_i9WE&list=PLEfKMmYWy5LTPwexaHOaPeYBne8j7udRq&index=35: -- Winnipeg -- Jets -- Winnipeg Jets diff --git a/audiotrivia/data/lists/nhlgoalhorns.yaml b/audiotrivia/data/lists/nhlgoalhorns.yaml new file mode 100644 index 0000000..4dd28ac --- /dev/null +++ b/audiotrivia/data/lists/nhlgoalhorns.yaml @@ -0,0 +1,125 @@ +AUTHOR: Lazar +https://youtu.be/6OejNXrGkK0: +- Anaheim +- Ducks +- Anaheim Ducks +https://youtu.be/RbUxSPoU9Yg: +- Arizona +- Coyotes +- Arizona Coyotes +https://youtu.be/DsI0PgWADks: +- Boston +- Bruins +- Boston Bruins +https://youtu.be/hjFTd3MJOHc: +- Buffalo +- Sabres +- Buffalo Sabres +https://youtu.be/sn1PliBCRDY: +- Calgary +- Flames +- Calgary Flames +https://youtu.be/3exZm6Frd18: +- Carolina +- Hurricanes +- Carolina Hurricanes +https://youtu.be/sBeXPMkqR80: +- Chicago +- Blackhawks +- Chicago Blackhawks +https://youtu.be/MARxzs_vCPI: +- Colorado +- Avalanche +- Colorado Avalanche +https://youtu.be/6yYbQfOWw4k: +- Columbus +- Blue Jackets +- Columbus Blue Jackets +https://youtu.be/Af8_9NP5lyw: +- Dallas +- Stars +- Dallas Stars +https://youtu.be/JflfvLvY7ks: +- Detroit +- Red wings +- Detroit Red Wings +https://youtu.be/xc422k5Tcqc: +- Edmonton +- Oilers +- Edmonton Oilers +https://youtu.be/Dm1bjUB9HLE: +- Florida +- Panthers +- Florida Panthers +https://youtu.be/jSgd3aIepY4: +- Los Angeles +- Kings +- Los Angeles Kings +https://youtu.be/4Pj8hWPR9VI: +- Minnesota +- Wild +- Minnesota Wild +https://youtu.be/rRGlUFWEBMk: +- Montreal +- Canadiens +- Montreal Canadiens +https://youtu.be/fHTehdlMwWQ: +- Nashville +- Predators +- Nashville Predators +https://youtu.be/4q0eNg-AbrQ: +- New Jersey +- Devils +- New Jersey Devils +https://youtu.be/ZC514zGrL80: +- New York +- Islanders +- New York Islanders +https://youtu.be/Zzfks2A2n38: +- New York +- Rangers +- New York Rangers +https://youtu.be/fHlWxPRNVBc: +- Ottawa +- Senators +- Ottawa Senators +https://youtu.be/0LsXpMiVD1E: +- Philadelphia +- Flyers +- Philadelphia Flyers +https://youtu.be/Llw3adcNuzI: +- Pittsburgh +- Penguins +- Pittsburgh Penguins +https://youtu.be/NZqSBkmpbLw: +- San Jose +- Sharks +- San Jose Sharks +https://youtu.be/Q23TDOJsY1s: +- St. Louis +- Blues +- St. Louis Blues +https://youtu.be/bdhDXxM20iM: +- Tampa Bay +- Lightning +- Tampa Bay Lightning +https://youtu.be/2cyekaemZgs: +- Toronto +- Maple Leafs +- Toronto Maple Leafs +https://youtu.be/CPozN-ZHpAo: +- Vancouver +- Canucks +- Vancouver Canucks +https://youtu.be/zheGI316WXg: +- Vegas +- Golden Knights +- Vegas Golden Knights +https://youtu.be/BH_CC1RxtfU: +- Washington +- Capitals +- Washington Capitals +https://youtu.be/3gcahU_i9WE: +- Winnipeg +- Jets +- Winnipeg Jets From 62215ab4bab24714de228afcc746af8b51d402bb Mon Sep 17 00:00:00 2001 From: bobloy Date: Fri, 29 May 2020 11:37:37 -0400 Subject: [PATCH 009/127] Full names --- audiotrivia/data/lists/nhlgoalhorns.yaml | 56 ++++++++++++------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/audiotrivia/data/lists/nhlgoalhorns.yaml b/audiotrivia/data/lists/nhlgoalhorns.yaml index 4dd28ac..689f478 100644 --- a/audiotrivia/data/lists/nhlgoalhorns.yaml +++ b/audiotrivia/data/lists/nhlgoalhorns.yaml @@ -1,125 +1,125 @@ AUTHOR: Lazar https://youtu.be/6OejNXrGkK0: +- Anaheim Ducks - Anaheim - Ducks -- Anaheim Ducks https://youtu.be/RbUxSPoU9Yg: +- Arizona Coyotes - Arizona - Coyotes -- Arizona Coyotes https://youtu.be/DsI0PgWADks: +- Boston Bruins - Boston - Bruins -- Boston Bruins https://youtu.be/hjFTd3MJOHc: +- Buffalo Sabres - Buffalo - Sabres -- Buffalo Sabres https://youtu.be/sn1PliBCRDY: +- Calgary Flames - Calgary - Flames -- Calgary Flames https://youtu.be/3exZm6Frd18: +- Carolina Hurricanes - Carolina - Hurricanes -- Carolina Hurricanes https://youtu.be/sBeXPMkqR80: +- Chicago Blackhawks - Chicago - Blackhawks -- Chicago Blackhawks https://youtu.be/MARxzs_vCPI: +- Colorado Avalanche - Colorado - Avalanche -- Colorado Avalanche https://youtu.be/6yYbQfOWw4k: +- Columbus Blue Jackets - Columbus - Blue Jackets -- Columbus Blue Jackets https://youtu.be/Af8_9NP5lyw: - Dallas - Stars - Dallas Stars https://youtu.be/JflfvLvY7ks: +- Detroit Red Wings - Detroit - Red wings -- Detroit Red Wings https://youtu.be/xc422k5Tcqc: +- Edmonton Oilers - Edmonton - Oilers -- Edmonton Oilers https://youtu.be/Dm1bjUB9HLE: +- Florida Panthers - Florida - Panthers -- Florida Panthers https://youtu.be/jSgd3aIepY4: +- Los Angeles Kings - Los Angeles - Kings -- Los Angeles Kings https://youtu.be/4Pj8hWPR9VI: +- Minnesota Wild - Minnesota - Wild -- Minnesota Wild https://youtu.be/rRGlUFWEBMk: +- Montreal Canadiens - Montreal - Canadiens -- Montreal Canadiens https://youtu.be/fHTehdlMwWQ: +- Nashville Predators - Nashville - Predators -- Nashville Predators https://youtu.be/4q0eNg-AbrQ: +- New Jersey Devils - New Jersey - Devils -- New Jersey Devils https://youtu.be/ZC514zGrL80: - New York - Islanders - New York Islanders https://youtu.be/Zzfks2A2n38: +- New York Rangers - New York - Rangers -- New York Rangers https://youtu.be/fHlWxPRNVBc: +- Ottawa Senators - Ottawa - Senators -- Ottawa Senators https://youtu.be/0LsXpMiVD1E: +- Philadelphia Flyers - Philadelphia - Flyers -- Philadelphia Flyers https://youtu.be/Llw3adcNuzI: +- Pittsburgh Penguins - Pittsburgh - Penguins -- Pittsburgh Penguins https://youtu.be/NZqSBkmpbLw: +- San Jose Sharks - San Jose - Sharks -- San Jose Sharks https://youtu.be/Q23TDOJsY1s: +- St. Louis Blues - St. Louis - Blues -- St. Louis Blues https://youtu.be/bdhDXxM20iM: +- Tampa Bay Lightning - Tampa Bay - Lightning -- Tampa Bay Lightning https://youtu.be/2cyekaemZgs: +- Toronto Maple Leafs - Toronto - Maple Leafs -- Toronto Maple Leafs https://youtu.be/CPozN-ZHpAo: - Vancouver - Canucks - Vancouver Canucks https://youtu.be/zheGI316WXg: +- Vegas Golden Knights - Vegas - Golden Knights -- Vegas Golden Knights https://youtu.be/BH_CC1RxtfU: +- Washington Capitals - Washington - Capitals -- Washington Capitals https://youtu.be/3gcahU_i9WE: +- Winnipeg Jets - Winnipeg - Jets -- Winnipeg Jets From a8ebe7eb97dc2b643a0174b6b55142d2bc768ad6 Mon Sep 17 00:00:00 2001 From: bobloy Date: Fri, 19 Jun 2020 07:37:08 -0400 Subject: [PATCH 010/127] double e extension --- qrinvite/qrinvite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qrinvite/qrinvite.py b/qrinvite/qrinvite.py index 0960c14..cef82e1 100644 --- a/qrinvite/qrinvite.py +++ b/qrinvite/qrinvite.py @@ -60,7 +60,7 @@ class QRInvite(Cog): ) return - eextention = pathlib.Path(image_url).parts[-1].replace(".", "?").split("?")[1] + extension = pathlib.Path(image_url).parts[-1].replace(".", "?").split("?")[1] path: pathlib.Path = cog_data_path(self) image_path = path / (ctx.guild.icon + "." + extension) From 7e66ed49174da7a9f052504e038df88ce72134d3 Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 23 Jun 2020 12:57:32 -0400 Subject: [PATCH 011/127] Handle other file extensions for now --- qrinvite/qrinvite.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/qrinvite/qrinvite.py b/qrinvite/qrinvite.py index cef82e1..67d895b 100644 --- a/qrinvite/qrinvite.py +++ b/qrinvite/qrinvite.py @@ -72,9 +72,15 @@ class QRInvite(Cog): file.write(image) if extension == "webp": - new_path = convert_png(str(image_path)) - else: + new_path = convert_webp_to_png(str(image_path)) + elif extension == "gif": + await ctx.send("gif is not supported yet, stay tuned") + return + elif extension == "png": new_path = str(image_path) + else: + await ctx.send(f"{extension} is not supported yet, stay tuned") + return myqr.run( invite, @@ -89,7 +95,7 @@ class QRInvite(Cog): await ctx.send(file=discord.File(png_fp.read(), "qrcode.png")) -def convert_png(path): +def convert_webp_to_png(path): im = Image.open(path) im.load() alpha = im.split()[-1] From 4844820785ca2f231708c7d40011129edd649be2 Mon Sep 17 00:00:00 2001 From: DannyDB5544 <59149717+DannyDB5544@users.noreply.github.com> Date: Tue, 14 Jul 2020 14:09:48 +0100 Subject: [PATCH 012/127] It'd be smart if i corrected the help page, eh? (#96) --- scp/scp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scp/scp.py b/scp/scp.py index 9827dad..7ffba0c 100644 --- a/scp/scp.py +++ b/scp/scp.py @@ -25,7 +25,7 @@ class SCP(Cog): msg = "http://www.scp-wiki.net/scp-{:03}".format(num) c = discord.Color.green() else: - msg = "You must specify a number between 1 and 4999." + msg = "You must specify a number between 1 and 5999." c = discord.Color.red() if await ctx.embed_requested(): From 6cc5162f56a1ecbe5ddf914196ca5da78f4d5c0a Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 20 Jul 2020 14:31:26 -0400 Subject: [PATCH 013/127] Use more built-in functions, switch to on_command_error to save on processing power (#97) --- ccrole/ccrole.py | 93 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 23 deletions(-) diff --git a/ccrole/ccrole.py b/ccrole/ccrole.py index 3f3be5e..2d662d3 100644 --- a/ccrole/ccrole.py +++ b/ccrole/ccrole.py @@ -1,10 +1,13 @@ import asyncio +import json import re from typing import Any import discord +from discord.ext.commands.view import StringView from redbot.core import Config, checks from redbot.core import commands +from redbot.core.bot import Red from redbot.core.utils.chat_formatting import pagify, box Cog: Any = getattr(commands, "Cog", object) @@ -16,7 +19,7 @@ class CCRole(Cog): Creates commands used to display text and adjust roles """ - def __init__(self, bot): + def __init__(self, bot: Red): self.bot = bot self.config = Config.get_conf(self, identifier=9999114111108101) default_guild = {"cmdlist": {}, "settings": {}} @@ -115,9 +118,7 @@ class CCRole(Cog): return # Selfrole - await ctx.send( - "Is this a targeted command?(yes/no)\nNo will make this a selfrole command" - ) + await ctx.send("Is this a targeted command?(yes/no)\nNo will make this a selfrole command") try: answer = await self.bot.wait_for("message", timeout=120, check=check) @@ -235,24 +236,41 @@ class CCRole(Cog): for page in pagify(cmd_list, delims=[" ", "\n"]): await ctx.author.send(box(page)) await ctx.send("Command list DM'd") - - @commands.Cog.listener() - async def on_message(self, message): - if len(message.content) < 2 or message.guild is None: - return - guild = message.guild - try: - prefix = await self.get_prefix(message) - except ValueError: - return + @commands.Cog.listener() + async def on_command_error(self, ctx: commands.Context, exception): + cmd = ctx.invoked_with + guild = ctx.guild + message = ctx.message cmdlist = self.config.guild(guild).cmdlist - cmd = message.content[len(prefix) :].split()[0].lower() + # cmd = message.content[len(prefix) :].split()[0].lower() cmd = await cmdlist.get_raw(cmd, default=None) if cmd is not None: - await self.eval_cc(cmd, message) + await self.eval_cc(cmd, message, ctx) + + # @commands.Cog.listener() + # async def on_message(self, message: discord.Message): + # if len(message.content) < 2 or message.guild is None: + # return + # + # ctx: commands.Context = await self.bot.get_context(message) + # cmd = ctx.invoked_with + # guild = message.guild + # # try: + # # prefix = await self.get_prefix(message) + # # except ValueError: + # # return + # + # # prefix = ctx.prefix + # + # cmdlist = self.config.guild(guild).cmdlist + # # cmd = message.content[len(prefix) :].split()[0].lower() + # cmd = await cmdlist.get_raw(cmd, default=None) + # + # if cmd is not None: + # await self.eval_cc(cmd, message, ctx) async def _get_roles_from_content(self, ctx, content): content_list = content.split(",") @@ -284,7 +302,7 @@ class CCRole(Cog): return p raise ValueError - async def eval_cc(self, cmd, message): + async def eval_cc(self, cmd, message, ctx): """Does all the work""" if cmd["proles"] and not ( set(role.id for role in message.author.roles) & set(cmd["proles"]) @@ -292,16 +310,45 @@ class CCRole(Cog): return # Not authorized, do nothing if cmd["targeted"]: - try: - target = discord.utils.get( - message.guild.members, mention=message.content.split(maxsplit=1)[1] - ) - except IndexError: # .split() return list of len<2 + # try: + # arg1 = message.content.split(maxsplit=1)[1] + # except IndexError: # .split() return list of len<2 + # target = None + # else: + # target = discord.utils.get( + # message.guild.members, mention=arg1 + # ) + + view: StringView = ctx.view + view.skip_ws() + + guild: discord.Guild = ctx.guild + # print(f"Guild: {guild}") + + target = view.get_quoted_word() + # print(f"Target: {target}") + + if target: + # target = discord.utils.get(guild.members, mention=target) + try: + target = await commands.MemberConverter().convert(ctx, target) + except commands.BadArgument: + target = None + else: target = None + # try: + # arg1 = ctx.args[1] + # except IndexError: # args is list of len<2 + # target = None + # else: + # target = discord.utils.get( + # message.guild.members, mention=arg1 + # ) + if not target: out_message = "This custom command is targeted! @mention a target\n`{} `".format( - message.content.split()[0] + ctx.invoked_with ) await message.channel.send(out_message) return From 9af8601124e4c4d932ccf5c57948b21a7add023a Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 20 Jul 2020 15:25:14 -0400 Subject: [PATCH 014/127] Remove commands.Cog backwards compatibility, and reformatting (#98) * Unneeded requirements * No need for lambda or function * Remove Cog backwards compatibility, and reformatting --- announcedaily/announcedaily.py | 6 +-- ccrole/ccrole.py | 4 +- chatter/chat.py | 15 ++++--- coglint/coglint.py | 7 +--- dad/dad.py | 7 +--- exclusiverole/exclusiverole.py | 4 +- flag/flag.py | 4 +- forcemention/forcemention.py | 10 ++--- hangman/hangman.py | 8 ++-- infochannel/infochannel.py | 30 +++++++------- leaver/leaver.py | 10 +---- lovecalculator/lovecalculator.py | 10 ++--- lseen/lseen.py | 8 +--- planttycoon/planttycoon.py | 69 +++++++++----------------------- qrinvite/qrinvite.py | 4 +- reactrestrict/reactrestrict.py | 7 +--- recyclingplant/recyclingplant.py | 4 +- rpsls/rpsls.py | 4 +- sayurl/sayurl.py | 5 +-- scp/scp.py | 3 +- stealemoji/stealemoji.py | 3 +- timerole/timerole.py | 6 +-- tts/tts.py | 4 +- unicode/unicode.py | 4 +- werewolf/werewolf.py | 35 ++++++++-------- 25 files changed, 97 insertions(+), 174 deletions(-) diff --git a/announcedaily/announcedaily.py b/announcedaily/announcedaily.py index a6b5c81..4b433ba 100644 --- a/announcedaily/announcedaily.py +++ b/announcedaily/announcedaily.py @@ -1,21 +1,19 @@ import asyncio import random from datetime import datetime, timedelta -from typing import Any import discord from redbot.core import Config, checks, commands from redbot.core.bot import Red +from redbot.core.commands import Cog from redbot.core.data_manager import cog_data_path -from redbot.core.utils.chat_formatting import pagify, box +from redbot.core.utils.chat_formatting import box, pagify DEFAULT_MESSAGES = [ # "Example message. Uncomment and overwrite to use", # "Example message 2. Each message is in quotes and separated by a comma" ] -Cog: Any = getattr(commands, "Cog", object) - class AnnounceDaily(Cog): """ diff --git a/ccrole/ccrole.py b/ccrole/ccrole.py index 2d662d3..d2e4fc1 100644 --- a/ccrole/ccrole.py +++ b/ccrole/ccrole.py @@ -10,10 +10,8 @@ from redbot.core import commands from redbot.core.bot import Red from redbot.core.utils.chat_formatting import pagify, box -Cog: Any = getattr(commands, "Cog", object) - -class CCRole(Cog): +class CCRole(commands.Cog): """ Custom commands Creates commands used to display text and adjust roles diff --git a/chatter/chat.py b/chatter/chat.py index 3daa957..10ab156 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -3,17 +3,14 @@ import pathlib from datetime import datetime, timedelta import discord -from redbot.core import Config -from redbot.core import commands +from redbot.core import Config, commands +from redbot.core.commands import Cog from redbot.core.data_manager import cog_data_path from .chatterbot import ChatBot from .chatterbot.comparisons import levenshtein_distance from .chatterbot.response_selection import get_first_response from .chatterbot.trainers import ListTrainer -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) class Chatter(Cog): @@ -152,9 +149,11 @@ class Chatter(Cog): Trains the bot based on language in this guild """ - await ctx.send("Warning: The cog may use significant RAM or CPU if trained on large data sets.\n" - "Additionally, large sets will use more disk space to save the trained data.\n\n" - "If you experience issues, clear your trained data and train again on a smaller scope.") + await ctx.send( + "Warning: The cog may use significant RAM or CPU if trained on large data sets.\n" + "Additionally, large sets will use more disk space to save the trained data.\n\n" + "If you experience issues, clear your trained data and train again on a smaller scope." + ) conversation = await self._get_conversation(ctx, channel) diff --git a/coglint/coglint.py b/coglint/coglint.py index 37f129f..c0feb9b 100644 --- a/coglint/coglint.py +++ b/coglint/coglint.py @@ -1,12 +1,9 @@ import discord from pylint import epylint as lint -from redbot.core import Config -from redbot.core import commands +from redbot.core import Config, commands from redbot.core.bot import Red +from redbot.core.commands import Cog from redbot.core.data_manager import cog_data_path -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) class CogLint(Cog): diff --git a/dad/dad.py b/dad/dad.py index b1ab14a..890134c 100644 --- a/dad/dad.py +++ b/dad/dad.py @@ -1,14 +1,11 @@ from collections import defaultdict from datetime import datetime, timedelta -from typing import Any import aiohttp import discord -from redbot.core import Config, checks -from redbot.core import commands +from redbot.core import Config, checks, commands from redbot.core.bot import Red - -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog async def fetch_url(session, url): diff --git a/exclusiverole/exclusiverole.py b/exclusiverole/exclusiverole.py index e792b44..0a2a054 100644 --- a/exclusiverole/exclusiverole.py +++ b/exclusiverole/exclusiverole.py @@ -2,9 +2,7 @@ import asyncio import discord from redbot.core import Config, checks, commands -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog class ExclusiveRole(Cog): diff --git a/flag/flag.py b/flag/flag.py index 2751d33..47f7270 100644 --- a/flag/flag.py +++ b/flag/flag.py @@ -3,10 +3,8 @@ from datetime import date, timedelta import discord from redbot.core import Config, checks, commands from redbot.core.bot import Red +from redbot.core.commands import Cog from redbot.core.utils.chat_formatting import pagify -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) class Flag(Cog): diff --git a/forcemention/forcemention.py b/forcemention/forcemention.py index 0bf2a61..72c9466 100644 --- a/forcemention/forcemention.py +++ b/forcemention/forcemention.py @@ -1,19 +1,17 @@ -from discord.utils import get +import asyncio +from discord.utils import get from redbot import VersionInfo, version_info from redbot.core import Config, checks, commands - from redbot.core.bot import Red -from typing import Any -import asyncio - -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog if version_info < VersionInfo.from_str("3.4.0"): SANITIZE_ROLES_KWARG = {} else: SANITIZE_ROLES_KWARG = {"sanitize_roles": False} + class ForceMention(Cog): """ Mention the unmentionables diff --git a/hangman/hangman.py b/hangman/hangman.py index 69db263..4e44ce6 100644 --- a/hangman/hangman.py +++ b/hangman/hangman.py @@ -3,10 +3,8 @@ from random import randint import discord from redbot.core import Config, checks, commands +from redbot.core.commands import Cog from redbot.core.data_manager import bundled_data_path -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) class Hangman(Cog): @@ -31,9 +29,9 @@ class Hangman(Cog): "answer": "", } ) -# self.path = str(cog_data_path(self)).replace("\\", "/") + # self.path = str(cog_data_path(self)).replace("\\", "/") -# self.answer_path = self.path + "/bundled_data/hanganswers.txt" + # self.answer_path = self.path + "/bundled_data/hanganswers.txt" self.answer_path = bundled_data_path(self) / "hanganswers.txt" diff --git a/infochannel/infochannel.py b/infochannel/infochannel.py index 23e04de..278a6d7 100644 --- a/infochannel/infochannel.py +++ b/infochannel/infochannel.py @@ -1,15 +1,14 @@ -from typing import Any import discord - -from redbot.core import Config, commands, checks +from redbot.core import Config, checks, commands from redbot.core.bot import Red +from redbot.core.commands import Cog -Cog: Any = getattr(commands, "Cog", object) -listener = getattr(commands.Cog, "listener", None) # Trusty + Sinbad -if listener is None: - def listener(name=None): - return lambda x: x +# Cog: Any = getattr(commands, "Cog", object) +# listener = getattr(commands.Cog, "listener", None) # Trusty + Sinbad +# if listener is None: +# def listener(name=None): +# return lambda x: x class InfoChannel(Cog): @@ -160,8 +159,10 @@ class InfoChannel(Cog): onlinecount = guild_data["online_count"] # Gets count of bots - bots = lambda x: x.bot - num = len([m for m in guild.members if bots(m)]) + # bots = lambda x: x.bot + # def bots(x): return x.bot + + num = len([m for m in guild.members if m.bot]) bot_msg = f"Bots: {num}" # Gets count of online users @@ -188,8 +189,7 @@ class InfoChannel(Cog): if guild_data["member_count"]: name = "{} ".format(human_msg) - - await channel.edit(reason="InfoChannel update", name=name) + await channel.edit(reason="InfoChannel update", name=name) if botcount: name = "{} ".format(bot_msg) @@ -199,15 +199,15 @@ class InfoChannel(Cog): name = "{} ".format(online_msg) await onlinechannel.edit(reason="InfoChannel update", name=name) - @listener() + @Cog.listener() async def on_member_join(self, member: discord.Member): await self.update_infochannel(member.guild) - @listener() + @Cog.listener() async def on_member_remove(self, member: discord.Member): await self.update_infochannel(member.guild) - @listener() + @Cog.listener() async def on_member_update(self, before: discord.Member, after: discord.Member): onlinecount = await self.config.guild(after.guild).online_count() if onlinecount: diff --git a/leaver/leaver.py b/leaver/leaver.py index 83e8f1f..f6e7fd7 100644 --- a/leaver/leaver.py +++ b/leaver/leaver.py @@ -1,11 +1,7 @@ import discord - from redbot.core import Config, checks, commands from redbot.core.bot import Red -from redbot.core.commands import Context -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog, Context class Leaver(Cog): @@ -15,9 +11,7 @@ class Leaver(Cog): def __init__(self, bot: Red): self.bot = bot - self.config = Config.get_conf( - self, identifier=9811198108111121, force_registration=True - ) + self.config = Config.get_conf(self, identifier=9811198108111121, force_registration=True) default_guild = {"channel": ""} self.config.register_guild(**default_guild) diff --git a/lovecalculator/lovecalculator.py b/lovecalculator/lovecalculator.py index 3a83cab..0bf85f3 100644 --- a/lovecalculator/lovecalculator.py +++ b/lovecalculator/lovecalculator.py @@ -2,9 +2,7 @@ import aiohttp import discord from bs4 import BeautifulSoup from redbot.core import commands -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog class LoveCalculator(Cog): @@ -15,7 +13,7 @@ class LoveCalculator(Cog): @commands.command(aliases=["lovecalc"]) async def lovecalculator( - self, ctx: commands.Context, lover: discord.Member, loved: discord.Member + self, ctx: commands.Context, lover: discord.Member, loved: discord.Member ): """Calculate the love percentage!""" @@ -30,7 +28,9 @@ class LoveCalculator(Cog): soup_object = BeautifulSoup(await response.text(), "html.parser") try: description = ( - soup_object.find("div", attrs={"class": "result__score"}).get_text().strip() + soup_object.find("div", attrs={"class": "result__score"}) + .get_text() + .strip() ) except: description = "Dr. Love is busy right now" diff --git a/lseen/lseen.py b/lseen/lseen.py index 7a3d6ab..95916d4 100644 --- a/lseen/lseen.py +++ b/lseen/lseen.py @@ -2,13 +2,9 @@ from datetime import datetime import dateutil.parser import discord - -from redbot.core import Config +from redbot.core import Config, commands from redbot.core.bot import Red -from redbot.core import commands -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog class LastSeen(Cog): diff --git a/planttycoon/planttycoon.py b/planttycoon/planttycoon.py index 1ab7eba..4396bd2 100644 --- a/planttycoon/planttycoon.py +++ b/planttycoon/planttycoon.py @@ -5,15 +5,14 @@ import datetime import json import time from random import choice -from typing import Any import discord -from redbot.core import commands, Config, bank +from redbot.core import Config, bank, commands from redbot.core.bot import Red from redbot.core.data_manager import bundled_data_path -class Gardener: +class Gardener(commands.Cog): """Gardener class""" def __init__(self, user: discord.User, config: Config): @@ -30,9 +29,7 @@ class Gardener: "Badges: {}\n" "Points: {}\n" "Products: {}\n" - "Current: {}".format( - self.user, self.badges, self.points, self.products, self.current - ) + "Current: {}".format(self.user, self.badges, self.points, self.products, self.current) ) def __repr__(self): @@ -246,17 +243,12 @@ class PlantTycoon(commands.Cog): degradation = ( 100 / (gardener.current["time"] / 60) - * ( - self.defaults["degradation"]["base_degradation"] - + gardener.current["degradation"] - ) + * (self.defaults["degradation"]["base_degradation"] + gardener.current["degradation"]) ) + modifiers d = collections.namedtuple("degradation", "degradation time modifiers") - return d( - degradation=degradation, time=gardener.current["time"], modifiers=modifiers - ) + return d(degradation=degradation, time=gardener.current["time"], modifiers=modifiers) # async def _get_member(self, user_id): # @@ -285,10 +277,7 @@ class PlantTycoon(commands.Cog): await self._load_plants_products() product = product.lower() product_category = product_category.lower() - if ( - product in self.products - and self.products[product]["category"] == product_category - ): + if product in self.products and self.products[product]["category"] == product_category: if product in gardener.products: if gardener.products[product] > 0: gardener.current["health"] += self.products[product]["health"] @@ -359,13 +348,9 @@ class PlantTycoon(commands.Cog): ``{0}prune``: Prune your plant.\n""" em = discord.Embed( - title=title, - description=description.format(prefix), - color=discord.Color.green(), - ) - em.set_thumbnail( - url="https://image.prntscr.com/image/AW7GuFIBSeyEgkR2W3SeiQ.png" + title=title, description=description.format(prefix), color=discord.Color.green(), ) + em.set_thumbnail(url="https://image.prntscr.com/image/AW7GuFIBSeyEgkR2W3SeiQ.png") em.set_footer( text="This cog was made by SnappyDragon18 and PaddoInWonderland. Inspired by The Lorax (2012)." ) @@ -439,9 +424,7 @@ class PlantTycoon(commands.Cog): em = discord.Embed(color=discord.Color.green()) # , description='\a\n') avatar = author.avatar_url if author.avatar else author.default_avatar_url - em.set_author( - name="Gardening profile of {}".format(author.name), icon_url=avatar - ) + em.set_author(name="Gardening profile of {}".format(author.name), icon_url=avatar) em.add_field(name="**Thneeds**", value=str(gardener.points)) if not gardener.current: em.add_field(name="**Currently growing**", value="None") @@ -508,9 +491,7 @@ class PlantTycoon(commands.Cog): else: tock += "**{}**\n".format(plant["name"]) tick_tock = 0 - em = discord.Embed( - title="All plants that are growable", color=discord.Color.green() - ) + em = discord.Embed(title="All plants that are growable", color=discord.Color.green()) em.add_field(name="\a", value=tick) em.add_field(name="\a", value=tock) await ctx.send(embed=em) @@ -532,18 +513,13 @@ class PlantTycoon(commands.Cog): if t: em = discord.Embed( - title="Plant statistics of {}".format(plant["name"]), - color=discord.Color.green(), + title="Plant statistics of {}".format(plant["name"]), color=discord.Color.green(), ) em.set_thumbnail(url=plant["image"]) em.add_field(name="**Name**", value=plant["name"]) em.add_field(name="**Rarity**", value=plant["rarity"].capitalize()) - em.add_field( - name="**Grow Time**", value="{0:.1f} minutes".format(plant["time"] / 60) - ) - em.add_field( - name="**Damage Threshold**", value="{}%".format(plant["threshold"]) - ) + em.add_field(name="**Grow Time**", value="{0:.1f} minutes".format(plant["time"] / 60)) + em.add_field(name="**Damage Threshold**", value="{}%".format(plant["threshold"])) em.add_field(name="**Badge**", value=plant["badge"]) em.add_field(name="**Reward**", value="{} τ".format(plant["reward"])) else: @@ -595,8 +571,7 @@ class PlantTycoon(commands.Cog): author = ctx.author if product is None: em = discord.Embed( - title="All gardening supplies that you can buy:", - color=discord.Color.green(), + title="All gardening supplies that you can buy:", color=discord.Color.green(), ) for pd in self.products: em.add_field( @@ -630,8 +605,7 @@ class PlantTycoon(commands.Cog): message = "You bought {}.".format(product.lower()) else: message = "You don't have enough Thneeds. You have {}, but need {}.".format( - gardener.points, - self.products[product.lower()]["cost"] * amount, + gardener.points, self.products[product.lower()]["cost"] * amount, ) else: message = "I don't have this product." @@ -650,14 +624,11 @@ class PlantTycoon(commands.Cog): plural = "s" if withdraw_points: await bank.deposit_credits(author, amount) - message = "{} Thneed{} successfully exchanged for credits.".format( - amount, plural - ) + message = "{} Thneed{} successfully exchanged for credits.".format(amount, plural) await gardener.save_gardener() else: - message = ( - "You don't have enough Thneed{}. " - "You have {}, but need {}.".format(plural, gardener.points, amount) + message = "You don't have enough Thneed{}. " "You have {}, but need {}.".format( + plural, gardener.points, amount ) em = discord.Embed(description=message, color=discord.Color.green()) @@ -749,9 +720,7 @@ class PlantTycoon(commands.Cog): degradation = await self._degradation(gardener) now = int(time.time()) timestamp = gardener.current["timestamp"] - degradation_count = (now - timestamp) // ( - self.defaults["timers"]["degradation"] * 60 - ) + degradation_count = (now - timestamp) // (self.defaults["timers"]["degradation"] * 60) degradation_count -= gardener.current["degrade_count"] gardener.current["health"] -= degradation.degradation * degradation_count gardener.points += self.defaults["points"]["growing"] * degradation_count diff --git a/qrinvite/qrinvite.py b/qrinvite/qrinvite.py index 67d895b..ace9cd7 100644 --- a/qrinvite/qrinvite.py +++ b/qrinvite/qrinvite.py @@ -6,10 +6,8 @@ from MyQR import myqr from PIL import Image from redbot.core import Config, commands from redbot.core.bot import Red +from redbot.core.commands import Cog from redbot.core.data_manager import cog_data_path -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) class QRInvite(Cog): diff --git a/reactrestrict/reactrestrict.py b/reactrestrict/reactrestrict.py index 339b474..a6f212e 100644 --- a/reactrestrict/reactrestrict.py +++ b/reactrestrict/reactrestrict.py @@ -1,12 +1,9 @@ from typing import List, Union import discord -from redbot.core import Config -from redbot.core import commands +from redbot.core import Config, commands from redbot.core.bot import Red -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog class ReactRestrictCombo: diff --git a/recyclingplant/recyclingplant.py b/recyclingplant/recyclingplant.py index 4f3ddfc..3ed88a2 100644 --- a/recyclingplant/recyclingplant.py +++ b/recyclingplant/recyclingplant.py @@ -1,13 +1,11 @@ import asyncio import json import random -from typing import Any from redbot.core import bank, commands +from redbot.core.commands import Cog from redbot.core.data_manager import bundled_data_path -Cog: Any = getattr(commands, "Cog", object) - class RecyclingPlant(Cog): """Apply for a job at the recycling plant!""" diff --git a/rpsls/rpsls.py b/rpsls/rpsls.py index e97a923..b1386d1 100644 --- a/rpsls/rpsls.py +++ b/rpsls/rpsls.py @@ -3,9 +3,7 @@ import random import discord from redbot.core import commands -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog class RPSLS(Cog): diff --git a/sayurl/sayurl.py b/sayurl/sayurl.py index abc9a99..21c3076 100644 --- a/sayurl/sayurl.py +++ b/sayurl/sayurl.py @@ -1,12 +1,9 @@ import aiohttp import html2text - from redbot.core import Config, commands from redbot.core.bot import Red +from redbot.core.commands import Cog from redbot.core.utils.chat_formatting import pagify -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) async def fetch_url(session, url): diff --git a/scp/scp.py b/scp/scp.py index 7ffba0c..4dd9b79 100644 --- a/scp/scp.py +++ b/scp/scp.py @@ -1,8 +1,7 @@ import discord from redbot.core import commands -from typing import Any -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog class SCP(Cog): diff --git a/stealemoji/stealemoji.py b/stealemoji/stealemoji.py index 0a12249..1ee4814 100644 --- a/stealemoji/stealemoji.py +++ b/stealemoji/stealemoji.py @@ -4,9 +4,8 @@ import discord from redbot.core import Config, commands from redbot.core.bot import Red -from typing import Any -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog async def fetch_img(session, url): diff --git a/timerole/timerole.py b/timerole/timerole.py index 44dd767..ef05f18 100644 --- a/timerole/timerole.py +++ b/timerole/timerole.py @@ -1,13 +1,11 @@ import asyncio -from datetime import timedelta, datetime +from datetime import datetime, timedelta import discord from redbot.core import Config, checks, commands from redbot.core.bot import Red +from redbot.core.commands import Cog from redbot.core.utils.chat_formatting import pagify -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) class Timerole(Cog): diff --git a/tts/tts.py b/tts/tts.py index 1ed18e7..02f8b8e 100644 --- a/tts/tts.py +++ b/tts/tts.py @@ -4,9 +4,7 @@ import discord from gtts import gTTS from redbot.core import Config, commands from redbot.core.bot import Red -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog class TTS(Cog): diff --git a/unicode/unicode.py b/unicode/unicode.py index 0d72a1c..c24c816 100644 --- a/unicode/unicode.py +++ b/unicode/unicode.py @@ -2,9 +2,7 @@ import codecs as c import discord from redbot.core import commands -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) +from redbot.core.commands import Cog class Unicode(Cog): diff --git a/werewolf/werewolf.py b/werewolf/werewolf.py index 3971f7f..cd4d95b 100644 --- a/werewolf/werewolf.py +++ b/werewolf/werewolf.py @@ -1,16 +1,17 @@ import discord - -from redbot.core import Config, checks - +from redbot.core import Config, checks, commands from redbot.core.bot import Red -from redbot.core import commands - -from .builder import GameBuilder, role_from_name, role_from_alignment, role_from_category, role_from_id +from redbot.core.commands import Cog +from redbot.core.utils.menus import DEFAULT_CONTROLS, menu + +from .builder import ( + GameBuilder, + role_from_alignment, + role_from_category, + role_from_id, + role_from_name, +) from .game import Game -from redbot.core.utils.menus import menu, DEFAULT_CONTROLS -from typing import Any - -Cog: Any = getattr(commands, "Cog", object) class Werewolf(Cog): @@ -20,13 +21,15 @@ class Werewolf(Cog): def __init__(self, bot: Red): self.bot = bot - self.config = Config.get_conf(self, identifier=87101114101119111108102, force_registration=True) + self.config = Config.get_conf( + self, identifier=87101114101119111108102, force_registration=True + ) default_global = {} default_guild = { "role_id": None, "category_id": None, "channel_id": None, - "log_channel_id": None + "log_channel_id": None, } self.config.register_global(**default_global) @@ -83,7 +86,7 @@ class Werewolf(Cog): @commands.guild_only() @wwset.command(name="role") - async def wwset_role(self, ctx: commands.Context, role: discord.Role=None): + async def wwset_role(self, ctx: commands.Context, role: discord.Role = None): """ Assign the game role This role should not be manually assigned @@ -97,7 +100,7 @@ class Werewolf(Cog): @commands.guild_only() @wwset.command(name="category") - async def wwset_category(self, ctx: commands.Context, category_id: int=None): + async def wwset_category(self, ctx: commands.Context, category_id: int = None): """ Assign the channel category """ @@ -114,7 +117,7 @@ class Werewolf(Cog): @commands.guild_only() @wwset.command(name="channel") - async def wwset_channel(self, ctx: commands.Context, channel: discord.TextChannel=None): + async def wwset_channel(self, ctx: commands.Context, channel: discord.TextChannel = None): """ Assign the village channel """ @@ -127,7 +130,7 @@ class Werewolf(Cog): @commands.guild_only() @wwset.command(name="logchannel") - async def wwset_log_channel(self, ctx: commands.Context, channel: discord.TextChannel=None): + async def wwset_log_channel(self, ctx: commands.Context, channel: discord.TextChannel = None): """ Assign the log channel """ From ebe59c937063a30e3fd2e52f90cf69fc810b1082 Mon Sep 17 00:00:00 2001 From: bobloy Date: Wed, 22 Jul 2020 15:25:31 -0400 Subject: [PATCH 015/127] Reapply case-insensitivity and get off that command_error train (#101) * Reapply case-insensitivity and get off that command_error train * Can't forget their check, thanks again Cog-Creators This will continue with CCRole being a case-insensitive cog, until the day I change my mind by making this a subclass of CustomCom --- ccrole/ccrole.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/ccrole/ccrole.py b/ccrole/ccrole.py index d2e4fc1..b07897b 100644 --- a/ccrole/ccrole.py +++ b/ccrole/ccrole.py @@ -1,14 +1,11 @@ import asyncio -import json import re -from typing import Any import discord from discord.ext.commands.view import StringView -from redbot.core import Config, checks -from redbot.core import commands +from redbot.core import Config, checks, commands from redbot.core.bot import Red -from redbot.core.utils.chat_formatting import pagify, box +from redbot.core.utils.chat_formatting import box, pagify class CCRole(commands.Cog): @@ -236,10 +233,30 @@ class CCRole(commands.Cog): await ctx.send("Command list DM'd") @commands.Cog.listener() - async def on_command_error(self, ctx: commands.Context, exception): + async def on_message_without_command(self, message: discord.Message): + + """Filtering credit to redbot.cogs.customcom's listener""" + ########### + is_private = isinstance(message.channel, discord.abc.PrivateChannel) + + # user_allowed check, will be replaced with self.bot.user_allowed or + # something similar once it's added + user_allowed = True + + if len(message.content) < 2 or is_private or not user_allowed or message.author.bot: + return + + ctx = await self.bot.get_context(message) + + if ctx.prefix is None: + return + ########### + # Thank you Cog-Creators + cmd = ctx.invoked_with + cmd = cmd.lower() # Continues the proud case_insentivity tradition of ccrole guild = ctx.guild - message = ctx.message + # message = ctx.message # Unneeded since switch to `on_message_without_command` from `on_command_error` cmdlist = self.config.guild(guild).cmdlist # cmd = message.content[len(prefix) :].split()[0].lower() From 0749706e8809d1f3d26f7c69e0baeb2b5a2d3f23 Mon Sep 17 00:00:00 2001 From: imnotverygood <68748172+imnotverygood@users.noreply.github.com> Date: Fri, 24 Jul 2020 18:30:52 +0100 Subject: [PATCH 016/127] Add BytesIO seek to fix empty file error (#104) * Add BytesIO seek to fix empty file error "ValueError: embedded null byte" error on the discord attachment due to the data being read from the end. * Fix incorrect indent * Add discord back to File call --- tts/tts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tts/tts.py b/tts/tts.py index 02f8b8e..ef2a223 100644 --- a/tts/tts.py +++ b/tts/tts.py @@ -30,4 +30,5 @@ class TTS(Cog): mp3_fp = io.BytesIO() tts = gTTS(text, "en") tts.write_to_fp(mp3_fp) - await ctx.send(file=discord.File(mp3_fp.getvalue(), "text.mp3")) + mp3_fp.seek(0) + await ctx.send(file=discord.File(mp3_fp, "text.mp3")) From acaa5b8fb9ecf6bfd23d4b90792491ca257704b2 Mon Sep 17 00:00:00 2001 From: bobloy Date: Fri, 24 Jul 2020 13:33:20 -0400 Subject: [PATCH 017/127] Positional arguments changed (#105) --- tts/tts.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tts/tts.py b/tts/tts.py index ef2a223..e00bfa7 100644 --- a/tts/tts.py +++ b/tts/tts.py @@ -12,7 +12,8 @@ class TTS(Cog): Send Text-to-Speech messages """ - def __init__(self, bot: Red): + def __init__(self, bot: Red, *args, **kwargs): + super().__init__(*args, **kwargs) self.bot = bot self.config = Config.get_conf(self, identifier=9811198108111121, force_registration=True) @@ -28,7 +29,7 @@ class TTS(Cog): Send Text to speech messages as an mp3 """ mp3_fp = io.BytesIO() - tts = gTTS(text, "en") + tts = gTTS(text, lang="en") tts.write_to_fp(mp3_fp) mp3_fp.seek(0) await ctx.send(file=discord.File(mp3_fp, "text.mp3")) From 24c2791f8933ae5d0d454aefc13f594edbac6fca Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 27 Jul 2020 12:24:12 -0400 Subject: [PATCH 018/127] Infochannel develop (#106) * Unneeded requirements * No need for lambda or function * Remove Cog backwards compatibility, and reformatting * Address rate limits? * More error checks, better help, common sense channel edits, and rate limit checks * Black formatting as well --- infochannel/infochannel.py | 108 ++++++++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/infochannel/infochannel.py b/infochannel/infochannel.py index 278a6d7..86d0df4 100644 --- a/infochannel/infochannel.py +++ b/infochannel/infochannel.py @@ -1,15 +1,18 @@ +import asyncio + import discord from redbot.core import Config, checks, commands from redbot.core.bot import Red from redbot.core.commands import Cog - # Cog: Any = getattr(commands, "Cog", object) # listener = getattr(commands.Cog, "listener", None) # Trusty + Sinbad # if listener is None: # def listener(name=None): # return lambda x: x +RATE_LIMIT_DELAY = 60 * 10 # If you're willing to risk rate limiting, you can decrease the delay + class InfoChannel(Cog): """ @@ -19,6 +22,7 @@ class InfoChannel(Cog): """ def __init__(self, bot: Red): + super().__init__() self.bot = bot self.config = Config.get_conf( self, identifier=731101021116710497110110101108, force_registration=True @@ -35,6 +39,8 @@ class InfoChannel(Cog): self.config.register_guild(**default_guild) + self._critical_section_wooah_ = 0 + @commands.command() @checks.admin() async def infochannel(self, ctx: commands.Context): @@ -70,9 +76,13 @@ class InfoChannel(Cog): return if channel is None: - await self.make_infochannel(guild) + try: + await self.make_infochannel(guild) + except discord.Forbidden: + await ctx.send("Failure: Missing permission to create voice channel") + return else: - await self.delete_infochannel(guild, channel) + await self.delete_all_infochannels(guild) if not await ctx.tick(): await ctx.send("Done!") @@ -83,6 +93,8 @@ class InfoChannel(Cog): """ Toggle different types of infochannels """ + if not ctx.invoked_subcommand: + pass @infochannelset.command(name="botcount") async def _infochannelset_botcount(self, ctx: commands.Context, enabled: bool = None): @@ -92,7 +104,10 @@ class InfoChannel(Cog): guild = ctx.guild if enabled is None: enabled = not await self.config.guild(guild).bot_count() + await self.config.guild(guild).bot_count.set(enabled) + await self.make_infochannel(ctx.guild) + if enabled: await ctx.send("InfoChannel for bot count has been enabled.") else: @@ -106,7 +121,10 @@ class InfoChannel(Cog): guild = ctx.guild if enabled is None: enabled = not await self.config.guild(guild).online_count() + await self.config.guild(guild).online_count.set(enabled) + await self.make_infochannel(ctx.guild) + if enabled: await ctx.send("InfoChannel for online user count has been enabled.") else: @@ -120,25 +138,49 @@ class InfoChannel(Cog): guild.me: discord.PermissionOverwrite(manage_channels=True, connect=True), } + # Remove the old info channel first + channel_id = await self.config.guild(guild).channel_id() + if channel_id is not None: + channel: discord.VoiceChannel = guild.get_channel(channel_id) + if channel: + await channel.delete(reason="InfoChannel delete") + + # Then create the new one channel = await guild.create_voice_channel( - "Placeholder", reason="InfoChannel make", overwrites=overwrites + "Total Humans:", reason="InfoChannel make", overwrites=overwrites ) await self.config.guild(guild).channel_id.set(channel.id) if botcount: + # Remove the old bot channel first + botchannel_id = await self.config.guild(guild).botchannel_id() + if channel_id is not None: + botchannel: discord.VoiceChannel = guild.get_channel(botchannel_id) + if botchannel: + await botchannel.delete(reason="InfoChannel delete") + + # Then create the new one botchannel = await guild.create_voice_channel( - "Placeholder", reason="InfoChannel botcount", overwrites=overwrites + "Bots:", reason="InfoChannel botcount", overwrites=overwrites ) await self.config.guild(guild).botchannel_id.set(botchannel.id) if onlinecount: + # Remove the old online channel first + onlinechannel_id = await self.config.guild(guild).onlinechannel_id() + if channel_id is not None: + onlinechannel: discord.VoiceChannel = guild.get_channel(onlinechannel_id) + if onlinechannel: + await onlinechannel.delete(reason="InfoChannel delete") + + # Then create the new one onlinechannel = await guild.create_voice_channel( - "Placeholder", reason="InfoChannel onlinecount", overwrites=overwrites + "Online:", reason="InfoChannel onlinecount", overwrites=overwrites ) await self.config.guild(guild).onlinechannel_id.set(onlinechannel.id) await self.update_infochannel(guild) - async def delete_infochannel(self, guild: discord.Guild, channel: discord.VoiceChannel): + async def delete_all_infochannels(self, guild: discord.Guild): guild_data = await self.config.guild(guild).all() botchannel_id = guild_data["botchannel_id"] onlinechannel_id = guild_data["onlinechannel_id"] @@ -151,6 +193,7 @@ class InfoChannel(Cog): await botchannel.delete(reason="InfoChannel delete") if onlinechannel_id is not None: await onlinechannel.delete(reason="InfoChannel delete") + await self.config.guild(guild).clear() async def update_infochannel(self, guild: discord.Guild): @@ -162,23 +205,23 @@ class InfoChannel(Cog): # bots = lambda x: x.bot # def bots(x): return x.bot - num = len([m for m in guild.members if m.bot]) - bot_msg = f"Bots: {num}" + bot_num = len([m for m in guild.members if m.bot]) + # bot_msg = f"Bots: {num}" # Gets count of online users members = guild.member_count offline = len(list(filter(lambda m: m.status is discord.Status.offline, guild.members))) - num = members - offline - online_msg = f"Online: {num}" + online_num = members - offline + # online_msg = f"Online: {num}" # Gets count of actual users total = lambda x: not x.bot - num = len([m for m in guild.members if total(m)]) - human_msg = f"Total Humans: {num}" + human_num = len([m for m in guild.members if total(m)]) + # human_msg = f"Total Humans: {num}" channel_id = guild_data["channel_id"] if channel_id is None: - return + return False botchannel_id = guild_data["botchannel_id"] onlinechannel_id = guild_data["onlinechannel_id"] @@ -188,28 +231,53 @@ class InfoChannel(Cog): onlinechannel: discord.VoiceChannel = guild.get_channel(onlinechannel_id) if guild_data["member_count"]: - name = "{} ".format(human_msg) + name = f"{channel.name.split(':')[0]}: {human_num}" + await channel.edit(reason="InfoChannel update", name=name) if botcount: - name = "{} ".format(bot_msg) + name = f"{botchannel.name.split(':')[0]}: {bot_num}" await botchannel.edit(reason="InfoChannel update", name=name) if onlinecount: - name = "{} ".format(online_msg) + name = f"{onlinechannel.name.split(':')[0]}: {online_num}" await onlinechannel.edit(reason="InfoChannel update", name=name) + async def update_infochannel_with_cooldown(self, guild): + """My attempt at preventing rate limits, lets see how it goes""" + if self._critical_section_wooah_: + if self._critical_section_wooah_ == 2: + print("Already pending, skipping") + return # Another one is already pending, don't queue more than one + print("Queuing another update") + self._critical_section_wooah_ = 2 + + while self._critical_section_wooah_: + await asyncio.sleep( + RATE_LIMIT_DELAY // 4 + ) # Max delay ends up as 1.25 * RATE_LIMIT_DELAY + + print("Issuing queued update") + return await self.update_infochannel_with_cooldown(guild) + + print("Entering critical") + self._critical_section_wooah_ = 1 + await self.update_infochannel(guild) + await asyncio.sleep(RATE_LIMIT_DELAY) + self._critical_section_wooah_ = 0 + print("Exiting critical") + @Cog.listener() async def on_member_join(self, member: discord.Member): - await self.update_infochannel(member.guild) + await self.update_infochannel_with_cooldown(member.guild) @Cog.listener() async def on_member_remove(self, member: discord.Member): - await self.update_infochannel(member.guild) + await self.update_infochannel_with_cooldown(member.guild) @Cog.listener() async def on_member_update(self, before: discord.Member, after: discord.Member): onlinecount = await self.config.guild(after.guild).online_count() if onlinecount: if before.status != after.status: - await self.update_infochannel(after.guild) + await self.update_infochannel_with_cooldown(after.guild) From ff074bd6030c8acf9324331a75eeda6c54f255fb Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 27 Jul 2020 12:24:49 -0400 Subject: [PATCH 019/127] Small ccrole update, sends help correctly --- ccrole/ccrole.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ccrole/ccrole.py b/ccrole/ccrole.py index b07897b..bacbd76 100644 --- a/ccrole/ccrole.py +++ b/ccrole/ccrole.py @@ -15,6 +15,7 @@ class CCRole(commands.Cog): """ def __init__(self, bot: Red): + super().__init__() self.bot = bot self.config = Config.get_conf(self, identifier=9999114111108101) default_guild = {"cmdlist": {}, "settings": {}} @@ -23,12 +24,12 @@ class CCRole(commands.Cog): @commands.guild_only() @commands.group() - async def ccrole(self, ctx): + async def ccrole(self, ctx: commands.Context): """Custom commands management with roles Highly customizable custom commands with role management.""" if not ctx.invoked_subcommand: - pass + await ctx.send_help() @ccrole.command(name="add") @checks.mod_or_permissions(administrator=True) From e1256d26a58b3a0caf8f66d81922748e17778fa6 Mon Sep 17 00:00:00 2001 From: bobloy Date: Wed, 29 Jul 2020 11:34:26 -0400 Subject: [PATCH 020/127] Better credits --- ccrole/ccrole.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ccrole/ccrole.py b/ccrole/ccrole.py index bacbd76..9be63f2 100644 --- a/ccrole/ccrole.py +++ b/ccrole/ccrole.py @@ -235,8 +235,11 @@ class CCRole(commands.Cog): @commands.Cog.listener() async def on_message_without_command(self, message: discord.Message): - - """Filtering credit to redbot.cogs.customcom's listener""" + """ + Credit to: + https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/develop/redbot/cogs/customcom/customcom.py#L508 + for the message filtering + """ ########### is_private = isinstance(message.channel, discord.abc.PrivateChannel) From 2a7a1b8b920bcee7658fded838bcb12df429c3bf Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 30 Jul 2020 09:48:43 -0400 Subject: [PATCH 021/127] [StealEmoji] Bug fixes and better handling (#42) * StealEmoji properly handle Emojis not having the `__dict__` attribute Start fix for emoiji vs animated max error * Comment-out some prints * First step to fixing stealemoji Uses discord.Asset now * Reformat * Call __init__ * Fix duplicate emojis, formatting, caching --- stealemoji/stealemoji.py | 161 ++++++++++++++++++++++++++++----------- 1 file changed, 117 insertions(+), 44 deletions(-) diff --git a/stealemoji/stealemoji.py b/stealemoji/stealemoji.py index 1ee4814..cfe011b 100644 --- a/stealemoji/stealemoji.py +++ b/stealemoji/stealemoji.py @@ -1,17 +1,27 @@ -import aiohttp - import discord - -from redbot.core import Config, commands +from redbot.core import Config, checks, commands from redbot.core.bot import Red - from redbot.core.commands import Cog -async def fetch_img(session, url): - async with session.get(url) as response: - assert response.status == 200 - return await response.read() +# Replaced with discord.Asset.read() +# async def fetch_img(session: aiohttp.ClientSession, url: StrOrURL): +# async with session.get(url) as response: +# assert response.status == 200 +# return await response.read() + + +async def check_guild(guild, emoji): + if len(guild.emojis) >= 100: + return False + + if len(guild.emojis) < 50: + return True + + if emoji.animated: + return sum(e.animated for e in guild.emojis) < 50 + else: + return sum(not e.animated for e in guild.emojis) < 50 class StealEmoji(Cog): @@ -22,20 +32,22 @@ class StealEmoji(Cog): default_stolemoji = { "guildbank": None, "name": None, - "require_colons": False, - "managed": False, + "require_colons": None, + "managed": None, "guild_id": None, - "url": None, - "animated": False, + "animated": None, } def __init__(self, red: Red): + super().__init__() self.bot = red self.config = Config.get_conf(self, identifier=11511610197108101109111106105) - default_global = {"stolemoji": {}, "guildbanks": [], "on": False} + default_global = {"stolemoji": {}, "guildbanks": [], "on": False, "notify": 0} self.config.register_global(**default_global) + self.is_on = await self.config.on() + @commands.group() async def stealemoji(self, ctx: commands.Context): """ @@ -44,20 +56,44 @@ class StealEmoji(Cog): if ctx.invoked_subcommand is None: pass + @checks.is_owner() + @stealemoji.command(name="notify") + async def se_notify(self, ctx: commands.Context): + """Cycles between notification settings for when an emoji is stolen + + None (Default) + DM Owner + Msg in server channel + """ + curr_setting = await self.config.notify() + + if not curr_setting: + await self.config.notify.set(1) + await ctx.send("Bot owner will now be notified when an emoji is stolen") + elif curr_setting == 1: + channel: discord.TextChannel = ctx.channel + await self.config.notify.set(channel.id) + await ctx.send("This channel will now be notified when an emoji is stolen") + else: + await self.config.notify.set(0) + await ctx.send("Notifications are now off") + + @checks.is_owner() @stealemoji.command(name="collect") async def se_collect(self, ctx): """Toggles whether emoji's are collected or not""" curr_setting = await self.config.on() await self.config.on.set(not curr_setting) + + self.is_on = await self.config.on() + await ctx.send("Collection is now " + str(not curr_setting)) + @checks.is_owner() + @commands.guild_only() @stealemoji.command(name="bank") async def se_bank(self, ctx): """Add current server as emoji bank""" - await ctx.send( - "This will upload custom emojis to this server\n" - "Are you sure you want to make the current server an emoji bank? (y//n)" - ) def check(m): return ( @@ -66,31 +102,50 @@ class StealEmoji(Cog): and m.author == ctx.author ) + already_a_guildbank = ctx.guild.id in (await self.config.guildbanks()) + + if already_a_guildbank: + await ctx.send( + "This is already an emoji bank\n" + "Are you sure you want to remove the current server from the emoji bank list? (y/n)" + ) + else: + await ctx.send( + "This will upload custom emojis to this server\n" + "Are you sure you want to make the current server an emoji bank? (y/n)" + ) + msg = await self.bot.wait_for("message", check=check) - if msg.content in ["N", "NO"]: + if msg.content.upper() in ["N", "NO"]: await ctx.send("Cancelled") return async with self.config.guildbanks() as guildbanks: - guildbanks.append(ctx.guild.id) + if already_a_guildbank: + guildbanks.remove(ctx.guild.id) + else: + guildbanks.append(ctx.guild.id) - await ctx.send("This server has been added as an emoji bank") + if already_a_guildbank: + await ctx.send("This server has been removed from being an emoji bank") + else: + await ctx.send("This server has been added to be an emoji bank") @commands.Cog.listener() async def on_reaction_add(self, reaction: discord.Reaction, user: discord.User): """Event handler for reaction watching""" if not reaction.custom_emoji: - print("Not a custom emoji") + # print("Not a custom emoji") return - if not (await self.config.on()): - print("Collecting is off") + if not self.is_on: + # print("Collecting is off") return - emoji = reaction.emoji + emoji: discord.Emoji = reaction.emoji if emoji in self.bot.emojis: - print("Emoji already in bot.emojis") + # print("Emoji already in bot.emojis") return # This is now a custom emoji that the bot doesn't have access to, time to steal it @@ -99,30 +154,39 @@ class StealEmoji(Cog): guildbank = None banklist = await self.config.guildbanks() for guild_id in banklist: - guild = self.bot.get_guild(guild_id) - if len(guild.emojis) < 50: + guild: discord.Guild = self.bot.get_guild(guild_id) + # if len(guild.emojis) < 50: + if await check_guild(guild, emoji): guildbank = guild break if guildbank is None: - print("No guildbank to store emoji") + # print("No guildbank to store emoji") # Eventually make a new banklist return # Next, have I saved this emoji before (because uploaded emoji != orignal emoji) - stolemojis = await self.config.stolemoji() - - if emoji.id in stolemojis: - print("Emoji has already been stolen") + if str(emoji.id) in await self.config.stolemoji(): + # print("Emoji has already been stolen") return + # stolemojis = await self.config.stolemoji() + # + # print(stolemojis.keys()) + # + # if emoji.id in stolemojis: + # print("Emoji has already been stolen") + # return + # Alright, time to steal it for real # path = urlparse(emoji.url).path # ext = os.path.splitext(path)[1] - async with aiohttp.ClientSession() as session: - img = await fetch_img(session, emoji.url) + # async with aiohttp.ClientSession() as session: + # img = await fetch_img(session, emoji.url) + + img = await emoji.url.read() # path = data_manager.cog_data_path(cog_instance=self) / (emoji.name+ext) @@ -135,20 +199,23 @@ class StealEmoji(Cog): name=emoji.name, image=img, reason="Stole from " + str(user) ) except discord.Forbidden as e: - print("PermissionError - no permission to add emojis") + # print("PermissionError - no permission to add emojis") raise PermissionError("No permission to add emojis") from e except discord.HTTPException as e: - print("HTTPException exception") + # print("HTTPException exception") raise e # Unhandled error # If you get this far, YOU DID IT save_dict = self.default_stolemoji.copy() - e_dict = vars(emoji) + # e_attr_list = [a for a in dir(emoji) if not a.startswith("__")] + + for k in save_dict.keys(): + save_dict[k] = getattr(emoji, k, None) - for k in e_dict: - if k in save_dict: - save_dict[k] = e_dict[k] + # for k in e_attr_list: + # if k in save_dict: + # save_dict[k] = getattr(emoji, k, None) save_dict["guildbank"] = guildbank.id @@ -156,6 +223,12 @@ class StealEmoji(Cog): stolemoji[emoji.id] = save_dict # Enable the below if you want to get notified when it works - # owner = await self.bot.application_info() - # owner = owner.owner - # await owner.send("Just added emoji "+str(emoji)+" to server "+str(guildbank)) + notify_settings = await self.config.notify() + if notify_settings: + if notify_settings == 1: + owner = await self.bot.application_info() + target = owner.owner + else: + target = self.bot.get_channel(notify_settings) + + await target.send(f"Just added emoji {emoji} to server {guildbank}") From 225246be9a545a4c8ed1deaccea4538eed494699 Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 30 Jul 2020 09:59:03 -0400 Subject: [PATCH 022/127] Chatter revamp (#107) * Move to importing the actual library * Chat revamp * Attempt at functioning info * Remove chatterbot as a requirement, install manually * Add chatter README.md Switch to medium spaCy model by default Correct paths for requirements.txt * Add Known Issues to README.md * Forgot about this line. Was it causes reload issue? * Known Issues: Installation * Add some more tags * Adjust requirements * Bump chatter to Beta, ccrole to release, dad to Beta, hangman to Beta, infochannel to Beta * Gotta be medium in requirements as wel * Better conversation grouping and formatting * Random instead of first for more variety Disable custom similarity threshold for now, until it's configurable use filter to avoid looking at empty messages --- README.md | 10 +- chatter/README.md | 151 ++++ chatter/__init__.py | 7 +- chatter/chat.py | 261 ++++-- chatter/chatterbot/__init__.py | 13 - chatter/chatterbot/__main__.py | 22 - chatter/chatterbot/adapters.py | 47 -- chatter/chatterbot/chatterbot.py | 172 ---- chatter/chatterbot/comparisons.py | 325 -------- chatter/chatterbot/constants.py | 15 - chatter/chatterbot/conversation.py | 213 ----- chatter/chatterbot/corpus.py | 10 - chatter/chatterbot/ext/__init__.py | 0 .../chatterbot/ext/sqlalchemy_app/__init__.py | 0 .../chatterbot/ext/sqlalchemy_app/models.py | 131 --- .../chatterbot/ext/sqlalchemy_app/types.py | 16 - chatter/chatterbot/filters.py | 47 -- chatter/chatterbot/input/__init__.py | 17 - chatter/chatterbot/input/gitter.py | 178 ----- chatter/chatterbot/input/hipchat.py | 115 --- chatter/chatterbot/input/input_adapter.py | 34 - chatter/chatterbot/input/mailgun.py | 63 -- chatter/chatterbot/input/microsoft.py | 117 --- chatter/chatterbot/input/terminal.py | 19 - .../input/variable_input_type_adapter.py | 61 -- chatter/chatterbot/logic/__init__.py | 19 - chatter/chatterbot/logic/best_match.py | 85 -- chatter/chatterbot/logic/logic_adapter.py | 101 --- chatter/chatterbot/logic/low_confidence.py | 59 -- .../logic/mathematical_evaluation.py | 68 -- chatter/chatterbot/logic/multi_adapter.py | 155 ---- .../chatterbot/logic/no_knowledge_adapter.py | 27 - chatter/chatterbot/logic/specific_response.py | 39 - chatter/chatterbot/logic/time_adapter.py | 93 --- chatter/chatterbot/output/__init__.py | 15 - chatter/chatterbot/output/gitter.py | 86 -- chatter/chatterbot/output/hipchat.py | 69 -- chatter/chatterbot/output/mailgun.py | 50 -- chatter/chatterbot/output/microsoft.py | 111 --- chatter/chatterbot/output/output_adapter.py | 20 - chatter/chatterbot/output/terminal.py | 17 - chatter/chatterbot/parsing.py | 752 ------------------ chatter/chatterbot/preprocessors.py | 50 -- chatter/chatterbot/response_selection.py | 71 -- chatter/chatterbot/storage/__init__.py | 9 - chatter/chatterbot/storage/mongodb.py | 397 --------- chatter/chatterbot/storage/sql_storage.py | 403 ---------- chatter/chatterbot/storage/storage_adapter.py | 174 ---- chatter/chatterbot/trainers.py | 424 ---------- chatter/chatterbot/utils.py | 199 ----- chatter/info.json | 28 +- chatter/requirements.txt | 12 + 52 files changed, 390 insertions(+), 5187 deletions(-) create mode 100644 chatter/README.md delete mode 100644 chatter/chatterbot/__init__.py delete mode 100644 chatter/chatterbot/__main__.py delete mode 100644 chatter/chatterbot/adapters.py delete mode 100644 chatter/chatterbot/chatterbot.py delete mode 100644 chatter/chatterbot/comparisons.py delete mode 100644 chatter/chatterbot/constants.py delete mode 100644 chatter/chatterbot/conversation.py delete mode 100644 chatter/chatterbot/corpus.py delete mode 100644 chatter/chatterbot/ext/__init__.py delete mode 100644 chatter/chatterbot/ext/sqlalchemy_app/__init__.py delete mode 100644 chatter/chatterbot/ext/sqlalchemy_app/models.py delete mode 100644 chatter/chatterbot/ext/sqlalchemy_app/types.py delete mode 100644 chatter/chatterbot/filters.py delete mode 100644 chatter/chatterbot/input/__init__.py delete mode 100644 chatter/chatterbot/input/gitter.py delete mode 100644 chatter/chatterbot/input/hipchat.py delete mode 100644 chatter/chatterbot/input/input_adapter.py delete mode 100644 chatter/chatterbot/input/mailgun.py delete mode 100644 chatter/chatterbot/input/microsoft.py delete mode 100644 chatter/chatterbot/input/terminal.py delete mode 100644 chatter/chatterbot/input/variable_input_type_adapter.py delete mode 100644 chatter/chatterbot/logic/__init__.py delete mode 100644 chatter/chatterbot/logic/best_match.py delete mode 100644 chatter/chatterbot/logic/logic_adapter.py delete mode 100644 chatter/chatterbot/logic/low_confidence.py delete mode 100644 chatter/chatterbot/logic/mathematical_evaluation.py delete mode 100644 chatter/chatterbot/logic/multi_adapter.py delete mode 100644 chatter/chatterbot/logic/no_knowledge_adapter.py delete mode 100644 chatter/chatterbot/logic/specific_response.py delete mode 100644 chatter/chatterbot/logic/time_adapter.py delete mode 100644 chatter/chatterbot/output/__init__.py delete mode 100644 chatter/chatterbot/output/gitter.py delete mode 100644 chatter/chatterbot/output/hipchat.py delete mode 100644 chatter/chatterbot/output/mailgun.py delete mode 100644 chatter/chatterbot/output/microsoft.py delete mode 100644 chatter/chatterbot/output/output_adapter.py delete mode 100644 chatter/chatterbot/output/terminal.py delete mode 100644 chatter/chatterbot/parsing.py delete mode 100644 chatter/chatterbot/preprocessors.py delete mode 100644 chatter/chatterbot/response_selection.py delete mode 100644 chatter/chatterbot/storage/__init__.py delete mode 100644 chatter/chatterbot/storage/mongodb.py delete mode 100644 chatter/chatterbot/storage/sql_storage.py delete mode 100644 chatter/chatterbot/storage/storage_adapter.py delete mode 100644 chatter/chatterbot/trainers.py delete mode 100644 chatter/chatterbot/utils.py create mode 100644 chatter/requirements.txt diff --git a/README.md b/README.md index 5de2cdd..1570dc9 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,18 @@ Cog Function | --- | --- | --- | | announcedaily | **Alpha** |
Send daily announcements to all servers at a specified timesCommissioned release, so suggestions will not be accepted
| | audiotrivia | **Alpha** |
Guess the audio using the core trivia cogReplaces the core Trivia cog. Needs help adding audio trivia lists, please submit a PR to contribute
| -| ccrole | **Beta** |
Create custom commands that also assign rolesMay have some bugs, please create an issue if you find any
| -| chatter | **Alpha** |
Chat-bot trained to talk like your guildMissing some key features, but currently functional
| +| ccrole | **Release** |
Create custom commands that also assign rolesMay have some bugs, please create an issue if you find any
| +| chatter | **Beta** |
Chat-bot trained to talk like your guildMissing some key features, but currently functional
| | coglint | **Alpha** |
Error check code in python syntax posted to discordWorks, but probably needs more turning to work for cogs
| -| dad | **Alpha** |
Tell dad jokesWorks great!
| +| dad | **Beta** |
Tell dad jokesWorks great!
| | exclusiverole | **Alpha** |
Prevent certain roles from getting any other rolesFully functional, but pretty simple
| | fight | **Incomplete** |
Organize bracket tournaments within discordStill in-progress, a massive project
| | flag | **Alpha** |
Create temporary marks on users that expire after specified timePorted, will not import old data. Please report bugs
| | forcemention | **Alpha** |
Mentions unmentionable rolesVery simple cog, mention doesn't persist
| -| hangman | **Alpha** |
Play a game of hangmanSome visual glitches and needs more customization
| +| hangman | **Beta** |
Play a game of hangmanSome visual glitches and needs more customization
| | howdoi | **Incomplete** |
Ask coding questions and get results from StackExchangeNot yet functional
| | leaver | **Beta** |
Send a message in a channel when a user leaves the serverSeems to be functional, please report any bugs or suggestions
| -| infochannel | **Alpha** |
Create a channel to display server infoJust released, please report bugs
| +| infochannel | **Beta** |
Create a channel to display server infoJust released, please report bugs
| | lovecalculator | **Alpha** |
Calculate the love between two users[Snap-Ons] Just updated to V3
| | lseen | **Alpha** |
Track when a member was last onlineAlpha release, please report bugs
| | nudity | **Incomplete** |
Checks for NSFW images posted in non-NSFW channelsLibrary this is based on has a bug, waiting for author to merge my PR
| diff --git a/chatter/README.md b/chatter/README.md new file mode 100644 index 0000000..9be320e --- /dev/null +++ b/chatter/README.md @@ -0,0 +1,151 @@ +# Chatter + +Chatter is a tool designed to be a self-hosted chat cog. + +It is based on the brilliant work over at [Chatterbot](https://github.com/gunthercox/ChatterBot) and [spaCy](https://github.com/explosion/spaCy) + + +## Known Issues + +* Chatter will not reload + * Causes this error: + ``` + chatterbot.adapters.Adapter.InvalidAdapterTypeException: chatterbot.storage.SQLStorageAdapter must be a subclass of StorageAdapter + ``` +* Chatter responses are slow + * This is an unfortunate side-effect to running self-hosted maching learning on a discord bot. + * This version includes a number of attempts at improving this, but there is only so much that can be done. +* Chatter responses are irrelevant + * This can be caused by bad training, but sometimes the data just doesn't come together right. + * Asking for better accuracy often leads to slower responses as well, so I've leaned towards speed over accuracy. +* Chatter installation is not working + * See installation instructions below + +## Warning + +**Chatter is a CPU, RAM, and Disk intensive cog.** + +Chatter by default uses spaCy's `en_core_web_md` training model, which is ~50 MB + +Chatter can potential use spaCy's `en_core_web_lg` training model, which is ~800 MB + +Chatter uses as sqlite database that can potentially take up a large amount os disk space, +depending on how much training Chatter has done. + +The sqlite database can be safely deleted at any time. Deletion will only erase training data. + + +# Installation +The installation is currently very tricky, and only tested on a Windows Machine. + +There are a number of reasons for this, but the main ones are as follows: +* Using a dev version of chatterbot +* Some chatterbot requirements conflict with Red's (as of 3.10) +* spaCy version is newer than chatterbot's requirements +* A symlink in spacy to map `en` to `en_core_web_sm` requires admin permissions on windows +* C++ Build tools are required on Windows for spaCy +* Pandoc is required for something on windows, but I can't remember what + +## Windows Prerequisites + +Install these on your windows machine before attempting the installation + +[Visual Studio C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) + +[Pandoc - Universal Document Converter](https://pandoc.org/installing.html) + +##Methods +### Windows - Manually +#### Step 1: Built-in Downloader + +You need to get a copy of the requirements.txt provided with chatter, I recommend this method. + +``` +[p]repo add Fox https://github.com/bobloy/Fox-V3 +``` + +#### Step 2: Install Requirements + +In a terminal running as an admin, navigate to the directory containing this repo. + +I've used my install directory as an example. + +``` +cd C:\Users\Bobloy\AppData\Local\Red-DiscordBot\Red-DiscordBot\data\bobbot\cogs\RepoManager\repos\Fox\chatter +pip install -r requirements.txt +pip install --no-deps "chatterbot>=1.1" +``` + +#### Step 3: Load Chatter + +``` +[p]cog install Fox chatter +[p]load chatter +``` + +### Linux - Manually + +Linux installation has not currently been evaluated, but Ubuntu testing is planned. + +# Configuration + +Chatter works out the the box without any training by learning as it goes, +but will have very poor and repetitive responses at first. + +Initial training is recommended to speed up its learning. + +## Training Setup + +### Minutes +``` +[p]chatter minutes X +``` +This command configures what Chatter considers the maximum amount of minutes +that can pass between statements before considering it a new conversation. + +Servers with lots of activity should set this low, where servers with low activity +will want this number to be fairly high. + +This is only used during training. + +### Age + +``` +[p]chatter age X +``` +This command configures the maximum number of days Chatter will look back when +gathering messages for training. + +Setting this to be extremely high is not recommended due to the increased disk space required to store +the data. Additionally, higher numbers will increase the training time tremendously. + + +## Training + +### Train English + +``` +[p]chatter trainenglish +``` + +This will train chatter on basic english greetings and conversations. +This is far from complete, but can act as a good base point for new installations. + +### Train Channel + +``` +[p]chatter train #channel_name +``` +This command trains Chatter on the specified channel based on the configured +settings. This can take a long time to process. + + +## Switching Algorithms + +``` +[p]chatter algorithm X +``` + +Chatter can be configured to use one of three different Similarity algorithms. + +Changing this can help if the response speed is too slow, but can reduce the accuracy of results. \ No newline at end of file diff --git a/chatter/__init__.py b/chatter/__init__.py index cc101b7..9447c6a 100644 --- a/chatter/__init__.py +++ b/chatter/__init__.py @@ -1,4 +1,3 @@ -from . import chatterbot from .chat import Chatter @@ -6,6 +5,6 @@ def setup(bot): bot.add_cog(Chatter(bot)) -__all__ = ( - 'chatterbot' -) +# __all__ = ( +# 'chatterbot' +# ) diff --git a/chatter/chat.py b/chatter/chat.py index 10ab156..fe8c839 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -3,14 +3,25 @@ import pathlib from datetime import datetime, timedelta import discord +from chatterbot import ChatBot +from chatterbot.comparisons import JaccardSimilarity, LevenshteinDistance, SpacySimilarity +from chatterbot.response_selection import get_random_response +from chatterbot.trainers import ChatterBotCorpusTrainer, ListTrainer from redbot.core import Config, commands from redbot.core.commands import Cog from redbot.core.data_manager import cog_data_path -from .chatterbot import ChatBot -from .chatterbot.comparisons import levenshtein_distance -from .chatterbot.response_selection import get_first_response -from .chatterbot.trainers import ListTrainer + +class ENG_LG: # TODO: Add option to use this large model + ISO_639_1 = "en_core_web_lg" + ISO_639 = "eng" + ENGLISH_NAME = "English" + + +class ENG_MD: + ISO_639_1 = "en_core_web_md" + ISO_639 = "eng" + ENGLISH_NAME = "English" class Chatter(Cog): @@ -19,35 +30,38 @@ class Chatter(Cog): """ def __init__(self, bot): + super().__init__() self.bot = bot self.config = Config.get_conf(self, identifier=6710497116116101114) default_global = {} - default_guild = {"whitelist": None, "days": 1} + default_guild = {"whitelist": None, "days": 1, "convo_delta": 15} path: pathlib.Path = cog_data_path(self) - data_path = path / "database.sqlite3" + self.data_path = path / "database.sqlite3" - self.chatbot = ChatBot( - "ChatterBot", - storage_adapter="chatter.chatterbot.storage.SQLStorageAdapter", - database=str(data_path), - statement_comparison_function=levenshtein_distance, - response_selection_method=get_first_response, - logic_adapters=[ - "chatter.chatterbot.logic.BestMatch", - { - "import_path": "chatter.chatterbot.logic.LowConfidenceAdapter", - "threshold": 0.65, - "default_response": ":thinking:", - }, - ], - ) - self.chatbot.set_trainer(ListTrainer) + self.chatbot = self._create_chatbot(self.data_path, SpacySimilarity, 0.45, ENG_MD) + # self.chatbot.set_trainer(ListTrainer) + + # self.trainer = ListTrainer(self.chatbot) self.config.register_global(**default_global) self.config.register_guild(**default_guild) self.loop = asyncio.get_event_loop() + def _create_chatbot( + self, data_path, similarity_algorithm, similarity_threshold, tagger_language + ): + return ChatBot( + "ChatterBot", + storage_adapter="chatterbot.storage.SQLStorageAdapter", + database_uri="sqlite:///" + str(data_path), + statement_comparison_function=similarity_algorithm, + response_selection_method=get_random_response, + logic_adapters=["chatterbot.logic.BestMatch"], + # maximum_similarity_threshold=similarity_threshold, + tagger_language=tagger_language, + ) + async def _get_conversation(self, ctx, in_channel: discord.TextChannel = None): """ Compiles all conversation in the Guild this bot can get it's hands on @@ -56,17 +70,22 @@ class Chatter(Cog): """ out = [[]] after = datetime.today() - timedelta(days=(await self.config.guild(ctx.guild).days())) + convo_delta = timedelta(minutes=(await self.config.guild(ctx.guild).convo_delta())) - def new_message(msg, sent, out_in): - if sent is None: - return False + def predicate(message: discord.Message): + return message.clean_content - if len(out_in) < 2: - return False + def new_conversation(msg, sent, out_in, delta): + # if sent is None: + # return False - return msg.created_at - sent >= timedelta( - hours=3 - ) # This should be configurable perhaps + # Don't do "too short" processing here. Sometimes people don't respond. + # if len(out_in) < 2: + # return False + + # print(msg.created_at - sent) + + return msg.created_at - sent >= delta for channel in ctx.guild.text_channels: if in_channel: @@ -74,18 +93,26 @@ class Chatter(Cog): await ctx.send("Gathering {}".format(channel.mention)) user = None i = 0 - send_time = None + send_time = after - timedelta(days=100) # Makes the first message a new message + try: - async for message in channel.history(limit=None, after=after): + async for message in channel.history( + limit=None, after=after, oldest_first=True + ).filter( + predicate=predicate + ): # type: discord.Message # if message.author.bot: # Skip bot messages # continue - if new_message(message, send_time, out[i]): + if new_conversation(message, send_time, out[i], convo_delta): out.append([]) i += 1 user = None - else: - send_time = message.created_at + timedelta(seconds=1) + + send_time = ( + message.created_at + ) # + timedelta(seconds=1) # Can't remember why I added 1 second + if user == message.author: out[i][-1] += "\n" + message.clean_content else: @@ -102,10 +129,20 @@ class Chatter(Cog): return out + def _train_english(self): + trainer = ChatterBotCorpusTrainer(self.chatbot) + try: + trainer.train("chatterbot.corpus.english") + except: + return False + return True + def _train(self, data): + trainer = ListTrainer(self.chatbot) try: for convo in data: - self.chatbot.train(convo) + if len(convo) > 1: + trainer.train(convo) except: return False return True @@ -118,28 +155,88 @@ class Chatter(Cog): if ctx.invoked_subcommand is None: pass - @chatter.command() + @chatter.command(name="algorithm") + async def chatter_algorithm(self, ctx: commands.Context, algo_number: int): + """ + Switch the active logic algorithm to one of the three. Default after reload is Spacy + + 0: Spacy + 1: Jaccard + 2: Levenshtein + """ + + algos = [(SpacySimilarity, 0.45), (JaccardSimilarity, 0.75), (LevenshteinDistance, 0.75)] + + if algo_number < 0 or algo_number > 2: + await ctx.send_help() + return + + self.chatbot = self._create_chatbot( + self.data_path, algos[algo_number][0], algos[algo_number][1], ENG_MD + ) + + await ctx.tick() + + @chatter.command(name="minutes") + async def minutes(self, ctx: commands.Context, minutes: int): + """ + Sets the number of minutes the bot will consider a break in a conversation during training + Active servers should set a lower number, while less active servers should have a higher number + """ + + if minutes < 1: + await ctx.send_help() + return + + await self.config.guild(ctx.guild).convo_length.set(minutes) + + await ctx.tick() + + @chatter.command(name="age") async def age(self, ctx: commands.Context, days: int): """ Sets the number of days to look back Will train on 1 day otherwise """ + if days < 1: + await ctx.send_help() + return + await self.config.guild(ctx.guild).days.set(days) - await ctx.send("Success") + await ctx.tick() - @chatter.command() + @chatter.command(name="backup") async def backup(self, ctx, backupname): """ Backup your training data to a json for later use """ + await ctx.send("Backing up data, this may take a while") + + path: pathlib.Path = cog_data_path(self) + + trainer = ListTrainer(self.chatbot) + future = await self.loop.run_in_executor( - None, self.chatbot.trainer.export_for_training, "./{}.json".format(backupname) + None, trainer.export_for_training, str(path / f"{backupname}.json") ) if future: - await ctx.send("Backup successful!") + await ctx.send(f"Backup successful! Look in {path} for your backup") + else: + await ctx.send("Error occurred :(") + + @chatter.command(name="trainenglish") + async def chatter_train_english(self, ctx: commands.Context): + """ + Trains the bot in english + """ + async with ctx.typing(): + future = await self.loop.run_in_executor(None, self._train_english) + + if future: + await ctx.send("Training successful!") else: await ctx.send("Error occurred :(") @@ -155,14 +252,16 @@ class Chatter(Cog): "If you experience issues, clear your trained data and train again on a smaller scope." ) - conversation = await self._get_conversation(ctx, channel) + async with ctx.typing(): + conversation = await self._get_conversation(ctx, channel) if not conversation: await ctx.send("Failed to gather training data") return await ctx.send( - "Gather successful! Training begins now\n(**This will take a long time, be patient**)" + "Gather successful! Training begins now\n" + "(**This will take a long time, be patient. See console for progress**)" ) embed = discord.Embed(title="Loading") embed.set_image(url="http://www.loop.universaleverything.com/animations/1295.gif") @@ -171,7 +270,7 @@ class Chatter(Cog): try: await temp_message.delete() - except: + except discord.Forbidden: pass if future: @@ -180,29 +279,71 @@ class Chatter(Cog): await ctx.send("Error occurred :(") @commands.Cog.listener() - async def on_message(self, message: discord.Message): + async def on_message_without_command(self, message: discord.Message): """ Credit to https://github.com/Twentysix26/26-Cogs/blob/master/cleverbot/cleverbot.py for on_message recognition of @bot + + Credit to: + https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/develop/redbot/cogs/customcom/customcom.py#L508 + for the message filtering """ + ########### + is_private = isinstance(message.channel, discord.abc.PrivateChannel) + + # user_allowed check, will be replaced with self.bot.user_allowed or + # something similar once it's added + user_allowed = True + + if len(message.content) < 2 or is_private or not user_allowed or message.author.bot: + return + + ctx: commands.Context = await self.bot.get_context(message) + + if ctx.prefix is not None: + return + + ########### + # Thank you Cog-Creators + + def my_local_get_prefix(prefixes, content): + for p in prefixes: + if content.startswith(p): + return p + return None + + when_mentionables = commands.when_mentioned(self.bot, message) + + prefix = my_local_get_prefix(when_mentionables, message.content) + + if prefix is None: + # print("not mentioned") + return + author = message.author guild: discord.Guild = message.guild channel: discord.TextChannel = message.channel - if author.id != self.bot.user.id: - if guild is None: - to_strip = "@" + channel.me.display_name + " " + # if author.id != self.bot.user.id: + # if guild is None: + # to_strip = "@" + channel.me.display_name + " " + # else: + # to_strip = "@" + guild.me.display_name + " " + # text = message.clean_content + # if not text.startswith(to_strip): + # return + # text = text.replace(to_strip, "", 1) + + # A bit more aggressive, could remove two mentions + # Or might not work at all, since mentionables are pre-cleaned_content + text = message.clean_content + text.replace(prefix, "", 1) + + async with channel.typing(): + future = await self.loop.run_in_executor(None, self.chatbot.get_response, text) + + if future and str(future): + await channel.send(str(future)) else: - to_strip = "@" + guild.me.display_name + " " - text = message.clean_content - if not text.startswith(to_strip): - return - text = text.replace(to_strip, "", 1) - async with channel.typing(): - future = await self.loop.run_in_executor(None, self.chatbot.get_response, text) - - if future and str(future): - await channel.send(str(future)) - else: - await channel.send(":thinking:") + await channel.send(":thinking:") diff --git a/chatter/chatterbot/__init__.py b/chatter/chatterbot/__init__.py deleted file mode 100644 index 7a127ee..0000000 --- a/chatter/chatterbot/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -ChatterBot is a machine learning, conversational dialog engine. -""" -from .chatterbot import ChatBot - -__version__ = '0.8.5' -__author__ = 'Gunther Cox' -__email__ = 'gunthercx@gmail.com' -__url__ = 'https://github.com/gunthercox/ChatterBot' - -__all__ = ( - 'ChatBot', -) diff --git a/chatter/chatterbot/__main__.py b/chatter/chatterbot/__main__.py deleted file mode 100644 index 0322854..0000000 --- a/chatter/chatterbot/__main__.py +++ /dev/null @@ -1,22 +0,0 @@ -import sys - -if __name__ == '__main__': - import importlib - - if '--version' in sys.argv: - chatterbot = importlib.import_module('chatterbot') - print(chatterbot.__version__) - - if 'list_nltk_data' in sys.argv: - import os - import nltk.data - - data_directories = [] - - # Find each data directory in the NLTK path that has content - for path in nltk.data.path: - if os.path.exists(path): - if os.listdir(path): - data_directories.append(path) - - print(os.linesep.join(data_directories)) diff --git a/chatter/chatterbot/adapters.py b/chatter/chatterbot/adapters.py deleted file mode 100644 index 83ce94c..0000000 --- a/chatter/chatterbot/adapters.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging - - -class Adapter(object): - """ - A superclass for all adapter classes. - - :param logger: A python logger. - """ - - def __init__(self, **kwargs): - self.logger = kwargs.get('logger', logging.getLogger(__name__)) - self.chatbot = kwargs.get('chatbot') - - def set_chatbot(self, chatbot): - """ - Gives the adapter access to an instance of the ChatBot class. - - :param chatbot: A chat bot instance. - :type chatbot: ChatBot - """ - self.chatbot = chatbot - - class AdapterMethodNotImplementedError(NotImplementedError): - """ - An exception to be raised when an adapter method has not been implemented. - Typically this indicates that the developer is expected to implement the - method in a subclass. - """ - - def __init__(self, message=None): - """ - Set the message for the esception. - """ - if not message: - message = 'This method must be overridden in a subclass method.' - self.message = message - - def __str__(self): - return self.message - - class InvalidAdapterTypeException(Exception): - """ - An exception to be raised when an adapter - of an unexpected class type is received. - """ - pass diff --git a/chatter/chatterbot/chatterbot.py b/chatter/chatterbot/chatterbot.py deleted file mode 100644 index c99de2c..0000000 --- a/chatter/chatterbot/chatterbot.py +++ /dev/null @@ -1,172 +0,0 @@ -from __future__ import unicode_literals - -import logging - -from . import utils - - -class ChatBot(object): - """ - A conversational dialog chat bot. - """ - - def __init__(self, name, **kwargs): - from .logic import MultiLogicAdapter - - self.name = name - kwargs['name'] = name - kwargs['chatbot'] = self - - self.default_session = None - - storage_adapter = kwargs.get('storage_adapter', 'chatter.chatterbot.storage.SQLStorageAdapter') - - logic_adapters = kwargs.get('logic_adapters', [ - 'chatter.chatterbot.logic.BestMatch' - ]) - - input_adapter = kwargs.get('input_adapter', 'chatter.chatterbot.input.VariableInputTypeAdapter') - - output_adapter = kwargs.get('output_adapter', 'chatter.chatterbot.output.OutputAdapter') - - # Check that each adapter is a valid subclass of it's respective parent - # utils.validate_adapter_class(storage_adapter, StorageAdapter) - # utils.validate_adapter_class(input_adapter, InputAdapter) - # utils.validate_adapter_class(output_adapter, OutputAdapter) - - self.logic = MultiLogicAdapter(**kwargs) - self.storage = utils.initialize_class(storage_adapter, **kwargs) - self.input = utils.initialize_class(input_adapter, **kwargs) - self.output = utils.initialize_class(output_adapter, **kwargs) - - filters = kwargs.get('filters', tuple()) - self.filters = tuple([utils.import_module(F)() for F in filters]) - - # Add required system logic adapter - self.logic.system_adapters.append( - utils.initialize_class('chatter.chatterbot.logic.NoKnowledgeAdapter', **kwargs) - ) - - for adapter in logic_adapters: - self.logic.add_adapter(adapter, **kwargs) - - # Add the chatbot instance to each adapter to share information such as - # the name, the current conversation, or other adapters - self.logic.set_chatbot(self) - self.input.set_chatbot(self) - self.output.set_chatbot(self) - - preprocessors = kwargs.get( - 'preprocessors', [ - 'chatter.chatterbot.preprocessors.clean_whitespace' - ] - ) - - self.preprocessors = [] - - for preprocessor in preprocessors: - self.preprocessors.append(utils.import_module(preprocessor)) - - # Use specified trainer or fall back to the default - trainer = kwargs.get('trainer', 'chatter.chatterbot.trainers.Trainer') - TrainerClass = utils.import_module(trainer) - self.trainer = TrainerClass(self.storage, **kwargs) - self.training_data = kwargs.get('training_data') - - self.default_conversation_id = None - - self.logger = kwargs.get('logger', logging.getLogger(__name__)) - - # Allow the bot to save input it receives so that it can learn - self.read_only = kwargs.get('read_only', False) - - if kwargs.get('initialize', True): - self.initialize() - - def initialize(self): - """ - Do any work that needs to be done before the responses can be returned. - """ - self.logic.initialize() - - def get_response(self, input_item, conversation_id=None): - """ - Return the bot's response based on the input. - - :param input_item: An input value. - :param conversation_id: The id of a conversation. - :returns: A response to the input. - :rtype: Statement - """ - if not conversation_id: - if not self.default_conversation_id: - self.default_conversation_id = self.storage.create_conversation() - conversation_id = self.default_conversation_id - - input_statement = self.input.process_input_statement(input_item) - - # Preprocess the input statement - for preprocessor in self.preprocessors: - input_statement = preprocessor(self, input_statement) - - statement, response = self.generate_response(input_statement, conversation_id) - - # Learn that the user's input was a valid response to the chat bot's previous output - previous_statement = self.storage.get_latest_response(conversation_id) - - if not self.read_only: - self.learn_response(statement, previous_statement) - self.storage.add_to_conversation(conversation_id, statement, response) - - # Process the response output with the output adapter - return self.output.process_response(response, conversation_id) - - def generate_response(self, input_statement, conversation_id): - """ - Return a response based on a given input statement. - """ - self.storage.generate_base_query(self, conversation_id) - - # Select a response to the input statement - response = self.logic.process(input_statement) - - return input_statement, response - - def learn_response(self, statement, previous_statement): - """ - Learn that the statement provided is a valid response. - """ - from .conversation import Response - - if previous_statement: - statement.add_response( - Response(previous_statement.text) - ) - self.logger.info('Adding "{}" as a response to "{}"'.format( - statement.text, - previous_statement.text - )) - - # Save the statement after selecting a response - self.storage.update(statement) - - def set_trainer(self, training_class, **kwargs): - """ - Set the module used to train the chatbot. - - :param training_class: The training class to use for the chat bot. - :type training_class: `Trainer` - - :param \**kwargs: Any parameters that should be passed to the training class. - """ - if 'chatbot' not in kwargs: - kwargs['chatbot'] = self - - self.trainer = training_class(self.storage, **kwargs) - - @property - def train(self): - """ - Proxy method to the chat bot's trainer class. - """ - return self.trainer.train diff --git a/chatter/chatterbot/comparisons.py b/chatter/chatterbot/comparisons.py deleted file mode 100644 index f7ceb8d..0000000 --- a/chatter/chatterbot/comparisons.py +++ /dev/null @@ -1,325 +0,0 @@ -# -*- coding: utf-8 -*- - - -""" -This module contains various text-comparison algorithms -designed to compare one statement to another. -""" - -# Use python-Levenshtein if available -try: - from Levenshtein.StringMatcher import StringMatcher as SequenceMatcher -except ImportError: - from difflib import SequenceMatcher - - -class Comparator: - - def __call__(self, statement_a, statement_b): - return self.compare(statement_a, statement_b) - - def compare(self, statement_a, statement_b): - return 0 - - def get_initialization_functions(self): - """ - Return all initialization methods for the comparison algorithm. - Initialization methods must start with 'initialize_' and - take no parameters. - """ - initialization_methods = [ - ( - method, - getattr(self, method), - ) for method in dir(self) if method.startswith('initialize_') - ] - - return { - key: value for (key, value) in initialization_methods - } - - -class LevenshteinDistance(Comparator): - """ - Compare two statements based on the Levenshtein distance - of each statement's text. - - For example, there is a 65% similarity between the statements - "where is the post office?" and "looking for the post office" - based on the Levenshtein distance algorithm. - """ - - def compare(self, statement, other_statement): - """ - Compare the two input statements. - - :return: The percent of similarity between the text of the statements. - :rtype: float - """ - - # Return 0 if either statement has a falsy text value - if not statement.text or not other_statement.text: - return 0 - - # Get the lowercase version of both strings - - statement_text = str(statement.text.lower()) - other_statement_text = str(other_statement.text.lower()) - - similarity = SequenceMatcher( - None, - statement_text, - other_statement_text - ) - - # Calculate a decimal percent of the similarity - percent = round(similarity.ratio(), 2) - - return percent - - -class SynsetDistance(Comparator): - """ - Calculate the similarity of two statements. - This is based on the total maximum synset similarity between each word in each sentence. - - This algorithm uses the `wordnet`_ functionality of `NLTK`_ to determine the similarity - of two statements based on the path similarity between each token of each statement. - This is essentially an evaluation of the closeness of synonyms. - """ - - def initialize_nltk_wordnet(self): - """ - Download required NLTK corpora if they have not already been downloaded. - """ - from .utils import nltk_download_corpus - - nltk_download_corpus('corpora/wordnet') - - def initialize_nltk_punkt(self): - """ - Download required NLTK corpora if they have not already been downloaded. - """ - from .utils import nltk_download_corpus - - nltk_download_corpus('tokenizers/punkt') - - def initialize_nltk_stopwords(self): - """ - Download required NLTK corpora if they have not already been downloaded. - """ - from .utils import nltk_download_corpus - - nltk_download_corpus('corpora/stopwords') - - def compare(self, statement, other_statement): - """ - Compare the two input statements. - - :return: The percent of similarity between the closest synset distance. - :rtype: float - - .. _wordnet: http://www.nltk.org/howto/wordnet.html - .. _NLTK: http://www.nltk.org/ - """ - from nltk.corpus import wordnet - from nltk import word_tokenize - from . import utils - import itertools - - tokens1 = word_tokenize(statement.text.lower()) - tokens2 = word_tokenize(other_statement.text.lower()) - - # Remove all stop words from the list of word tokens - tokens1 = utils.remove_stopwords(tokens1, language='english') - tokens2 = utils.remove_stopwords(tokens2, language='english') - - # The maximum possible similarity is an exact match - # Because path_similarity returns a value between 0 and 1, - # max_possible_similarity is the number of words in the longer - # of the two input statements. - max_possible_similarity = max( - len(statement.text.split()), - len(other_statement.text.split()) - ) - - max_similarity = 0.0 - - # Get the highest matching value for each possible combination of words - for combination in itertools.product(*[tokens1, tokens2]): - - synset1 = wordnet.synsets(combination[0]) - synset2 = wordnet.synsets(combination[1]) - - if synset1 and synset2: - - # Get the highest similarity for each combination of synsets - for synset in itertools.product(*[synset1, synset2]): - similarity = synset[0].path_similarity(synset[1]) - - if similarity and (similarity > max_similarity): - max_similarity = similarity - - if max_possible_similarity == 0: - return 0 - - return max_similarity / max_possible_similarity - - -class SentimentComparison(Comparator): - """ - Calculate the similarity of two statements based on the closeness of - the sentiment value calculated for each statement. - """ - - def initialize_nltk_vader_lexicon(self): - """ - Download the NLTK vader lexicon for sentiment analysis - that is required for this algorithm to run. - """ - from .utils import nltk_download_corpus - - nltk_download_corpus('sentiment/vader_lexicon') - - def compare(self, statement, other_statement): - """ - Return the similarity of two statements based on - their calculated sentiment values. - - :return: The percent of similarity between the sentiment value. - :rtype: float - """ - from nltk.sentiment.vader import SentimentIntensityAnalyzer - - sentiment_analyzer = SentimentIntensityAnalyzer() - statement_polarity = sentiment_analyzer.polarity_scores(statement.text.lower()) - statement2_polarity = sentiment_analyzer.polarity_scores(other_statement.text.lower()) - - statement_greatest_polarity = 'neu' - statement_greatest_score = -1 - for polarity in sorted(statement_polarity): - if statement_polarity[polarity] > statement_greatest_score: - statement_greatest_polarity = polarity - statement_greatest_score = statement_polarity[polarity] - - statement2_greatest_polarity = 'neu' - statement2_greatest_score = -1 - for polarity in sorted(statement2_polarity): - if statement2_polarity[polarity] > statement2_greatest_score: - statement2_greatest_polarity = polarity - statement2_greatest_score = statement2_polarity[polarity] - - # Check if the polarity if of a different type - if statement_greatest_polarity != statement2_greatest_polarity: - return 0 - - values = [statement_greatest_score, statement2_greatest_score] - difference = max(values) - min(values) - - return 1.0 - difference - - -class JaccardSimilarity(Comparator): - """ - Calculates the similarity of two statements based on the Jaccard index. - - The Jaccard index is composed of a numerator and denominator. - In the numerator, we count the number of items that are shared between the sets. - In the denominator, we count the total number of items across both sets. - Let's say we define sentences to be equivalent if 50% or more of their tokens are equivalent. - Here are two sample sentences: - - The young cat is hungry. - The cat is very hungry. - - When we parse these sentences to remove stopwords, we end up with the following two sets: - - {young, cat, hungry} - {cat, very, hungry} - - In our example above, our intersection is {cat, hungry}, which has count of two. - The union of the sets is {young, cat, very, hungry}, which has a count of four. - Therefore, our `Jaccard similarity index`_ is two divided by four, or 50%. - Given our similarity threshold above, we would consider this to be a match. - - .. _`Jaccard similarity index`: https://en.wikipedia.org/wiki/Jaccard_index - """ - - SIMILARITY_THRESHOLD = 0.5 - - def initialize_nltk_wordnet(self): - """ - Download the NLTK wordnet corpora that is required for this algorithm - to run only if the corpora has not already been downloaded. - """ - from .utils import nltk_download_corpus - - nltk_download_corpus('corpora/wordnet') - - def compare(self, statement, other_statement): - """ - Return the calculated similarity of two - statements based on the Jaccard index. - """ - from nltk.corpus import wordnet - import nltk - import string - - a = statement.text.lower() - b = other_statement.text.lower() - - # Get default English stopwords and extend with punctuation - stopwords = nltk.corpus.stopwords.words('english') - stopwords.extend(string.punctuation) - stopwords.append('') - lemmatizer = nltk.stem.wordnet.WordNetLemmatizer() - - def get_wordnet_pos(pos_tag): - if pos_tag[1].startswith('J'): - return (pos_tag[0], wordnet.ADJ) - elif pos_tag[1].startswith('V'): - return (pos_tag[0], wordnet.VERB) - elif pos_tag[1].startswith('N'): - return (pos_tag[0], wordnet.NOUN) - elif pos_tag[1].startswith('R'): - return (pos_tag[0], wordnet.ADV) - else: - return (pos_tag[0], wordnet.NOUN) - - ratio = 0 - pos_a = map(get_wordnet_pos, nltk.pos_tag(nltk.tokenize.word_tokenize(a))) - pos_b = map(get_wordnet_pos, nltk.pos_tag(nltk.tokenize.word_tokenize(b))) - lemma_a = [ - lemmatizer.lemmatize( - token.strip(string.punctuation), - pos - ) for token, pos in pos_a if pos == wordnet.NOUN and token.strip( - string.punctuation - ) not in stopwords - ] - lemma_b = [ - lemmatizer.lemmatize( - token.strip(string.punctuation), - pos - ) for token, pos in pos_b if pos == wordnet.NOUN and token.strip( - string.punctuation - ) not in stopwords - ] - - # Calculate Jaccard similarity - try: - numerator = len(set(lemma_a).intersection(lemma_b)) - denominator = float(len(set(lemma_a).union(lemma_b))) - ratio = numerator / denominator - except Exception as e: - print('Error', e) - return ratio >= self.SIMILARITY_THRESHOLD - - -# ---------------------------------------- # - - -levenshtein_distance = LevenshteinDistance() -synset_distance = SynsetDistance() -sentiment_comparison = SentimentComparison() -jaccard_similarity = JaccardSimilarity() diff --git a/chatter/chatterbot/constants.py b/chatter/chatterbot/constants.py deleted file mode 100644 index 3a5ae7d..0000000 --- a/chatter/chatterbot/constants.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -ChatterBot constants -""" - -''' -The maximum length of characters that the text of a statement can contain. -This should be enforced on a per-model basis by the data model for each -storage adapter. -''' -STATEMENT_TEXT_MAX_LENGTH = 400 - -# The maximum length of characters that the name of a tag can contain -TAG_NAME_MAX_LENGTH = 50 - -DEFAULT_DJANGO_APP_NAME = 'django_chatterbot' diff --git a/chatter/chatterbot/conversation.py b/chatter/chatterbot/conversation.py deleted file mode 100644 index 52231f8..0000000 --- a/chatter/chatterbot/conversation.py +++ /dev/null @@ -1,213 +0,0 @@ -class StatementMixin(object): - """ - This class has shared methods used to - normalize different statement models. - """ - tags = [] - - def get_tags(self): - """ - Return the list of tags for this statement. - """ - return self.tags - - def add_tags(self, tags): - """ - Add a list of strings to the statement as tags. - """ - for tag in tags: - self.tags.append(tag) - - -class Statement(StatementMixin): - """ - A statement represents a single spoken entity, sentence or - phrase that someone can say. - """ - - def __init__(self, text, **kwargs): - - # Try not to allow non-string types to be passed to statements - try: - text = str(text) - except UnicodeEncodeError: - pass - - self.text = text - self.tags = kwargs.pop('tags', []) - self.in_response_to = kwargs.pop('in_response_to', []) - - self.extra_data = kwargs.pop('extra_data', {}) - - # This is the confidence with which the chat bot believes - # this is an accurate response. This value is set when the - # statement is returned by the chat bot. - self.confidence = 0 - - self.storage = None - - def __str__(self): - return self.text - - def __repr__(self): - return '' % (self.text) - - def __hash__(self): - return hash(self.text) - - def __eq__(self, other): - if not other: - return False - - if isinstance(other, Statement): - return self.text == other.text - - return self.text == other - - def save(self): - """ - Save the statement in the database. - """ - self.storage.update(self) - - def add_extra_data(self, key, value): - """ - This method allows additional data to be stored on the statement object. - - Typically this data is something that pertains just to this statement. - For example, a value stored here might be the tagged parts of speech for - each word in the statement text. - - - key = 'pos_tags' - - value = [('Now', 'RB'), ('for', 'IN'), ('something', 'NN'), ('different', 'JJ')] - - :param key: The key to use in the dictionary of extra data. - :type key: str - - :param value: The value to set for the specified key. - """ - self.extra_data[key] = value - - def add_response(self, response): - """ - Add the response to the list of statements that this statement is in response to. - If the response is already in the list, increment the occurrence count of that response. - - :param response: The response to add. - :type response: `Response` - """ - if not isinstance(response, Response): - raise Statement.InvalidTypeException( - 'A {} was received when a {} instance was expected'.format( - type(response), - type(Response('')) - ) - ) - - updated = False - for index in range(0, len(self.in_response_to)): - if response.text == self.in_response_to[index].text: - self.in_response_to[index].occurrence += 1 - updated = True - - if not updated: - self.in_response_to.append(response) - - def remove_response(self, response_text): - """ - Removes a response from the statement's response list based - on the value of the response text. - - :param response_text: The text of the response to be removed. - :type response_text: str - """ - for response in self.in_response_to: - if response_text == response.text: - self.in_response_to.remove(response) - return True - return False - - def get_response_count(self, statement): - """ - Find the number of times that the statement has been used - as a response to the current statement. - - :param statement: The statement object to get the count for. - :type statement: `Statement` - - :returns: Return the number of times the statement has been used as a response. - :rtype: int - """ - for response in self.in_response_to: - if statement.text == response.text: - return response.occurrence - - return 0 - - def serialize(self): - """ - :returns: A dictionary representation of the statement object. - :rtype: dict - """ - data = {'text': self.text, 'in_response_to': [], 'extra_data': self.extra_data} - - for response in self.in_response_to: - data['in_response_to'].append(response.serialize()) - - return data - - @property - def response_statement_cache(self): - """ - This property is to allow ChatterBot Statement objects to - be swappable with Django Statement models. - """ - return self.in_response_to - - class InvalidTypeException(Exception): - - def __init__(self, value='Received an unexpected value type.'): - self.value = value - - def __str__(self): - return repr(self.value) - - -class Response(object): - """ - A response represents an entity which response to a statement. - """ - - def __init__(self, text, **kwargs): - from datetime import datetime - from dateutil import parser as date_parser - - self.text = text - self.created_at = kwargs.get('created_at', datetime.now()) - self.occurrence = kwargs.get('occurrence', 1) - - if not isinstance(self.created_at, datetime): - self.created_at = date_parser.parse(self.created_at) - - def __str__(self): - return self.text - - def __repr__(self): - return '' % (self.text) - - def __hash__(self): - return hash(self.text) - - def __eq__(self, other): - if not other: - return False - - if isinstance(other, Response): - return self.text == other.text - - return self.text == other - - def serialize(self): - data = {'text': self.text, 'created_at': self.created_at.isoformat(), 'occurrence': self.occurrence} - - return data diff --git a/chatter/chatterbot/corpus.py b/chatter/chatterbot/corpus.py deleted file mode 100644 index 4bf0e4b..0000000 --- a/chatter/chatterbot/corpus.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -Seamlessly import the external chatterbot corpus module. -View the corpus on GitHub at https://github.com/gunthercox/chatterbot-corpus -""" - -from chatterbot_corpus import Corpus - -__all__ = ( - 'Corpus', -) diff --git a/chatter/chatterbot/ext/__init__.py b/chatter/chatterbot/ext/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/chatter/chatterbot/ext/sqlalchemy_app/__init__.py b/chatter/chatterbot/ext/sqlalchemy_app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/chatter/chatterbot/ext/sqlalchemy_app/models.py b/chatter/chatterbot/ext/sqlalchemy_app/models.py deleted file mode 100644 index cf0843f..0000000 --- a/chatter/chatterbot/ext/sqlalchemy_app/models.py +++ /dev/null @@ -1,131 +0,0 @@ -from sqlalchemy import Table, Column, Integer, DateTime, ForeignKey, PickleType -from sqlalchemy.ext.declarative import declared_attr, declarative_base -from sqlalchemy.orm import relationship -from sqlalchemy.sql import func - -from ...constants import TAG_NAME_MAX_LENGTH, STATEMENT_TEXT_MAX_LENGTH -from ...conversation import StatementMixin -from .types import UnicodeString - - -class ModelBase(object): - """ - An augmented base class for SqlAlchemy models. - """ - - @declared_attr - def __tablename__(cls): - """ - Return the lowercase class name as the name of the table. - """ - return cls.__name__.lower() - - id = Column( - Integer, - primary_key=True, - autoincrement=True - ) - - -Base = declarative_base(cls=ModelBase) - -tag_association_table = Table( - 'tag_association', - Base.metadata, - Column('tag_id', Integer, ForeignKey('tag.id')), - Column('statement_id', Integer, ForeignKey('statement.id')) -) - - -class Tag(Base): - """ - A tag that describes a statement. - """ - - name = Column(UnicodeString(TAG_NAME_MAX_LENGTH)) - - -class Statement(Base, StatementMixin): - """ - A Statement represents a sentence or phrase. - """ - - text = Column(UnicodeString(STATEMENT_TEXT_MAX_LENGTH), unique=True) - - tags = relationship( - 'Tag', - secondary=lambda: tag_association_table, - backref='statements' - ) - - extra_data = Column(PickleType) - - in_response_to = relationship( - 'Response', - back_populates='statement_table' - ) - - def get_tags(self): - """ - Return a list of tags for this statement. - """ - return [tag.name for tag in self.tags] - - def get_statement(self): - from ...conversation import Statement as StatementObject - from ...conversation import Response as ResponseObject - - statement = StatementObject( - self.text, - tags=[tag.name for tag in self.tags], - extra_data=self.extra_data - ) - for response in self.in_response_to: - statement.add_response( - ResponseObject(text=response.text, occurrence=response.occurrence) - ) - return statement - - -class Response(Base): - """ - Response, contains responses related to a given statement. - """ - - text = Column(UnicodeString(STATEMENT_TEXT_MAX_LENGTH)) - - created_at = Column( - DateTime(timezone=True), - server_default=func.now() - ) - - occurrence = Column(Integer, default=1) - - statement_text = Column(UnicodeString(STATEMENT_TEXT_MAX_LENGTH), ForeignKey('statement.text')) - - statement_table = relationship( - 'Statement', - back_populates='in_response_to', - cascade='all', - uselist=False - ) - - -conversation_association_table = Table( - 'conversation_association', - Base.metadata, - Column('conversation_id', Integer, ForeignKey('conversation.id')), - Column('statement_id', Integer, ForeignKey('statement.id')) -) - - -class Conversation(Base): - """ - A conversation. - """ - - statements = relationship( - 'Statement', - secondary=lambda: conversation_association_table, - backref='conversations' - ) diff --git a/chatter/chatterbot/ext/sqlalchemy_app/types.py b/chatter/chatterbot/ext/sqlalchemy_app/types.py deleted file mode 100644 index ee9b123..0000000 --- a/chatter/chatterbot/ext/sqlalchemy_app/types.py +++ /dev/null @@ -1,16 +0,0 @@ -from sqlalchemy.types import TypeDecorator, Unicode - - -class UnicodeString(TypeDecorator): - """ - Type for unicode strings. - """ - - impl = Unicode - - def process_bind_param(self, value, dialect): - """ - Coerce Python bytestrings to unicode before - saving them to the database. - """ - return value diff --git a/chatter/chatterbot/filters.py b/chatter/chatterbot/filters.py deleted file mode 100644 index 9a07a09..0000000 --- a/chatter/chatterbot/filters.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -Filters set the base query that gets passed to the storage adapter. -""" - - -class Filter(object): - """ - A base filter object from which all other - filters should be subclassed. - """ - - def filter_selection(self, chatterbot, conversation_id): - """ - Because this is the base filter class, this method just - returns the storage adapter's base query. Other filters - are expected to override this method. - """ - return chatterbot.storage.base_query - - -class RepetitiveResponseFilter(Filter): - """ - A filter that eliminates possibly repetitive responses to prevent - a chat bot from repeating statements that it has recently said. - """ - - def filter_selection(self, chatterbot, conversation_id): - - text_of_recent_responses = [] - - # TODO: Add a larger quantity of response history - latest_response = chatterbot.storage.get_latest_response(conversation_id) - if latest_response: - text_of_recent_responses.append(latest_response.text) - - # Return the query with no changes if there are no statements to exclude - if not text_of_recent_responses: - return super(RepetitiveResponseFilter, self).filter_selection( - chatterbot, - conversation_id - ) - - query = chatterbot.storage.base_query.statement_text_not_in( - text_of_recent_responses - ) - - return query diff --git a/chatter/chatterbot/input/__init__.py b/chatter/chatterbot/input/__init__.py deleted file mode 100644 index 53c53f9..0000000 --- a/chatter/chatterbot/input/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from .input_adapter import InputAdapter -from .gitter import Gitter -from .hipchat import HipChat -from .mailgun import Mailgun -from .microsoft import Microsoft -from .terminal import TerminalAdapter -from .variable_input_type_adapter import VariableInputTypeAdapter - -__all__ = ( - 'InputAdapter', - 'Microsoft', - 'Gitter', - 'HipChat', - 'Mailgun', - 'TerminalAdapter', - 'VariableInputTypeAdapter', -) diff --git a/chatter/chatterbot/input/gitter.py b/chatter/chatterbot/input/gitter.py deleted file mode 100644 index 24e97cd..0000000 --- a/chatter/chatterbot/input/gitter.py +++ /dev/null @@ -1,178 +0,0 @@ -from __future__ import unicode_literals - -from time import sleep - -from ..conversation import Statement -from . import InputAdapter - - -class Gitter(InputAdapter): - """ - An input adapter that allows a ChatterBot instance to get - input statements from a Gitter room. - """ - - def __init__(self, **kwargs): - super(Gitter, self).__init__(**kwargs) - - self.gitter_host = kwargs.get('gitter_host', 'https://api.gitter.im/v1/') - self.gitter_room = kwargs.get('gitter_room') - self.gitter_api_token = kwargs.get('gitter_api_token') - self.only_respond_to_mentions = kwargs.get('gitter_only_respond_to_mentions', True) - self.sleep_time = kwargs.get('gitter_sleep_time', 4) - - authorization_header = 'Bearer {}'.format(self.gitter_api_token) - - self.headers = { - 'Authorization': authorization_header, - 'Content-Type': 'application/json', - 'Accept': 'application/json' - } - - # Join the Gitter room - room_data = self.join_room(self.gitter_room) - self.room_id = room_data.get('id') - - user_data = self.get_user_data() - self.user_id = user_data[0].get('id') - self.username = user_data[0].get('username') - - def _validate_status_code(self, response): - code = response.status_code - if code not in [200, 201]: - raise self.HTTPStatusException('{} status code recieved'.format(code)) - - def join_room(self, room_name): - """ - Join the specified Gitter room. - """ - import requests - - endpoint = '{}rooms'.format(self.gitter_host) - response = requests.post( - endpoint, - headers=self.headers, - json={'uri': room_name} - ) - self.logger.info('{} joining room {}'.format( - response.status_code, endpoint - )) - self._validate_status_code(response) - return response.json() - - def get_user_data(self): - import requests - - endpoint = '{}user'.format(self.gitter_host) - response = requests.get( - endpoint, - headers=self.headers - ) - self.logger.info('{} retrieving user data {}'.format( - response.status_code, endpoint - )) - self._validate_status_code(response) - return response.json() - - def mark_messages_as_read(self, message_ids): - """ - Mark the specified message ids as read. - """ - import requests - - endpoint = '{}user/{}/rooms/{}/unreadItems'.format( - self.gitter_host, self.user_id, self.room_id - ) - response = requests.post( - endpoint, - headers=self.headers, - json={'chat': message_ids} - ) - self.logger.info('{} marking messages as read {}'.format( - response.status_code, endpoint - )) - self._validate_status_code(response) - return response.json() - - def get_most_recent_message(self): - """ - Get the most recent message from the Gitter room. - """ - import requests - - endpoint = '{}rooms/{}/chatMessages?limit=1'.format(self.gitter_host, self.room_id) - response = requests.get( - endpoint, - headers=self.headers - ) - self.logger.info('{} getting most recent message'.format( - response.status_code - )) - self._validate_status_code(response) - data = response.json() - if data: - return data[0] - return None - - def _contains_mention(self, mentions): - for mention in mentions: - if self.username == mention.get('screenName'): - return True - return False - - def should_respond(self, data): - """ - Takes the API response data from a single message. - Returns true if the chat bot should respond. - """ - if data: - unread = data.get('unread', False) - - if self.only_respond_to_mentions: - if unread and self._contains_mention(data['mentions']): - return True - else: - return False - elif unread: - return True - - return False - - def remove_mentions(self, text): - """ - Return a string that has no leading mentions. - """ - import re - text_without_mentions = re.sub(r'@\S+', '', text) - - # Remove consecutive spaces - text_without_mentions = re.sub(' +', ' ', text_without_mentions.strip()) - - return text_without_mentions - - def process_input(self, statement): - new_message = False - - while not new_message: - data = self.get_most_recent_message() - if self.should_respond(data): - self.mark_messages_as_read([data['id']]) - new_message = True - sleep(self.sleep_time) - - text = self.remove_mentions(data['text']) - statement = Statement(text) - - return statement - - class HTTPStatusException(Exception): - """ - Exception raised when unexpected non-success HTTP - status codes are returned in a response. - """ - - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) diff --git a/chatter/chatterbot/input/hipchat.py b/chatter/chatterbot/input/hipchat.py deleted file mode 100644 index bfcb06a..0000000 --- a/chatter/chatterbot/input/hipchat.py +++ /dev/null @@ -1,115 +0,0 @@ -from __future__ import unicode_literals - -from time import sleep - -from ..conversation import Statement -from . import InputAdapter - - -class HipChat(InputAdapter): - """ - An input adapter that allows a ChatterBot instance to get - input statements from a HipChat room. - """ - - def __init__(self, **kwargs): - super(HipChat, self).__init__(**kwargs) - - self.hipchat_host = kwargs.get('hipchat_host') - self.hipchat_access_token = kwargs.get('hipchat_access_token') - self.hipchat_room = kwargs.get('hipchat_room') - self.session_id = str(self.chatbot.default_session.uuid) - - import requests - self.session = requests.Session() - self.session.verify = kwargs.get('ssl_verify', True) - - authorization_header = 'Bearer {}'.format(self.hipchat_access_token) - - self.headers = { - 'Authorization': authorization_header, - 'Content-Type': 'application/json' - } - - # This is a list of the messages that have been responded to - self.recent_message_ids = self.get_initial_ids() - - def get_initial_ids(self): - """ - Returns a list of the most recent message ids. - """ - data = self.view_recent_room_history( - self.hipchat_room, - max_results=75 - ) - - results = set() - - for item in data['items']: - results.add(item['id']) - - return results - - def view_recent_room_history(self, room_id_or_name, max_results=1): - """ - https://www.hipchat.com/docs/apiv2/method/view_recent_room_history - """ - - recent_histroy_url = '{}/v2/room/{}/history?max-results={}'.format( - self.hipchat_host, - room_id_or_name, - max_results - ) - - response = self.session.get( - recent_histroy_url, - headers=self.headers - ) - - return response.json() - - def get_most_recent_message(self, room_id_or_name): - """ - Return the most recent message from the HipChat room. - """ - data = self.view_recent_room_history(room_id_or_name) - - items = data['items'] - - if not items: - return None - return items[-1] - - def process_input(self, statement): - """ - Process input from the HipChat room. - """ - new_message = False - - response_statement = self.chatbot.storage.get_latest_response( - self.session_id - ) - - if response_statement: - last_message_id = response_statement.extra_data.get( - 'hipchat_message_id', None - ) - if last_message_id: - self.recent_message_ids.add(last_message_id) - - while not new_message: - data = self.get_most_recent_message(self.hipchat_room) - - if data and data['id'] not in self.recent_message_ids: - self.recent_message_ids.add(data['id']) - new_message = True - else: - pass - sleep(3.5) - - text = data['message'] - - statement = Statement(text) - statement.add_extra_data('hipchat_message_id', data['id']) - - return statement diff --git a/chatter/chatterbot/input/input_adapter.py b/chatter/chatterbot/input/input_adapter.py deleted file mode 100644 index 1785b1f..0000000 --- a/chatter/chatterbot/input/input_adapter.py +++ /dev/null @@ -1,34 +0,0 @@ -from __future__ import unicode_literals - -from ..adapters import Adapter - - -class InputAdapter(Adapter): - """ - This is an abstract class that represents the - interface that all input adapters should implement. - """ - - def process_input(self, *args, **kwargs): - """ - Returns a statement object based on the input source. - """ - raise self.AdapterMethodNotImplementedError() - - def process_input_statement(self, *args, **kwargs): - """ - Return an existing statement object (if one exists). - """ - input_statement = self.process_input(*args, **kwargs) - - self.logger.info('Received input statement: {}'.format(input_statement.text)) - - existing_statement = self.chatbot.storage.find(input_statement.text) - - if existing_statement: - self.logger.info('"{}" is a known statement'.format(input_statement.text)) - input_statement = existing_statement - else: - self.logger.info('"{}" is not a known statement'.format(input_statement.text)) - - return input_statement diff --git a/chatter/chatterbot/input/mailgun.py b/chatter/chatterbot/input/mailgun.py deleted file mode 100644 index 6de09d7..0000000 --- a/chatter/chatterbot/input/mailgun.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import unicode_literals - -import datetime - -from ..conversation import Statement -from . import InputAdapter - - -class Mailgun(InputAdapter): - """ - Get input from Mailgun. - """ - - def __init__(self, **kwargs): - super(Mailgun, self).__init__(**kwargs) - - # Use the bot's name for the name of the sender - self.name = kwargs.get('name') - self.from_address = kwargs.get('mailgun_from_address') - self.api_key = kwargs.get('mailgun_api_key') - self.endpoint = kwargs.get('mailgun_api_endpoint') - - def get_email_stored_events(self): - import requests - - yesterday = datetime.datetime.now() - datetime.timedelta(1) - return requests.get( - '{}/events'.format(self.endpoint), - auth=('api', self.api_key), - params={ - 'begin': yesterday.isoformat(), - 'ascending': 'yes', - 'limit': 1 - } - ) - - def get_stored_email_urls(self): - response = self.get_email_stored_events() - data = response.json() - - for item in data.get('items', []): - if 'storage' in item: - if 'url' in item['storage']: - yield item['storage']['url'] - - def get_message(self, url): - import requests - - return requests.get( - url, - auth=('api', self.api_key) - ) - - def process_input(self, statement): - urls = self.get_stored_email_urls() - url = list(urls)[0] - - response = self.get_message(url) - message = response.json() - - text = message.get('stripped-text') - - return Statement(text) diff --git a/chatter/chatterbot/input/microsoft.py b/chatter/chatterbot/input/microsoft.py deleted file mode 100644 index 7a9d446..0000000 --- a/chatter/chatterbot/input/microsoft.py +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import unicode_literals - -from time import sleep - -from ..conversation import Statement -from . import InputAdapter - - -class Microsoft(InputAdapter): - """ - An input adapter that allows a ChatterBot instance to get - input statements from a Microsoft Bot using *Directline client protocol*. - https://docs.botframework.com/en-us/restapi/directline/#navtitle - """ - - def __init__(self, **kwargs): - super(Microsoft, self).__init__(**kwargs) - import requests - from requests.packages.urllib3.exceptions import InsecureRequestWarning - requests.packages.urllib3.disable_warnings(InsecureRequestWarning) - - self.directline_host = kwargs.get('directline_host', 'https://directline.botframework.com') - - # NOTE: Direct Line client credentials are different from your bot's - # credentials - self.direct_line_token_or_secret = kwargs. \ - get('direct_line_token_or_secret') - - authorization_header = 'BotConnector {}'. \ - format(self.direct_line_token_or_secret) - - self.headers = { - 'Authorization': authorization_header, - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'charset': 'utf-8' - } - - conversation_data = self.start_conversation() - self.conversation_id = conversation_data.get('conversationId') - self.conversation_token = conversation_data.get('token') - - def _validate_status_code(self, response): - code = response.status_code - if not code == 200: - raise self.HTTPStatusException('{} status code recieved'. - format(code)) - - def start_conversation(self): - import requests - - endpoint = '{host}/api/conversations'.format(host=self.directline_host) - response = requests.post( - endpoint, - headers=self.headers, - verify=False - ) - self.logger.info('{} starting conversation {}'.format( - response.status_code, endpoint - )) - self._validate_status_code(response) - return response.json() - - def get_most_recent_message(self): - import requests - - endpoint = '{host}/api/conversations/{id}/messages' \ - .format(host=self.directline_host, - id=self.conversation_id) - - response = requests.get( - endpoint, - headers=self.headers, - verify=False - ) - - self.logger.info('{} retrieving most recent messages {}'.format( - response.status_code, endpoint - )) - - self._validate_status_code(response) - - data = response.json() - - if data['messages']: - last_msg = int(data['watermark']) - return data['messages'][last_msg - 1] - return None - - def process_input(self, statement): - new_message = False - data = None - while not new_message: - data = self.get_most_recent_message() - if data and data['id']: - new_message = True - else: - pass - sleep(3.5) - - text = data['text'] - statement = Statement(text) - self.logger.info('processing user statement {}'.format(statement)) - - return statement - - class HTTPStatusException(Exception): - """ - Exception raised when unexpected non-success HTTP - status codes are returned in a response. - """ - - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) diff --git a/chatter/chatterbot/input/terminal.py b/chatter/chatterbot/input/terminal.py deleted file mode 100644 index 582060d..0000000 --- a/chatter/chatterbot/input/terminal.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals - -from ..conversation import Statement -from . import InputAdapter -from ..utils import input_function - - -class TerminalAdapter(InputAdapter): - """ - A simple adapter that allows ChatterBot to - communicate through the terminal. - """ - - def process_input(self, *args, **kwargs): - """ - Read the user's input from the terminal. - """ - user_input = input_function() - return Statement(user_input) diff --git a/chatter/chatterbot/input/variable_input_type_adapter.py b/chatter/chatterbot/input/variable_input_type_adapter.py deleted file mode 100644 index dda8ef3..0000000 --- a/chatter/chatterbot/input/variable_input_type_adapter.py +++ /dev/null @@ -1,61 +0,0 @@ -from __future__ import unicode_literals - -from ..conversation import Statement -from . import InputAdapter - - -class VariableInputTypeAdapter(InputAdapter): - JSON = 'json' - TEXT = 'text' - OBJECT = 'object' - VALID_FORMATS = (JSON, TEXT, OBJECT,) - - def detect_type(self, statement): - - string_types = str - - if hasattr(statement, 'text'): - return self.OBJECT - if isinstance(statement, string_types): - return self.TEXT - if isinstance(statement, dict): - return self.JSON - - input_type = type(statement) - - raise self.UnrecognizedInputFormatException( - 'The type {} is not recognized as a valid input type.'.format( - input_type - ) - ) - - def process_input(self, statement): - input_type = self.detect_type(statement) - - # Return the statement object without modification - if input_type == self.OBJECT: - return statement - - # Convert the input string into a statement object - if input_type == self.TEXT: - return Statement(statement) - - # Convert input dictionary into a statement object - if input_type == self.JSON: - input_json = dict(statement) - text = input_json['text'] - del input_json['text'] - - return Statement(text, **input_json) - - class UnrecognizedInputFormatException(Exception): - """ - Exception raised when an input format is specified that is - not in the VariableInputTypeAdapter.VALID_FORMATS variable. - """ - - def __init__(self, value='The input format was not recognized.'): - self.value = value - - def __str__(self): - return repr(self.value) diff --git a/chatter/chatterbot/logic/__init__.py b/chatter/chatterbot/logic/__init__.py deleted file mode 100644 index 8a6cc97..0000000 --- a/chatter/chatterbot/logic/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -from .logic_adapter import LogicAdapter -from .best_match import BestMatch -from .low_confidence import LowConfidenceAdapter -from .mathematical_evaluation import MathematicalEvaluation -from .multi_adapter import MultiLogicAdapter -from .no_knowledge_adapter import NoKnowledgeAdapter -from .specific_response import SpecificResponseAdapter -from .time_adapter import TimeLogicAdapter - -__all__ = ( - 'LogicAdapter', - 'BestMatch', - 'LowConfidenceAdapter', - 'MathematicalEvaluation', - 'MultiLogicAdapter', - 'NoKnowledgeAdapter', - 'SpecificResponseAdapter', - 'TimeLogicAdapter', -) diff --git a/chatter/chatterbot/logic/best_match.py b/chatter/chatterbot/logic/best_match.py deleted file mode 100644 index 05b3863..0000000 --- a/chatter/chatterbot/logic/best_match.py +++ /dev/null @@ -1,85 +0,0 @@ -from __future__ import unicode_literals - -from . import LogicAdapter - - -class BestMatch(LogicAdapter): - """ - A logic adapter that returns a response based on known responses to - the closest matches to the input statement. - """ - - def get(self, input_statement): - """ - Takes a statement string and a list of statement strings. - Returns the closest matching statement from the list. - """ - statement_list = self.chatbot.storage.get_response_statements() - - if not statement_list: - if self.chatbot.storage.count(): - # Use a randomly picked statement - self.logger.info( - 'No statements have known responses. ' + - 'Choosing a random response to return.' - ) - random_response = self.chatbot.storage.get_random() - random_response.confidence = 0 - return random_response - else: - raise self.EmptyDatasetException() - - closest_match = input_statement - closest_match.confidence = 0 - - # Find the closest matching known statement - for statement in statement_list: - confidence = self.compare_statements(input_statement, statement) - - if confidence > closest_match.confidence: - statement.confidence = confidence - closest_match = statement - - return closest_match - - def can_process(self, statement): - """ - Check that the chatbot's storage adapter is available to the logic - adapter and there is at least one statement in the database. - """ - return self.chatbot.storage.count() - - def process(self, input_statement): - - # Select the closest match to the input statement - closest_match = self.get(input_statement) - self.logger.info('Using "{}" as a close match to "{}"'.format( - input_statement.text, closest_match.text - )) - - # Get all statements that are in response to the closest match - response_list = self.chatbot.storage.filter( - in_response_to__contains=closest_match.text - ) - - if response_list: - self.logger.info( - 'Selecting response from {} optimal responses.'.format( - len(response_list) - ) - ) - response = self.select_response(input_statement, response_list) - response.confidence = closest_match.confidence - self.logger.info('Response selected. Using "{}"'.format(response.text)) - else: - response = self.chatbot.storage.get_random() - self.logger.info( - 'No response to "{}" found. Selecting a random response.'.format( - closest_match.text - ) - ) - - # Set confidence to zero because a random response is selected - response.confidence = 0 - - return response diff --git a/chatter/chatterbot/logic/logic_adapter.py b/chatter/chatterbot/logic/logic_adapter.py deleted file mode 100644 index 0a2e359..0000000 --- a/chatter/chatterbot/logic/logic_adapter.py +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import unicode_literals - -from ..adapters import Adapter -from ..utils import import_module - - -class LogicAdapter(Adapter): - """ - This is an abstract class that represents the interface - that all logic adapters should implement. - - :param statement_comparison_function: The dot-notated import path to a statement comparison function. - Defaults to ``levenshtein_distance``. - - :param response_selection_method: The a response selection method. - Defaults to ``get_first_response``. - """ - - def __init__(self, **kwargs): - super(LogicAdapter, self).__init__(**kwargs) - from ..comparisons import levenshtein_distance - from ..response_selection import get_first_response - - # Import string module parameters - if 'statement_comparison_function' in kwargs: - import_path = kwargs.get('statement_comparison_function') - if isinstance(import_path, str): - kwargs['statement_comparison_function'] = import_module(import_path) - - if 'response_selection_method' in kwargs: - import_path = kwargs.get('response_selection_method') - if isinstance(import_path, str): - kwargs['response_selection_method'] = import_module(import_path) - - # By default, compare statements using Levenshtein distance - self.compare_statements = kwargs.get( - 'statement_comparison_function', - levenshtein_distance - ) - - # By default, select the first available response - self.select_response = kwargs.get( - 'response_selection_method', - get_first_response - ) - - def get_initialization_functions(self): - """ - Return a dictionary of functions to be run once when the chat bot is instantiated. - """ - return self.compare_statements.get_initialization_functions() - - def initialize(self): - for function in self.get_initialization_functions().values(): - function() - - def can_process(self, statement): - """ - A preliminary check that is called to determine if a - logic adapter can process a given statement. By default, - this method returns true but it can be overridden in - child classes as needed. - - :rtype: bool - """ - return True - - def process(self, statement): - """ - Override this method and implement your logic for selecting a response to an input statement. - - A confidence value and the selected response statement should be returned. - The confidence value represents a rating of how accurate the logic adapter - expects the selected response to be. Confidence scores are used to select - the best response from multiple logic adapters. - - The confidence value should be a number between 0 and 1 where 0 is the - lowest confidence level and 1 is the highest. - - :param statement: An input statement to be processed by the logic adapter. - :type statement: Statement - - :rtype: Statement - """ - raise self.AdapterMethodNotImplementedError() - - @property - def class_name(self): - """ - Return the name of the current logic adapter class. - This is typically used for logging and debugging. - """ - return str(self.__class__.__name__) - - class EmptyDatasetException(Exception): - - def __init__(self, value='An empty set was received when at least one statement was expected.'): - self.value = value - - def __str__(self): - return repr(self.value) diff --git a/chatter/chatterbot/logic/low_confidence.py b/chatter/chatterbot/logic/low_confidence.py deleted file mode 100644 index a07d9af..0000000 --- a/chatter/chatterbot/logic/low_confidence.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import unicode_literals - -from ..conversation import Statement -from . import BestMatch - - -class LowConfidenceAdapter(BestMatch): - """ - Returns a default response with a high confidence - when a high confidence response is not known. - - :kwargs: - * *threshold* (``float``) -- - The low confidence value that triggers this adapter. - Defaults to 0.65. - * *default_response* (``str``) or (``iterable``)-- - The response returned by this logic adaper. - * *response_selection_method* (``str``) or (``callable``) - The a response selection method. - Defaults to ``get_first_response``. - """ - - def __init__(self, **kwargs): - super(LowConfidenceAdapter, self).__init__(**kwargs) - - self.confidence_threshold = kwargs.get('threshold', 0.65) - - default_responses = kwargs.get( - 'default_response', "I'm sorry, I do not understand." - ) - - # Convert a single string into a list - if isinstance(default_responses, str): - default_responses = [ - default_responses - ] - - self.default_responses = [ - Statement(text=default) for default in default_responses - ] - - def process(self, input_statement): - """ - Return a default response with a high confidence if - a high confidence response is not known. - """ - # Select the closest match to the input statement - closest_match = self.get(input_statement) - - # Choose a response from the list of options - response = self.select_response(input_statement, self.default_responses) - - # Confidence should be high only if it is less than the threshold - if closest_match.confidence < self.confidence_threshold: - response.confidence = 1 - else: - response.confidence = 0 - - return response diff --git a/chatter/chatterbot/logic/mathematical_evaluation.py b/chatter/chatterbot/logic/mathematical_evaluation.py deleted file mode 100644 index b720e10..0000000 --- a/chatter/chatterbot/logic/mathematical_evaluation.py +++ /dev/null @@ -1,68 +0,0 @@ -from __future__ import unicode_literals - -from ..conversation import Statement -from . import LogicAdapter - - -class MathematicalEvaluation(LogicAdapter): - """ - The MathematicalEvaluation logic adapter parses input to determine - whether the user is asking a question that requires math to be done. - If so, the equation is extracted from the input and returned with - the evaluated result. - - For example: - User: 'What is three plus five?' - Bot: 'Three plus five equals eight' - - :kwargs: - * *language* (``str``) -- - The language is set to 'ENG' for English by default. - """ - - def __init__(self, **kwargs): - super(MathematicalEvaluation, self).__init__(**kwargs) - - self.language = kwargs.get('language', 'ENG') - self.cache = {} - - def can_process(self, statement): - """ - Determines whether it is appropriate for this - adapter to respond to the user input. - """ - response = self.process(statement) - self.cache[statement.text] = response - return response.confidence == 1 - - def process(self, statement): - """ - Takes a statement string. - Returns the equation from the statement with the mathematical terms solved. - """ - from mathparse import mathparse - - input_text = statement.text - - # Use the result cached by the process method if it exists - if input_text in self.cache: - cached_result = self.cache[input_text] - self.cache = {} - return cached_result - - # Getting the mathematical terms within the input statement - expression = mathparse.extract_expression(input_text, language=self.language) - - response = Statement(text=expression) - - try: - response.text += ' = ' + str( - mathparse.parse(expression, language=self.language) - ) - - # The confidence is 1 if the expression could be evaluated - response.confidence = 1 - except mathparse.PostfixTokenEvaluationException: - response.confidence = 0 - - return response diff --git a/chatter/chatterbot/logic/multi_adapter.py b/chatter/chatterbot/logic/multi_adapter.py deleted file mode 100644 index 97a1958..0000000 --- a/chatter/chatterbot/logic/multi_adapter.py +++ /dev/null @@ -1,155 +0,0 @@ -from __future__ import unicode_literals - -from collections import Counter - -from .. import utils -from . import LogicAdapter - - -class MultiLogicAdapter(LogicAdapter): - """ - MultiLogicAdapter allows ChatterBot to use multiple logic - adapters. It has methods that allow ChatterBot to add an - adapter, set the chat bot, and process an input statement - to get a response. - """ - - def __init__(self, **kwargs): - super(MultiLogicAdapter, self).__init__(**kwargs) - - # Logic adapters added by the chat bot - self.adapters = [] - - # Required logic adapters that must always be present - self.system_adapters = [] - - def get_initialization_functions(self): - """ - Get the initialization functions for each logic adapter. - """ - functions_dict = {} - - # Iterate over each adapter and get its initialization functions - for logic_adapter in self.get_adapters(): - functions = logic_adapter.get_initialization_functions() - functions_dict.update(functions) - - return functions_dict - - def process(self, statement): - """ - Returns the output of a selection of logic adapters - for a given input statement. - - :param statement: The input statement to be processed. - """ - results = [] - result = None - max_confidence = -1 - - for adapter in self.get_adapters(): - if adapter.can_process(statement): - - output = adapter.process(statement) - results.append((output.confidence, output,)) - - self.logger.info( - '{} selected "{}" as a response with a confidence of {}'.format( - adapter.class_name, output.text, output.confidence - ) - ) - - if output.confidence > max_confidence: - result = output - max_confidence = output.confidence - else: - self.logger.info( - 'Not processing the statement using {}'.format(adapter.class_name) - ) - - # If multiple adapters agree on the same statement, - # then that statement is more likely to be the correct response - if len(results) >= 3: - statements = [s[1] for s in results] - count = Counter(statements) - most_common = count.most_common() - if most_common[0][1] > 1: - result = most_common[0][0] - max_confidence = self.get_greatest_confidence(result, results) - - result.confidence = max_confidence - return result - - def get_greatest_confidence(self, statement, options): - """ - Returns the greatest confidence value for a statement that occurs - multiple times in the set of options. - - :param statement: A statement object. - :param options: A tuple in the format of (confidence, statement). - """ - values = [] - for option in options: - if option[1] == statement: - values.append(option[0]) - - return max(values) - - def get_adapters(self): - """ - Return a list of all logic adapters being used, including system logic adapters. - """ - adapters = [] - adapters.extend(self.adapters) - adapters.extend(self.system_adapters) - return adapters - - def add_adapter(self, adapter, **kwargs): - """ - Appends a logic adapter to the list of logic adapters being used. - - :param adapter: The logic adapter to be added. - :type adapter: `LogicAdapter` - """ - utils.validate_adapter_class(adapter, LogicAdapter) - adapter = utils.initialize_class(adapter, **kwargs) - self.adapters.append(adapter) - - def insert_logic_adapter(self, logic_adapter, insert_index, **kwargs): - """ - Adds a logic adapter at a specified index. - - :param logic_adapter: The string path to the logic adapter to add. - :type logic_adapter: str - - :param insert_index: The index to insert the logic adapter into the list at. - :type insert_index: int - """ - utils.validate_adapter_class(logic_adapter, LogicAdapter) - - NewAdapter = utils.import_module(logic_adapter) - adapter = NewAdapter(**kwargs) - - self.adapters.insert(insert_index, adapter) - - def remove_logic_adapter(self, adapter_name): - """ - Removes a logic adapter from the chat bot. - - :param adapter_name: The class name of the adapter to remove. - :type adapter_name: str - """ - for index, adapter in enumerate(self.adapters): - if adapter_name == type(adapter).__name__: - del self.adapters[index] - return True - return False - - def set_chatbot(self, chatbot): - """ - Set the chatbot for each of the contained logic adapters. - """ - super(MultiLogicAdapter, self).set_chatbot(chatbot) - - for adapter in self.get_adapters(): - adapter.set_chatbot(chatbot) diff --git a/chatter/chatterbot/logic/no_knowledge_adapter.py b/chatter/chatterbot/logic/no_knowledge_adapter.py deleted file mode 100644 index 4811f75..0000000 --- a/chatter/chatterbot/logic/no_knowledge_adapter.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import unicode_literals - -from . import LogicAdapter - - -class NoKnowledgeAdapter(LogicAdapter): - """ - This is a system adapter that is automatically added - to the list of logic adapters during initialization. - This adapter is placed at the beginning of the list - to be given the highest priority. - """ - - def process(self, statement): - """ - If there are no known responses in the database, - then a confidence of 1 should be returned with - the input statement. - Otherwise, a confidence of 0 should be returned. - """ - - if self.chatbot.storage.count(): - statement.confidence = 0 - else: - statement.confidence = 1 - - return statement diff --git a/chatter/chatterbot/logic/specific_response.py b/chatter/chatterbot/logic/specific_response.py deleted file mode 100644 index bdf71fa..0000000 --- a/chatter/chatterbot/logic/specific_response.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import unicode_literals - -from . import LogicAdapter - - -class SpecificResponseAdapter(LogicAdapter): - """ - Return a specific response to a specific input. - - :kwargs: - * *input_text* (``str``) -- - The input text that triggers this logic adapter. - * *output_text* (``str``) -- - The output text returned by this logic adapter. - """ - - def __init__(self, **kwargs): - super(SpecificResponseAdapter, self).__init__(**kwargs) - from ..conversation import Statement - - self.input_text = kwargs.get('input_text') - - output_text = kwargs.get('output_text') - self.response_statement = Statement(output_text) - - def can_process(self, statement): - if statement == self.input_text: - return True - - return False - - def process(self, statement): - - if statement == self.input_text: - self.response_statement.confidence = 1 - else: - self.response_statement.confidence = 0 - - return self.response_statement diff --git a/chatter/chatterbot/logic/time_adapter.py b/chatter/chatterbot/logic/time_adapter.py deleted file mode 100644 index 78f4a2a..0000000 --- a/chatter/chatterbot/logic/time_adapter.py +++ /dev/null @@ -1,93 +0,0 @@ -from __future__ import unicode_literals - -from datetime import datetime - -from . import LogicAdapter - - -class TimeLogicAdapter(LogicAdapter): - """ - The TimeLogicAdapter returns the current time. - - :kwargs: - * *positive* (``list``) -- - The time-related questions used to identify time questions. - Defaults to a list of English sentences. - * *negative* (``list``) -- - The non-time-related questions used to identify time questions. - Defaults to a list of English sentences. - """ - - def __init__(self, **kwargs): - super(TimeLogicAdapter, self).__init__(**kwargs) - from nltk import NaiveBayesClassifier - - self.positive = kwargs.get('positive', [ - 'what time is it', - 'hey what time is it', - 'do you have the time', - 'do you know the time', - 'do you know what time it is', - 'what is the time' - ]) - - self.negative = kwargs.get('negative', [ - 'it is time to go to sleep', - 'what is your favorite color', - 'i had a great time', - 'thyme is my favorite herb', - 'do you have time to look at my essay', - 'how do you have the time to do all this' - 'what is it' - ]) - - labeled_data = ( - [(name, 0) for name in self.negative] + - [(name, 1) for name in self.positive] - ) - - train_set = [ - (self.time_question_features(text), n) for (text, n) in labeled_data - ] - - self.classifier = NaiveBayesClassifier.train(train_set) - - def time_question_features(self, text): - """ - Provide an analysis of significant features in the string. - """ - features = {} - - # A list of all words from the known sentences - all_words = " ".join(self.positive + self.negative).split() - - # A list of the first word in each of the known sentence - all_first_words = [] - for sentence in self.positive + self.negative: - all_first_words.append( - sentence.split(' ', 1)[0] - ) - - for word in text.split(): - features['first_word({})'.format(word)] = (word in all_first_words) - - for word in text.split(): - features['contains({})'.format(word)] = (word in all_words) - - for letter in 'abcdefghijklmnopqrstuvwxyz': - features['count({})'.format(letter)] = text.lower().count(letter) - features['has({})'.format(letter)] = (letter in text.lower()) - - return features - - def process(self, statement): - from ..conversation import Statement - - now = datetime.now() - - time_features = self.time_question_features(statement.text.lower()) - confidence = self.classifier.classify(time_features) - response = Statement('The current time is ' + now.strftime('%I:%M %p')) - - response.confidence = confidence - return response diff --git a/chatter/chatterbot/output/__init__.py b/chatter/chatterbot/output/__init__.py deleted file mode 100644 index 52c3534..0000000 --- a/chatter/chatterbot/output/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from .output_adapter import OutputAdapter -from .gitter import Gitter -from .hipchat import HipChat -from .mailgun import Mailgun -from .microsoft import Microsoft -from .terminal import TerminalAdapter - -__all__ = ( - 'OutputAdapter', - 'Microsoft', - 'TerminalAdapter', - 'Mailgun', - 'Gitter', - 'HipChat', -) diff --git a/chatter/chatterbot/output/gitter.py b/chatter/chatterbot/output/gitter.py deleted file mode 100644 index ff5db8b..0000000 --- a/chatter/chatterbot/output/gitter.py +++ /dev/null @@ -1,86 +0,0 @@ -from __future__ import unicode_literals - -from . import OutputAdapter - - -class Gitter(OutputAdapter): - """ - An output adapter that allows a ChatterBot instance to send - responses to a Gitter room. - """ - - def __init__(self, **kwargs): - super(Gitter, self).__init__(**kwargs) - - self.gitter_host = kwargs.get('gitter_host', 'https://api.gitter.im/v1/') - self.gitter_room = kwargs.get('gitter_room') - self.gitter_api_token = kwargs.get('gitter_api_token') - - authorization_header = 'Bearer {}'.format(self.gitter_api_token) - - self.headers = { - 'Authorization': authorization_header, - 'Content-Type': 'application/json; charset=utf-8', - 'Accept': 'application/json' - } - - # Join the Gitter room - room_data = self.join_room(self.gitter_room) - self.room_id = room_data.get('id') - - def _validate_status_code(self, response): - code = response.status_code - if code not in [200, 201]: - raise self.HTTPStatusException('{} status code recieved'.format(code)) - - def join_room(self, room_name): - """ - Join the specified Gitter room. - """ - import requests - - endpoint = '{}rooms'.format(self.gitter_host) - response = requests.post( - endpoint, - headers=self.headers, - json={'uri': room_name} - ) - self.logger.info('{} status joining room {}'.format( - response.status_code, endpoint - )) - self._validate_status_code(response) - return response.json() - - def send_message(self, text): - """ - Send a message to a Gitter room. - """ - import requests - - endpoint = '{}rooms/{}/chatMessages'.format(self.gitter_host, self.room_id) - response = requests.post( - endpoint, - headers=self.headers, - json={'text': text} - ) - self.logger.info('{} sending message to {}'.format( - response.status_code, endpoint - )) - self._validate_status_code(response) - return response.json() - - def process_response(self, statement, session_id=None): - self.send_message(statement.text) - return statement - - class HTTPStatusException(Exception): - """ - Exception raised when unexpected non-success HTTP - status codes are returned in a response. - """ - - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) diff --git a/chatter/chatterbot/output/hipchat.py b/chatter/chatterbot/output/hipchat.py deleted file mode 100644 index 647c155..0000000 --- a/chatter/chatterbot/output/hipchat.py +++ /dev/null @@ -1,69 +0,0 @@ -from __future__ import unicode_literals - -import json - -from . import OutputAdapter - - -class HipChat(OutputAdapter): - """ - An output adapter that allows a ChatterBot instance to send - responses to a HipChat room. - """ - - def __init__(self, **kwargs): - super(HipChat, self).__init__(**kwargs) - - self.hipchat_host = kwargs.get("hipchat_host") - self.hipchat_access_token = kwargs.get("hipchat_access_token") - self.hipchat_room = kwargs.get("hipchat_room") - - authorization_header = "Bearer {}".format(self.hipchat_access_token) - - self.headers = { - 'Authorization': authorization_header, - 'Content-Type': 'application/json' - } - - import requests - self.session = requests.Session() - self.session.verify = kwargs.get('ssl_verify', True) - - def send_message(self, room_id_or_name, message): - """ - Send a message to a HipChat room. - https://www.hipchat.com/docs/apiv2/method/send_message - """ - message_url = "{}/v2/room/{}/message".format( - self.hipchat_host, - room_id_or_name - ) - - response = self.session.post( - message_url, - headers=self.headers, - data=json.dumps({ - 'message': message - }) - ) - - return response.json() - - def reply_to_message(self): - """ - The HipChat api supports responding to a given message. - This may be a good feature to implement in the future to - help with multi-user conversations. - https://www.hipchat.com/docs/apiv2/method/reply_to_message - """ - raise self.AdapterMethodNotImplementedError() - - def process_response(self, statement, session_id=None): - data = self.send_message(self.hipchat_room, statement.text) - - # Update the output statement with the message id - self.chatbot.storage.update( - statement.add_extra_data('hipchat_message_id', data['id']) - ) - - return statement diff --git a/chatter/chatterbot/output/mailgun.py b/chatter/chatterbot/output/mailgun.py deleted file mode 100644 index 47ec55c..0000000 --- a/chatter/chatterbot/output/mailgun.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import unicode_literals - -from . import OutputAdapter - - -class Mailgun(OutputAdapter): - - def __init__(self, **kwargs): - super(Mailgun, self).__init__(**kwargs) - - # Use the bot's name for the name of the sender - self.name = kwargs.get('name') - self.from_address = kwargs.get('mailgun_from_address') - self.api_key = kwargs.get('mailgun_api_key') - self.endpoint = kwargs.get('mailgun_api_endpoint') - self.recipients = kwargs.get('mailgun_recipients') - - def send_message(self, subject, text, from_address, recipients): - """ - * subject: Subject of the email. - * text: Text body of the email. - * from_email: The email address that the message will be sent from. - * recipients: A list of recipient email addresses. - """ - import requests - - return requests.post( - self.endpoint, - auth=('api', self.api_key), - data={ - 'from': '%s <%s>' % (self.name, from_address), - 'to': recipients, - 'subject': subject, - 'text': text - }) - - def process_response(self, statement, session_id=None): - """ - Send the response statement as an email. - """ - subject = 'Message from %s' % (self.name) - - self.send_message( - subject, - statement.text, - self.from_address, - self.recipients - ) - - return statement diff --git a/chatter/chatterbot/output/microsoft.py b/chatter/chatterbot/output/microsoft.py deleted file mode 100644 index caf7ce7..0000000 --- a/chatter/chatterbot/output/microsoft.py +++ /dev/null @@ -1,111 +0,0 @@ -from __future__ import unicode_literals - -import json - -from . import OutputAdapter - - -class Microsoft(OutputAdapter): - """ - An output adapter that allows a ChatterBot instance to send - responses to a Microsoft bot using *Direct Line client protocol*. - """ - - def __init__(self, **kwargs): - super(Microsoft, self).__init__(**kwargs) - - self.directline_host = kwargs.get( - 'directline_host', - 'https://directline.botframework.com' - ) - self.direct_line_token_or_secret = kwargs.get( - 'direct_line_token_or_secret' - ) - self.conversation_id = kwargs.get('conversation_id') - - authorization_header = 'BotConnector {}'.format( - self.direct_line_token_or_secret - ) - - self.headers = { - 'Authorization': authorization_header, - 'Content-Type': 'application/json' - } - - def _validate_status_code(self, response): - status_code = response.status_code - if status_code not in [200, 204]: - raise self.HTTPStatusException('{} status code recieved'.format(status_code)) - - def get_most_recent_message(self): - """ - Return the most recently sent message. - """ - import requests - endpoint = '{host}/api/conversations/{id}/messages'.format( - host=self.directline_host, - id=self.conversation_id - ) - - response = requests.get( - endpoint, - headers=self.headers, - verify=False - ) - - self.logger.info('{} retrieving most recent messages {}'.format( - response.status_code, endpoint - )) - - self._validate_status_code(response) - - data = response.json() - - if data['messages']: - last_msg = int(data['watermark']) - return data['messages'][last_msg - 1] - return None - - def send_message(self, conversation_id, message): - """ - Send a message to a HipChat room. - https://www.hipchat.com/docs/apiv2/method/send_message - """ - import requests - - message_url = "{host}/api/conversations/{conversationId}/messages".format( - host=self.directline_host, - conversationId=conversation_id - ) - - response = requests.post( - message_url, - headers=self.headers, - data=json.dumps({ - 'message': message - }) - ) - - self.logger.info('{} sending message {}'.format( - response.status_code, message_url - )) - self._validate_status_code(response) - # Microsoft return 204 on operation succeeded and no content was returned. - return self.get_most_recent_message() - - def process_response(self, statement, session_id=None): - data = self.send_message(self.conversation_id, statement.text) - self.logger.info('processing user response {}'.format(data)) - return statement - - class HTTPStatusException(Exception): - """ - Exception raised when unexpected non-success HTTP - status codes are returned in a response. - """ - - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) diff --git a/chatter/chatterbot/output/output_adapter.py b/chatter/chatterbot/output/output_adapter.py deleted file mode 100644 index 880cb18..0000000 --- a/chatter/chatterbot/output/output_adapter.py +++ /dev/null @@ -1,20 +0,0 @@ -from ..adapters import Adapter - - -class OutputAdapter(Adapter): - """ - A generic class that can be overridden by a subclass to provide extended - functionality, such as delivering a response to an API endpoint. - """ - - def process_response(self, statement, session_id=None): - """ - Override this method in a subclass to implement customized functionality. - - :param statement: The statement that the chat bot has produced in response to some input. - - :param session_id: The unique id of the current chat session. - - :returns: The response statement. - """ - return statement diff --git a/chatter/chatterbot/output/terminal.py b/chatter/chatterbot/output/terminal.py deleted file mode 100644 index 9fbdafd..0000000 --- a/chatter/chatterbot/output/terminal.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import unicode_literals - -from . import OutputAdapter - - -class TerminalAdapter(OutputAdapter): - """ - A simple adapter that allows ChatterBot to - communicate through the terminal. - """ - - def process_response(self, statement, session_id=None): - """ - Print the response to the user's input. - """ - print(statement.text) - return statement.text diff --git a/chatter/chatterbot/parsing.py b/chatter/chatterbot/parsing.py deleted file mode 100644 index 5aafa75..0000000 --- a/chatter/chatterbot/parsing.py +++ /dev/null @@ -1,752 +0,0 @@ -# -*- coding: utf-8 -*- -import calendar -import re -from datetime import timedelta, datetime - -# Variations of dates that the parser can capture -year_variations = ['year', 'years', 'yrs'] -day_variations = ['days', 'day'] -minute_variations = ['minute', 'minutes', 'mins'] -hour_variations = ['hrs', 'hours', 'hour'] -week_variations = ['weeks', 'week', 'wks'] -month_variations = ['month', 'months'] - -# Variables used for RegEx Matching -day_names = 'monday|tuesday|wednesday|thursday|friday|saturday|sunday' -month_names_long = ( - 'january|february|march|april|may|june|july|august|september|october|november|december' -) -month_names = month_names_long + '|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec' -day_nearest_names = 'today|yesterday|tomorrow|tonight|tonite' -numbers = ( - '(^a(?=\s)|one|two|three|four|five|six|seven|eight|nine|ten|' - 'eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|' - 'eighteen|nineteen|twenty|thirty|forty|fifty|sixty|seventy|' - 'eighty|ninety|hundred|thousand)' -) -re_dmy = '(' + '|'.join(day_variations + minute_variations + year_variations + week_variations + month_variations) + ')' -re_duration = '(before|after|earlier|later|ago|from\snow)' -re_year = '(19|20)\d{2}|^(19|20)\d{2}' -re_timeframe = 'this|coming|next|following|previous|last|end\sof\sthe' -re_ordinal = 'st|nd|rd|th|first|second|third|fourth|fourth|' + re_timeframe -re_time = r'(?P\d{1,2})(\:(?P\d{1,2})|(?Pam|pm))' -re_separator = 'of|at|on' - -# A list tuple of regular expressions / parser fn to match -# Start with the widest match and narrow it down because the order of the match in this list matters -regex = [ - ( - re.compile( - r''' - ( - ((?P%s)[,\s]\s*)? #Matches Monday, 12 Jan 2012, 12 Jan 2012 etc - (?P\d{1,2}) # Matches a digit - (%s)? - [-\s] # One or more space - (?P%s) # Matches any month name - [-\s] # Space - (?P%s) # Year - ((\s|,\s|\s(%s))?\s*(%s))? - ) - ''' % (day_names, re_ordinal, month_names, re_year, re_separator, re_time), - (re.VERBOSE | re.IGNORECASE) - ), - lambda m, base_date: datetime( - int(m.group('year') if m.group('year') else base_date.year), - HASHMONTHS[m.group('month').strip().lower()], - int(m.group('day') if m.group('day') else 1), - ) + timedelta(**convert_time_to_hour_minute( - m.group('hour'), - m.group('minute'), - m.group('convention') - )) - ), - ( - re.compile( - r''' - ( - ((?P%s)[,\s][-\s]*)? #Matches Monday, Jan 12 2012, Jan 12 2012 etc - (?P%s) # Matches any month name - [-\s] # Space - ((?P\d{1,2})) # Matches a digit - (%s)? - ([-\s](?P%s))? # Year - ((\s|,\s|\s(%s))?\s*(%s))? - ) - ''' % (day_names, month_names, re_ordinal, re_year, re_separator, re_time), - (re.VERBOSE | re.IGNORECASE) - ), - lambda m, base_date: datetime( - int(m.group('year') if m.group('year') else base_date.year), - HASHMONTHS[m.group('month').strip().lower()], - int(m.group('day') if m.group('day') else 1) - ) + timedelta(**convert_time_to_hour_minute( - m.group('hour'), - m.group('minute'), - m.group('convention') - )) - ), - ( - re.compile( - r''' - ( - (?P%s) # Matches any month name - [-\s] # One or more space - (?P\d{1,2}) # Matches a digit - (%s)? - [-\s]\s*? - (?P%s) # Year - ((\s|,\s|\s(%s))?\s*(%s))? - ) - ''' % (month_names, re_ordinal, re_year, re_separator, re_time), - (re.VERBOSE | re.IGNORECASE) - ), - lambda m, base_date: datetime( - int(m.group('year') if m.group('year') else base_date.year), - HASHMONTHS[m.group('month').strip().lower()], - int(m.group('day') if m.group('day') else 1), - ) + timedelta(**convert_time_to_hour_minute( - m.group('hour'), - m.group('minute'), - m.group('convention') - )) - ), - ( - re.compile( - r''' - ( - ((?P\d+|(%s[-\s]?)+)\s)? # Matches any number or string 25 or twenty five - (?P%s)s?\s # Matches days, months, years, weeks, minutes - (?P%s) # before, after, earlier, later, ago, from now - (\s*(?P(%s)))? - ((\s|,\s|\s(%s))?\s*(%s))? - ) - ''' % (numbers, re_dmy, re_duration, day_nearest_names, re_separator, re_time), - (re.VERBOSE | re.IGNORECASE) - ), - lambda m, base_date: date_from_duration( - base_date, - m.group('number'), - m.group('unit').lower(), - m.group('duration').lower(), - m.group('base_time') - ) + timedelta(**convert_time_to_hour_minute( - m.group('hour'), - m.group('minute'), - m.group('convention') - )) - ), - ( - re.compile( - r''' - ( - (?P%s) # First quarter of 2014 - \s+ - quarter\sof - \s+ - (?P%s) - ) - ''' % (re_ordinal, re_year), - (re.VERBOSE | re.IGNORECASE) - ), - lambda m, base_date: date_from_quarter( - base_date, - HASHORDINALS[m.group('ordinal').lower()], - int(m.group('year') if m.group('year') else base_date.year) - ) - ), - ( - re.compile( - r''' - ( - (?P\d+) - (?P%s) # 1st January 2012 - ((\s|,\s|\s(%s))?\s*)? - (?P%s) - ([,\s]\s*(?P%s))? - ) - ''' % (re_ordinal, re_separator, month_names, re_year), - (re.VERBOSE | re.IGNORECASE) - ), - lambda m, base_date: datetime( - int(m.group('year') if m.group('year') else base_date.year), - int(HASHMONTHS[m.group('month').lower()] if m.group('month') else 1), - int(m.group('ordinal_value') if m.group('ordinal_value') else 1), - ) - ), - ( - re.compile( - r''' - ( - (?P%s) - \s+ - (?P\d+) - (?P%s) # January 1st 2012 - ([,\s]\s*(?P%s))? - ) - ''' % (month_names, re_ordinal, re_year), - (re.VERBOSE | re.IGNORECASE) - ), - lambda m, base_date: datetime( - int(m.group('year') if m.group('year') else base_date.year), - int(HASHMONTHS[m.group('month').lower()] if m.group('month') else 1), - int(m.group('ordinal_value') if m.group('ordinal_value') else 1), - ) - ), - ( - re.compile( - r''' - (?P