From 762b0fd32005e3a403dcb44bae32a53f2f6d1777 Mon Sep 17 00:00:00 2001 From: bobloy Date: Fri, 25 Sep 2020 12:02:13 -0400 Subject: [PATCH 01/40] WIP Twitter training --- chatter/chat.py | 21 ++++++++++---------- chatter/trainers.py | 48 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 chatter/trainers.py diff --git a/chatter/chat.py b/chatter/chat.py index ad8e37b..607457c 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -15,6 +15,8 @@ from redbot.core.commands import Cog from redbot.core.data_manager import cog_data_path from redbot.core.utils.predicates import MessagePredicate +from chatter.trainers import TwitterCorpusTrainer + log = logging.getLogger("red.fox_v3.chatter") @@ -105,15 +107,7 @@ class Chatter(Cog): return msg.clean_content def new_conversation(msg, sent, out_in, delta): - # if sent is None: - # return False - - # Don't do "too short" processing here. Sometimes people don't respond. - # if len(out_in) < 2: - # return False - - # print(msg.created_at - sent) - + # Should always be positive numbers return msg.created_at - sent >= delta for channel in ctx.guild.text_channels: @@ -158,6 +152,11 @@ class Chatter(Cog): return out + def _train_twitter(self, *args, **kwargs): + trainer = TwitterCorpusTrainer(self.chatbot) + trainer.train(*args, **kwargs) + return True + def _train_ubuntu(self): trainer = UbuntuCorpusTrainer(self.chatbot) trainer.train() @@ -479,7 +478,9 @@ class Chatter(Cog): text = message.clean_content async with channel.typing(): - future = await self.loop.run_in_executor(None, self.chatbot.get_response, text) + # Switched to `generate_response` from `get_result` + # Switch back once better conversation detection is used. + future = await self.loop.run_in_executor(None, self.chatbot.generate_response, text) if future and str(future): await channel.send(str(future)) diff --git a/chatter/trainers.py b/chatter/trainers.py new file mode 100644 index 0000000..e6eedba --- /dev/null +++ b/chatter/trainers.py @@ -0,0 +1,48 @@ +from chatterbot import utils +from chatterbot.conversation import Statement +from chatterbot.trainers import Trainer + + +class TwitterCorpusTrainer(Trainer): + def train(self, *args, **kwargs): + """ + Train the chat bot based on the provided list of + statements that represents a single conversation. + """ + import twint + + c = twint.Config() + c.__dict__.update(kwargs) + twint.run.Search(c) + + + previous_statement_text = None + previous_statement_search_text = '' + + statements_to_create = [] + + for conversation_count, text in enumerate(conversation): + if self.show_training_progress: + utils.print_progress_bar( + 'List Trainer', + conversation_count + 1, len(conversation) + ) + + statement_search_text = self.chatbot.storage.tagger.get_text_index_string(text) + + statement = self.get_preprocessed_statement( + Statement( + text=text, + search_text=statement_search_text, + in_response_to=previous_statement_text, + search_in_response_to=previous_statement_search_text, + conversation='training' + ) + ) + + previous_statement_text = statement.text + previous_statement_search_text = statement_search_text + + statements_to_create.append(statement) + + self.chatbot.storage.create_many(statements_to_create) \ No newline at end of file From 26234e3b18a465ded651960a73ed7d15692a53fb Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 19 Oct 2020 15:16:49 -0400 Subject: [PATCH 02/40] Alternate dependencies attempt --- chatter/info.json | 3 +- chatter/trainers.py | 85 +++++++++++++++++++++++---------------------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/chatter/info.json b/chatter/info.json index b79e587..df77ee8 100644 --- a/chatter/info.json +++ b/chatter/info.json @@ -17,7 +17,8 @@ "pytz", "https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.3.1/en_core_web_sm-2.3.1.tar.gz#egg=en_core_web_sm", "https://github.com/explosion/spacy-models/releases/download/en_core_web_md-2.3.1/en_core_web_md-2.3.1.tar.gz#egg=en_core_web_md", - "spacy>=2.3,<2.4" + "spacy>=2.3,<2.4", + "--no-deps \"chatterbot>=1.1\"" ], "short": "Local Chatbot run on machine learning", "end_user_data_statement": "This cog only stores anonymous conversations data; no End User Data is stored.", diff --git a/chatter/trainers.py b/chatter/trainers.py index e6eedba..42d6288 100644 --- a/chatter/trainers.py +++ b/chatter/trainers.py @@ -4,45 +4,46 @@ from chatterbot.trainers import Trainer class TwitterCorpusTrainer(Trainer): - def train(self, *args, **kwargs): - """ - Train the chat bot based on the provided list of - statements that represents a single conversation. - """ - import twint - - c = twint.Config() - c.__dict__.update(kwargs) - twint.run.Search(c) - - - previous_statement_text = None - previous_statement_search_text = '' - - statements_to_create = [] - - for conversation_count, text in enumerate(conversation): - if self.show_training_progress: - utils.print_progress_bar( - 'List Trainer', - conversation_count + 1, len(conversation) - ) - - statement_search_text = self.chatbot.storage.tagger.get_text_index_string(text) - - statement = self.get_preprocessed_statement( - Statement( - text=text, - search_text=statement_search_text, - in_response_to=previous_statement_text, - search_in_response_to=previous_statement_search_text, - conversation='training' - ) - ) - - previous_statement_text = statement.text - previous_statement_search_text = statement_search_text - - statements_to_create.append(statement) - - self.chatbot.storage.create_many(statements_to_create) \ No newline at end of file + pass + # def train(self, *args, **kwargs): + # """ + # Train the chat bot based on the provided list of + # statements that represents a single conversation. + # """ + # import twint + # + # c = twint.Config() + # c.__dict__.update(kwargs) + # twint.run.Search(c) + # + # + # previous_statement_text = None + # previous_statement_search_text = '' + # + # statements_to_create = [] + # + # for conversation_count, text in enumerate(conversation): + # if self.show_training_progress: + # utils.print_progress_bar( + # 'List Trainer', + # conversation_count + 1, len(conversation) + # ) + # + # statement_search_text = self.chatbot.storage.tagger.get_text_index_string(text) + # + # statement = self.get_preprocessed_statement( + # Statement( + # text=text, + # search_text=statement_search_text, + # in_response_to=previous_statement_text, + # search_in_response_to=previous_statement_search_text, + # conversation='training' + # ) + # ) + # + # previous_statement_text = statement.text + # previous_statement_search_text = statement_search_text + # + # statements_to_create.append(statement) + # + # self.chatbot.storage.create_many(statements_to_create) \ No newline at end of file From a6ebe02233eadd97cedc3191b680d3a3040dd8fe Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 19 Oct 2020 16:09:21 -0400 Subject: [PATCH 03/40] Back to basics --- chatter/info.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/chatter/info.json b/chatter/info.json index df77ee8..b79e587 100644 --- a/chatter/info.json +++ b/chatter/info.json @@ -17,8 +17,7 @@ "pytz", "https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.3.1/en_core_web_sm-2.3.1.tar.gz#egg=en_core_web_sm", "https://github.com/explosion/spacy-models/releases/download/en_core_web_md-2.3.1/en_core_web_md-2.3.1.tar.gz#egg=en_core_web_md", - "spacy>=2.3,<2.4", - "--no-deps \"chatterbot>=1.1\"" + "spacy>=2.3,<2.4" ], "short": "Local Chatbot run on machine learning", "end_user_data_statement": "This cog only stores anonymous conversations data; no End User Data is stored.", From 46342109604e2824a3bd011dfbd880fe3909e91c Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 19 Oct 2020 16:24:39 -0400 Subject: [PATCH 04/40] Add automatic install option --- chatter/README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/chatter/README.md b/chatter/README.md index 8ef6734..c831bb8 100644 --- a/chatter/README.md +++ b/chatter/README.md @@ -59,6 +59,35 @@ Install these on your windows machine before attempting the installation: [Pandoc - Universal Document Converter](https://pandoc.org/installing.html) ## Methods +### Automatic + +This method requires some luck to pull off. + +#### Step 1: Add repo and install cog + +``` +[p]repo add Fox https://github.com/bobloy/Fox-V3 +[p]cog install Fox chatter +``` + +If you get an error at this step, stop and skip to one of the manual methods below. + +#### Step 2: Install additional dependencies + +Assuming the previous commands had no error, you can now use `pipinstall` to add the remaining dependencies. + +NOTE: This method is not the intended use case for `pipinstall` and may stop working in the future. + +``` +[p]pipinstall --no-deps chatterbot>=1.1 +``` + +#### Step 3: Load the cog and get started + +``` +[p]load chatter +``` + ### Windows - Manually #### Step 1: Built-in Downloader From 14f8b825d8a5a81d12aa885da7236b13e97964d0 Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 2 Feb 2021 16:35:41 -0500 Subject: [PATCH 05/40] Fix bad learning and checks --- chatter/chat.py | 55 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/chatter/chat.py b/chatter/chat.py index 0988d46..a0a5f28 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -2,8 +2,10 @@ import asyncio import logging import os import pathlib +from collections import defaultdict from datetime import datetime, timedelta -from typing import Optional +from functools import partial +from typing import Dict, Optional import discord from chatterbot import ChatBot @@ -75,6 +77,10 @@ class Chatter(Cog): self.loop = asyncio.get_event_loop() + self._guild_cache = defaultdict(dict) + + self._last_message_per_channel: Dict[Optional[discord.Message]] = defaultdict(lambda: None) + async def red_delete_data_for_user(self, **kwargs): """Nothing to delete""" return @@ -190,6 +196,7 @@ class Chatter(Cog): if ctx.invoked_subcommand is None: pass + @commands.admin() @chatter.command(name="channel") async def chatter_channel( self, ctx: commands.Context, channel: Optional[discord.TextChannel] = None @@ -209,6 +216,7 @@ class Chatter(Cog): await self.config.guild(ctx.guild).chatchannel.set(channel.id) await ctx.maybe_send_embed(f"Chat channel is now {channel.mention}") + @commands.is_owner() @chatter.command(name="cleardata") async def chatter_cleardata(self, ctx: commands.Context, confirm: bool = False): """ @@ -241,6 +249,7 @@ class Chatter(Cog): await ctx.tick() + @commands.is_owner() @chatter.command(name="algorithm", aliases=["algo"]) async def chatter_algorithm( self, ctx: commands.Context, algo_number: int, threshold: float = None @@ -274,6 +283,7 @@ class Chatter(Cog): await ctx.tick() + @commands.is_owner() @chatter.command(name="model") async def chatter_model(self, ctx: commands.Context, model_number: int): """ @@ -311,6 +321,7 @@ class Chatter(Cog): f"Model has been switched to {self.tagger_language.ISO_639_1}" ) + @commands.is_owner() @chatter.command(name="minutes") async def minutes(self, ctx: commands.Context, minutes: int): """ @@ -322,10 +333,12 @@ class Chatter(Cog): await ctx.send_help() return - await self.config.guild(ctx.guild).convo_length.set(minutes) + await self.config.guild(ctx.guild).convo_delta.set(minutes) + self._guild_cache[ctx.guild.id]["convo_delta"] = minutes await ctx.tick() + @commands.is_owner() @chatter.command(name="age") async def age(self, ctx: commands.Context, days: int): """ @@ -340,6 +353,7 @@ class Chatter(Cog): await self.config.guild(ctx.guild).days.set(days) await ctx.tick() + @commands.is_owner() @chatter.command(name="backup") async def backup(self, ctx, backupname): """ @@ -361,6 +375,7 @@ class Chatter(Cog): else: await ctx.maybe_send_embed("Error occurred :(") + @commands.is_owner() @chatter.command(name="trainubuntu") async def chatter_train_ubuntu(self, ctx: commands.Context, confirmation: bool = False): """ @@ -382,6 +397,7 @@ class Chatter(Cog): else: await ctx.send("Error occurred :(") + @commands.is_owner() @chatter.command(name="trainenglish") async def chatter_train_english(self, ctx: commands.Context): """ @@ -395,6 +411,7 @@ class Chatter(Cog): else: await ctx.maybe_send_embed("Error occurred :(") + @commands.is_owner() @chatter.command() async def train(self, ctx: commands.Context, channel: discord.TextChannel): """ @@ -477,12 +494,34 @@ class Chatter(Cog): text = message.clean_content - async with channel.typing(): - # Switched to `generate_response` from `get_result` - # Switch back once better conversation detection is used. - future = await self.loop.run_in_executor(None, self.chatbot.generate_response, text) + async with ctx.typing(): + + if not self._guild_cache[ctx.guild.id]: + self._guild_cache[ctx.guild.id] = await self.config.guild(ctx.guild).all() + + if self._last_message_per_channel[ctx.channel.id] is not None: + last_m: discord.Message = self._last_message_per_channel[ctx.channel.id] + minutes = self._guild_cache[ctx.guild.id]["convo_delta"] + if (datetime.utcnow() - last_m.created_at).seconds > minutes*60: + in_response_to = None + else: + in_response_to = last_m.content + else: + in_response_to = None + + if in_response_to is None: + log.debug("Generating response") + Statement = self.chatbot.storage.get_object('statement') + future = await self.loop.run_in_executor( + None, self.chatbot.generate_response, Statement(text) + ) + else: + log.debug("Getting response") + future = await self.loop.run_in_executor( + None, partial(self.chatbot.get_response, text, in_response_to=in_response_to) + ) if future and str(future): - await channel.send(str(future)) + self._last_message_per_channel[ctx.channel.id] = await ctx.send(str(future)) else: - await channel.send(":thinking:") + await ctx.send(":thinking:") From 337def2fa32ebdcc788e5d785bd388537a6b4899 Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 15 Feb 2021 10:18:18 -0500 Subject: [PATCH 06/40] Some progress on updated ubuntu trainer --- chatter/chat.py | 79 +++++++++++++++++++++--- chatter/info.json | 3 +- chatter/trainers.py | 142 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 213 insertions(+), 11 deletions(-) diff --git a/chatter/chat.py b/chatter/chat.py index a0a5f28..098ba73 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -17,7 +17,7 @@ from redbot.core.commands import Cog from redbot.core.data_manager import cog_data_path from redbot.core.utils.predicates import MessagePredicate -from chatter.trainers import TwitterCorpusTrainer +from chatter.trainers import TwitterCorpusTrainer, UbuntuCorpusTrainer2 log = logging.getLogger("red.fox_v3.chatter") @@ -168,6 +168,10 @@ class Chatter(Cog): trainer.train() return True + async def _train_ubuntu2(self): + trainer = UbuntuCorpusTrainer2(self.chatbot, cog_data_path(self)) + await trainer.asynctrain() + def _train_english(self): trainer = ChatterBotCorpusTrainer(self.chatbot) # try: @@ -353,6 +357,15 @@ class Chatter(Cog): await self.config.guild(ctx.guild).days.set(days) await ctx.tick() + @commands.is_owner() + @chatter.command(name="kaggle") + async def chatter_kaggle(self, ctx: commands.Context): + """Register with the kaggle API to download additional datasets for training""" + if not await self.check_for_kaggle(): + await ctx.maybe_send_embed( + "[Click here for instructions to setup the kaggle api](https://github.com/Kaggle/kaggle-api#api-credentials)" + ) + @commands.is_owner() @chatter.command(name="backup") async def backup(self, ctx, backupname): @@ -376,7 +389,13 @@ class Chatter(Cog): await ctx.maybe_send_embed("Error occurred :(") @commands.is_owner() - @chatter.command(name="trainubuntu") + @chatter.group(name="train") + async def chatter_train(self, ctx: commands.Context): + """Commands for training the bot""" + pass + + @commands.is_owner() + @chatter_train.command(name="ubuntu") async def chatter_train_ubuntu(self, ctx: commands.Context, confirmation: bool = False): """ WARNING: Large Download! Trains the bot using Ubuntu Dialog Corpus data. @@ -385,7 +404,7 @@ class Chatter(Cog): if not confirmation: await ctx.maybe_send_embed( "Warning: This command downloads ~500MB then eats your CPU for training\n" - "If you're sure you want to continue, run `[p]chatter trainubuntu True`" + "If you're sure you want to continue, run `[p]chatter train ubuntu True`" ) return @@ -398,7 +417,29 @@ class Chatter(Cog): await ctx.send("Error occurred :(") @commands.is_owner() - @chatter.command(name="trainenglish") + @chatter_train.command(name="ubuntu2") + async def chatter_train_ubuntu2(self, ctx: commands.Context, confirmation: bool = False): + """ + WARNING: Large Download! Trains the bot using *NEW* Ubuntu Dialog Corpus data. + """ + + if not confirmation: + await ctx.maybe_send_embed( + "Warning: This command downloads ~800 then eats your CPU for training\n" + "If you're sure you want to continue, run `[p]chatter train ubuntu2 True`" + ) + return + + async with ctx.typing(): + future = await self._train_ubuntu2() + + if future: + await ctx.send("Training successful!") + else: + await ctx.send("Error occurred :(") + + @commands.is_owner() + @chatter_train.command(name="english") async def chatter_train_english(self, ctx: commands.Context): """ Trains the bot in english @@ -412,10 +453,27 @@ class Chatter(Cog): await ctx.maybe_send_embed("Error occurred :(") @commands.is_owner() - @chatter.command() - async def train(self, ctx: commands.Context, channel: discord.TextChannel): + @chatter_train.command(name="list") + async def chatter_train_list(self, ctx: commands.Context): + """Trains the bot based on an uploaded list. + + Must be a file in the format of a python list: ['prompt', 'response1', 'response2'] + """ + if not ctx.message.attachments: + await ctx.maybe_send_embed("You must upload a file when using this command") + return + + attachment: discord.Attachment = ctx.message.attachments[0] + + a_bytes = await attachment.read() + + await ctx.send("Not yet implemented") + + @commands.is_owner() + @chatter_train.command(name="channel") + async def chatter_train_channel(self, ctx: commands.Context, channel: discord.TextChannel): """ - Trains the bot based on language in this guild + Trains the bot based on language in this guild. """ await ctx.maybe_send_embed( @@ -502,7 +560,7 @@ class Chatter(Cog): if self._last_message_per_channel[ctx.channel.id] is not None: last_m: discord.Message = self._last_message_per_channel[ctx.channel.id] minutes = self._guild_cache[ctx.guild.id]["convo_delta"] - if (datetime.utcnow() - last_m.created_at).seconds > minutes*60: + if (datetime.utcnow() - last_m.created_at).seconds > minutes * 60: in_response_to = None else: in_response_to = last_m.content @@ -511,7 +569,7 @@ class Chatter(Cog): if in_response_to is None: log.debug("Generating response") - Statement = self.chatbot.storage.get_object('statement') + Statement = self.chatbot.storage.get_object("statement") future = await self.loop.run_in_executor( None, self.chatbot.generate_response, Statement(text) ) @@ -525,3 +583,6 @@ class Chatter(Cog): self._last_message_per_channel[ctx.channel.id] = await ctx.send(str(future)) else: await ctx.send(":thinking:") + + async def check_for_kaggle(self): + return False diff --git a/chatter/info.json b/chatter/info.json index b79e587..a048c23 100644 --- a/chatter/info.json +++ b/chatter/info.json @@ -17,7 +17,8 @@ "pytz", "https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.3.1/en_core_web_sm-2.3.1.tar.gz#egg=en_core_web_sm", "https://github.com/explosion/spacy-models/releases/download/en_core_web_md-2.3.1/en_core_web_md-2.3.1.tar.gz#egg=en_core_web_md", - "spacy>=2.3,<2.4" + "spacy>=2.3,<2.4", + "kaggle" ], "short": "Local Chatbot run on machine learning", "end_user_data_statement": "This cog only stores anonymous conversations data; no End User Data is stored.", diff --git a/chatter/trainers.py b/chatter/trainers.py index 42d6288..0b765b7 100644 --- a/chatter/trainers.py +++ b/chatter/trainers.py @@ -1,6 +1,146 @@ +import asyncio +import csv +import logging +import os +import pathlib +import time +from functools import partial + from chatterbot import utils from chatterbot.conversation import Statement +from chatterbot.tagging import PosLemmaTagger from chatterbot.trainers import Trainer +from redbot.core.bot import Red +from dateutil import parser as date_parser +from redbot.core.utils import AsyncIter + +log = logging.getLogger("red.fox_v3.chatter.trainers") + + +class KaggleTrainer(Trainer): + def __init__(self, chatbot, datapath: pathlib.Path, **kwargs): + super().__init__(chatbot, **kwargs) + + self.data_directory = datapath / kwargs.get("downloadpath", "kaggle_download") + + self.kaggle_dataset = kwargs.get( + "kaggle_dataset", + "Cornell-University/movie-dialog-corpus", + ) + + # Create the data directory if it does not already exist + if not os.path.exists(self.data_directory): + os.makedirs(self.data_directory) + + def is_downloaded(self, file_path): + """ + Check if the data file is already downloaded. + """ + if os.path.exists(file_path): + self.chatbot.logger.info("File is already downloaded") + return True + + return False + + async def download(self, dataset): + import kaggle # This triggers the API token check + + future = await asyncio.get_event_loop().run_in_executor( + None, + partial( + kaggle.api.dataset_download_files, + dataset=dataset, + path=self.data_directory, + quiet=False, + unzip=True, + ), + ) + + +class UbuntuCorpusTrainer2(KaggleTrainer): + def __init__(self, chatbot, datapath: pathlib.Path, **kwargs): + super().__init__( + chatbot, + datapath, + downloadpath="ubuntu_data_v2", + kaggle_dataset="rtatman/ubuntu-dialogue-corpus", + **kwargs + ) + + async def asynctrain(self, *args, **kwargs): + extracted_dir = self.data_directory / "Ubuntu-dialogue-corpus" + + # Download and extract the Ubuntu dialog corpus if needed + if not extracted_dir.exists(): + await self.download(self.kaggle_dataset) + else: + log.info("Ubuntu dialogue already downloaded") + if not extracted_dir.exists(): + raise FileNotFoundError("Did not extract in the expected way") + + train_dialogue = kwargs.get("train_dialogue", True) + train_196_dialogue = kwargs.get("train_196", False) + train_301_dialogue = kwargs.get("train_301", False) + + if train_dialogue: + await self.run_dialogue_training(extracted_dir, "dialogueText.csv") + + if train_196_dialogue: + await self.run_dialogue_training(extracted_dir, "dialogueText_196.csv") + + if train_301_dialogue: + await self.run_dialogue_training(extracted_dir, "dialogueText_301.csv") + + async def run_dialogue_training(self, extracted_dir, dialogue_file): + log.info(f"Beginning dialogue training on {dialogue_file}") + start_time = time.time() + + tagger = PosLemmaTagger(language=self.chatbot.storage.tagger.language) + + with open(extracted_dir / dialogue_file, "r", encoding="utf-8") as dg: + reader = csv.DictReader(dg) + + next(reader) # Skip the header + + last_dialogue_id = None + previous_statement_text = None + previous_statement_search_text = "" + statements_from_file = [] + + async for row in AsyncIter(reader): + dialogue_id = row["dialogueID"] + if dialogue_id != last_dialogue_id: + previous_statement_text = None + previous_statement_search_text = "" + last_dialogue_id = dialogue_id + + if len(row) > 0: + statement = Statement( + text=row["text"], + in_response_to=previous_statement_text, + conversation="training", + created_at=date_parser.parse(row["date"]), + persona=row["from"], + ) + + for preprocessor in self.chatbot.preprocessors: + statement = preprocessor(statement) + + statement.search_text = tagger.get_text_index_string(statement.text) + statement.search_in_response_to = previous_statement_search_text + + previous_statement_text = statement.text + previous_statement_search_text = statement.search_text + + statements_from_file.append(statement) + + if statements_from_file: + self.chatbot.storage.create_many(statements_from_file) + + print("Training took", time.time() - start_time, "seconds.") + + def train(self, *args, **kwargs): + log.error("See asynctrain instead") class TwitterCorpusTrainer(Trainer): @@ -46,4 +186,4 @@ class TwitterCorpusTrainer(Trainer): # # statements_to_create.append(statement) # - # self.chatbot.storage.create_many(statements_to_create) \ No newline at end of file + # self.chatbot.storage.create_many(statements_to_create) From 6363f5eadc833a4a442fff2572d7c4e97bf14a2d Mon Sep 17 00:00:00 2001 From: Obi-Wan3 <44986166+Obi-Wan3@users.noreply.github.com> Date: Tue, 16 Feb 2021 17:15:25 -0800 Subject: [PATCH 07/40] [StealEmoji] update to use guild.emoji_limit --- stealemoji/stealemoji.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stealemoji/stealemoji.py b/stealemoji/stealemoji.py index a492527..5d3701f 100644 --- a/stealemoji/stealemoji.py +++ b/stealemoji/stealemoji.py @@ -16,16 +16,16 @@ log = logging.getLogger("red.fox_v3.stealemoji") async def check_guild(guild, emoji): - if len(guild.emojis) >= 100: + if len(guild.emojis) >= 2*guild.emoji_limit: return False if len(guild.emojis) < 50: return True if emoji.animated: - return sum(e.animated for e in guild.emojis) < 50 + return sum(e.animated for e in guild.emojis) < guild.emoji_limit else: - return sum(not e.animated for e in guild.emojis) < 50 + return sum(not e.animated for e in guild.emojis) < guild.emoji_limit class StealEmoji(Cog): From 7ad6b156419c147b2e7db692dfc796c90add0320 Mon Sep 17 00:00:00 2001 From: Obi-Wan3 <44986166+Obi-Wan3@users.noreply.github.com> Date: Tue, 16 Feb 2021 17:19:41 -0800 Subject: [PATCH 08/40] Edit to satisfy style requirement --- stealemoji/stealemoji.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stealemoji/stealemoji.py b/stealemoji/stealemoji.py index 5d3701f..de65728 100644 --- a/stealemoji/stealemoji.py +++ b/stealemoji/stealemoji.py @@ -16,7 +16,7 @@ log = logging.getLogger("red.fox_v3.stealemoji") async def check_guild(guild, emoji): - if len(guild.emojis) >= 2*guild.emoji_limit: + if len(guild.emojis) >= 2 * guild.emoji_limit: return False if len(guild.emojis) < 50: From 92957bcb1f87957e2454350d5a1ce85922d37f57 Mon Sep 17 00:00:00 2001 From: Obi-Wan3 <44986166+Obi-Wan3@users.noreply.github.com> Date: Thu, 18 Feb 2021 10:08:24 -0800 Subject: [PATCH 09/40] implement same logic for skipping further checks Co-authored-by: bobloy --- stealemoji/stealemoji.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stealemoji/stealemoji.py b/stealemoji/stealemoji.py index de65728..8f32d74 100644 --- a/stealemoji/stealemoji.py +++ b/stealemoji/stealemoji.py @@ -19,7 +19,7 @@ async def check_guild(guild, emoji): if len(guild.emojis) >= 2 * guild.emoji_limit: return False - if len(guild.emojis) < 50: + if len(guild.emojis) < guild.emoji_limit: return True if emoji.animated: From ee8f6bbf5726dc2f545a2c2c59a754b205099ba4 Mon Sep 17 00:00:00 2001 From: bobloy Date: Fri, 19 Feb 2021 11:07:34 -0500 Subject: [PATCH 10/40] Fix docstrings --- flag/flag.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/flag/flag.py b/flag/flag.py index 6216f65..10f0334 100644 --- a/flag/flag.py +++ b/flag/flag.py @@ -53,9 +53,7 @@ class Flag(Cog): @commands.group() async def flagset(self, ctx: commands.Context): """ - My custom cog - - Extra information goes here + Commands for managing Flag settings """ if ctx.invoked_subcommand is None: pass From 221ca4074bd6f7ec7ae561e5935fce6d4d6b63cd Mon Sep 17 00:00:00 2001 From: bobloy Date: Wed, 24 Feb 2021 12:27:50 -0500 Subject: [PATCH 11/40] Update launchlib to version 2 --- launchlib/info.json | 2 +- launchlib/launchlib.py | 95 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/launchlib/info.json b/launchlib/info.json index c1c7ad7..f9b7f11 100644 --- a/launchlib/info.json +++ b/launchlib/info.json @@ -8,7 +8,7 @@ "install_msg": "Thank you for installing LaunchLib. Get started with `[p]load launchlib`, then `[p]help LaunchLib`", "short": "Access launch data for space flights", "end_user_data_statement": "This cog does not store any End User Data", - "requirements": ["python-launch-library>=1.0.6"], + "requirements": ["python-launch-library>=2.0.3"], "tags": [ "bobloy", "utils", diff --git a/launchlib/launchlib.py b/launchlib/launchlib.py index ae870fd..4994a1d 100644 --- a/launchlib/launchlib.py +++ b/launchlib/launchlib.py @@ -1,7 +1,7 @@ import asyncio import functools import logging - +import re import discord import launchlibrary as ll from redbot.core import Config, commands @@ -14,9 +14,7 @@ log = logging.getLogger("red.fox_v3.launchlib") class LaunchLib(commands.Cog): """ - Cog Description - - Less important information about the cog + Cog using `thespacedevs` API to get details about rocket launches """ def __init__(self, bot: Red): @@ -37,27 +35,86 @@ class LaunchLib(commands.Cog): return async def _embed_launch_data(self, launch: ll.AsyncLaunch): - status: ll.AsyncLaunchStatus = await launch.get_status() + + if False: + example_launch = ll.AsyncLaunch( + id="9279744e-46b2-4eca-adea-f1379672ec81", + name="Atlas LV-3A | Samos 2", + tbddate=False, + tbdtime=False, + status={"id": 3, "name": "Success"}, + inhold=False, + windowstart="1961-01-31 20:21:19+00:00", + windowend="1961-01-31 20:21:19+00:00", + net="1961-01-31 20:21:19+00:00", + info_urls=[], + vid_urls=[], + holdreason=None, + failreason=None, + probability=0, + hashtag=None, + agency=None, + changed=None, + pad=ll.Pad( + id=93, + name="Space Launch Complex 3W", + latitude=34.644, + longitude=-120.593, + map_url="http://maps.google.com/maps?q=34.644+N,+120.593+W", + retired=None, + total_launch_count=3, + agency_id=161, + wiki_url=None, + info_url=None, + location=ll.Location( + id=11, + name="Vandenberg AFB, CA, USA", + country_code="USA", + total_launch_count=83, + total_landing_count=3, + pads=None, + ), + map_image="https://spacelaunchnow-prod-east.nyc3.digitaloceanspaces.com/media/launch_images/pad_93_20200803143225.jpg", + ), + rocket=ll.Rocket( + id=2362, + name=None, + default_pads=None, + family=None, + wiki_url=None, + info_url=None, + image_url=None, + ), + missions=None, + ) + + # status: ll.AsyncLaunchStatus = await launch.get_status() + status = launch.status rocket: ll.AsyncRocket = launch.rocket title = launch.name - description = status.description + description = status["name"] urls = launch.vid_urls + launch.info_urls - if not urls and rocket: - urls = rocket.info_urls + [rocket.wiki_url] + if rocket: + urls += [rocket.info_url, rocket.wiki_url] + if launch.pad: + urls += [launch.pad.info_url, launch.pad.wiki_url] + if urls: - url = urls[0] + url = next((url for url in urls if urls is not None), None) else: url = None - color = discord.Color.green() if status.id in [1, 3] else discord.Color.red() + color = discord.Color.green() if status["id"] in [1, 3] else discord.Color.red() em = discord.Embed(title=title, description=description, url=url, color=color) if rocket and rocket.image_url and rocket.image_url != "Array": em.set_image(url=rocket.image_url) + elif launch.pad and launch.pad.map_image: + em.set_image(url=launch.pad.map_image) agency = getattr(launch, "agency", None) if agency is not None: @@ -89,6 +146,16 @@ class LaunchLib(commands.Cog): data = mission.get(f[0], None) if data is not None and data: em.add_field(name=f[1], value=data) + if launch.pad: + location_url = getattr(launch.pad, "map_url", None) + pad_name = getattr(launch.pad, "name", None) + + if pad_name is not None: + if location_url is not None: + location_url = re.sub("[^a-zA-Z0-9/:.'+\"°?=,-]", "", location_url) # Fix bad URLS + em.add_field(name="Launch Pad Name", value=f"[{pad_name}]({location_url})") + else: + em.add_field(name="Launch Pad Name", value=pad_name) if rocket and rocket.family: em.add_field(name="Rocket Family", value=rocket.family) @@ -101,11 +168,17 @@ class LaunchLib(commands.Cog): @commands.group() async def launchlib(self, ctx: commands.Context): + """Base command for getting launches""" if ctx.invoked_subcommand is None: pass @launchlib.command() async def next(self, ctx: commands.Context, num_launches: int = 1): + """ + Show the next launches + + Use `num_launches` to get more than one. + """ # launches = await api.async_next_launches(num_launches) # loop = asyncio.get_running_loop() # @@ -115,6 +188,8 @@ class LaunchLib(commands.Cog): # launches = await self.api.async_fetch_launch(num=num_launches) + # log.debug(str(launches)) + async with ctx.typing(): for x, launch in enumerate(launches): if x >= num_launches: From a5eda8ca2a25e61d5ced5cf2223e1cb1b31e0cf9 Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 2 Mar 2021 08:53:50 -0500 Subject: [PATCH 12/40] Forgot how to use regex apparently --- isitdown/isitdown.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/isitdown/isitdown.py b/isitdown/isitdown.py index f786928..b72549a 100644 --- a/isitdown/isitdown.py +++ b/isitdown/isitdown.py @@ -10,9 +10,9 @@ log = logging.getLogger("red.fox_v3.isitdown") class IsItDown(commands.Cog): """ - Cog Description + Cog for checking whether a website is down or not. - Less important information about the cog + Uses the `isitdown.site` API """ def __init__(self, bot: Red): @@ -36,23 +36,25 @@ class IsItDown(commands.Cog): Alias: iid """ try: - resp = await self._check_if_down(url_to_check) + resp, url = await self._check_if_down(url_to_check) except AssertionError: await ctx.maybe_send_embed("Invalid URL provided. Make sure not to include `http://`") return + # log.debug(resp) if resp["isitdown"]: - await ctx.maybe_send_embed(f"{url_to_check} is DOWN!") + await ctx.maybe_send_embed(f"{url} is DOWN!") else: - await ctx.maybe_send_embed(f"{url_to_check} is UP!") + await ctx.maybe_send_embed(f"{url} is UP!") async def _check_if_down(self, url_to_check): - url = re.compile(r"https?://(www\.)?") - url.sub("", url_to_check).strip().strip("/") + re_compiled = re.compile(r"https?://(www\.)?") + url = re_compiled.sub("", url_to_check).strip().strip("/") url = f"https://isitdown.site/api/v3/{url}" + # log.debug(url) async with aiohttp.ClientSession() as session: async with session.get(url) as response: assert response.status == 200 resp = await response.json() - return resp + return resp, url From bf9115e13cf81279aeff4ce61255d2dbe6483992 Mon Sep 17 00:00:00 2001 From: bobloy Date: Wed, 10 Mar 2021 13:36:37 -0500 Subject: [PATCH 13/40] Update for new channel --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ec76ead..b1c3b73 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Check out *Deprecated* my V2 cogs at [Fox-Cogs v2](https://github.com/bobloy/Fox # Contact Get support on the [Third Party Cog Server](https://discord.gg/GET4DVk) -Feel free to @ me in the #support_othercogs channel +Feel free to @ me in the #support_fox-v3 channel Discord: Bobloy#6513 From 5a26b48fdacf6c360da4acddbedd081c8d171b74 Mon Sep 17 00:00:00 2001 From: Antoine Rybacki Date: Fri, 12 Mar 2021 23:57:56 +0100 Subject: [PATCH 14/40] [Chatter] Allow bot to reply to maintain conversation continuity --- chatter/chat.py | 34 ++++++++++++++++++++++++++++++++-- chatter/info.json | 2 +- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/chatter/chat.py b/chatter/chat.py index 41affb6..84e8fbb 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -53,7 +53,13 @@ class Chatter(Cog): self.bot = bot self.config = Config.get_conf(self, identifier=6710497116116101114) default_global = {} - default_guild = {"whitelist": None, "days": 1, "convo_delta": 15, "chatchannel": None} + default_guild = { + "whitelist": None, + "days": 1, + "convo_delta": 15, + "chatchannel": None, + "reply": False, + } path: pathlib.Path = cog_data_path(self) self.data_path = path / "database.sqlite3" @@ -213,6 +219,25 @@ class Chatter(Cog): await self.config.guild(ctx.guild).chatchannel.set(channel.id) await ctx.maybe_send_embed(f"Chat channel is now {channel.mention}") + @checks.admin() + @chatter.command(name="reply") + async def chatter_reply(self, ctx: commands.Context, toggle: Optional[bool] = None): + """ + Toggle bot reply to messages if conversation continuity is not present + + """ + reply = await self.config.guild(ctx.guild).reply() + if toggle is None: + toggle = not reply + await self.config.guild(ctx.guild).reply.set(toggle) + + if toggle: + await ctx.send("I will now respond to you if conversation continuity is not present") + else: + await ctx.send( + "I will not reply to your message if conversation continuity is not present, anymore" + ) + @checks.is_owner() @chatter.command(name="cleardata") async def chatter_cleardata(self, ctx: commands.Context, confirm: bool = False): @@ -493,7 +518,12 @@ class Chatter(Cog): async with channel.typing(): future = await self.loop.run_in_executor(None, self.chatbot.get_response, text) + replying = None + if await self.config.guild(guild).reply(): + if message != ctx.channel.last_message: + replying = message + if future and str(future): - await channel.send(str(future)) + await channel.send(str(future), reference=replying) else: await channel.send(":thinking:") diff --git a/chatter/info.json b/chatter/info.json index 85107ed..a3fe0da 100644 --- a/chatter/info.json +++ b/chatter/info.json @@ -2,7 +2,7 @@ "author": [ "Bobloy" ], - "min_bot_version": "3.4.0", + "min_bot_version": "3.4.6", "description": "Create an offline chatbot that talks like your average member using Machine Learning. See setup instructions at https://github.com/bobloy/Fox-V3/tree/master/chatter", "hidden": false, "install_msg": "Thank you for installing Chatter! Please make sure you check the install instructions at https://github.com/bobloy/Fox-V3/blob/master/chatter/README.md\nAfter that, get started ith `[p]load chatter` and `[p]help Chatter`", From cc199c395d7851614abdda469382760c75491654 Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 15 Mar 2021 08:16:20 -0400 Subject: [PATCH 15/40] Discord file now takes a path, so just give em a path. --- qrinvite/qrinvite.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qrinvite/qrinvite.py b/qrinvite/qrinvite.py index ab5f5dc..a91f7ab 100644 --- a/qrinvite/qrinvite.py +++ b/qrinvite/qrinvite.py @@ -96,8 +96,8 @@ class QRInvite(Cog): ) png_path: pathlib.Path = path / (ctx.guild.icon + "_qrcode.png") - with png_path.open("rb") as png_fp: - await ctx.send(file=discord.File(png_fp.read(), "qrcode.png")) + # with png_path.open("rb") as png_fp: + await ctx.send(file=discord.File(png_path, "qrcode.png")) def convert_webp_to_png(path): From f7dad0aa3f9d0e5a73778062314ba8621d490a4a Mon Sep 17 00:00:00 2001 From: Antoine Rybacki Date: Mon, 15 Mar 2021 14:25:46 +0100 Subject: [PATCH 16/40] [Chatter] Bot will respond to reply --- chatter/chat.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/chatter/chat.py b/chatter/chat.py index 84e8fbb..ed8b49d 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -500,7 +500,15 @@ class Chatter(Cog): # Thank you Cog-Creators channel: discord.TextChannel = message.channel - if guild is not None and channel.id == await self.config.guild(guild).chatchannel(): + # is_reply = False # this is only useful with in_response_to + if ( + message.reference is not None + and isinstance(message.reference.resolved,discord.Message) + and message.reference.resolved.author.id == self.bot.user.id + ): + # is_reply = True # this is only useful with in_response_to + pass # this is a reply to the bot, good to go + elif guild is not None and channel.id == await self.config.guild(guild).chatchannel(): pass # good to go else: when_mentionables = commands.when_mentioned(self.bot, message) From 42bdc640289d82318229ca3de0b8cff3dfe070dc Mon Sep 17 00:00:00 2001 From: Antoine Rybacki Date: Mon, 15 Mar 2021 14:29:34 +0100 Subject: [PATCH 17/40] Black format fix --- chatter/chat.py | 2 +- launchlib/launchlib.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/chatter/chat.py b/chatter/chat.py index ed8b49d..971492c 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -503,7 +503,7 @@ class Chatter(Cog): # is_reply = False # this is only useful with in_response_to if ( message.reference is not None - and isinstance(message.reference.resolved,discord.Message) + and isinstance(message.reference.resolved, discord.Message) and message.reference.resolved.author.id == self.bot.user.id ): # is_reply = True # this is only useful with in_response_to diff --git a/launchlib/launchlib.py b/launchlib/launchlib.py index 4994a1d..3d3eb0e 100644 --- a/launchlib/launchlib.py +++ b/launchlib/launchlib.py @@ -152,7 +152,9 @@ class LaunchLib(commands.Cog): if pad_name is not None: if location_url is not None: - location_url = re.sub("[^a-zA-Z0-9/:.'+\"°?=,-]", "", location_url) # Fix bad URLS + location_url = re.sub( + "[^a-zA-Z0-9/:.'+\"°?=,-]", "", location_url + ) # Fix bad URLS em.add_field(name="Launch Pad Name", value=f"[{pad_name}]({location_url})") else: em.add_field(name="Launch Pad Name", value=pad_name) From 920f8817d75c26df7f6042e181c3ddcab7702b49 Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 15 Mar 2021 10:15:13 -0400 Subject: [PATCH 18/40] Use guild.id and author.id for file name, support using jpg --- qrinvite/qrinvite.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/qrinvite/qrinvite.py b/qrinvite/qrinvite.py index a91f7ab..a4c377c 100644 --- a/qrinvite/qrinvite.py +++ b/qrinvite/qrinvite.py @@ -68,7 +68,7 @@ class QRInvite(Cog): extension = pathlib.Path(image_url).parts[-1].replace(".", "?").split("?")[1] path: pathlib.Path = cog_data_path(self) - image_path = path / (ctx.guild.icon + "." + extension) + image_path = path / f"{ctx.guild.id}-{ctx.author.id}.{extension}" async with aiohttp.ClientSession() as session: async with session.get(image_url) as response: image = await response.read() @@ -83,6 +83,8 @@ class QRInvite(Cog): return elif extension == "png": new_path = str(image_path) + elif extension == "jpg": + new_path = convert_jpg_to_png(str(image_path)) else: await ctx.maybe_send_embed(f"{extension} is not supported yet, stay tuned") return @@ -110,3 +112,10 @@ def convert_webp_to_png(path): new_path = path.replace(".webp", ".png") im.save(new_path, transparency=255) return new_path + + +def convert_jpg_to_png(path): + im = Image.open(path) + new_path = path.replace(".jpg", ".png") + im.save(new_path) + return new_path From 578ea4a555edbdd9d30a4221ed9ac145e1bd129f Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 15 Mar 2021 10:31:29 -0400 Subject: [PATCH 19/40] Consistently avoid guild.icon --- qrinvite/qrinvite.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/qrinvite/qrinvite.py b/qrinvite/qrinvite.py index a4c377c..684b69d 100644 --- a/qrinvite/qrinvite.py +++ b/qrinvite/qrinvite.py @@ -67,8 +67,10 @@ class QRInvite(Cog): extension = pathlib.Path(image_url).parts[-1].replace(".", "?").split("?")[1] + save_as_name = f"{ctx.guild.id}-{ctx.author.id}" + path: pathlib.Path = cog_data_path(self) - image_path = path / f"{ctx.guild.id}-{ctx.author.id}.{extension}" + image_path = path / f"{save_as_name}.{extension}" async with aiohttp.ClientSession() as session: async with session.get(image_url) as response: image = await response.read() @@ -77,27 +79,27 @@ class QRInvite(Cog): file.write(image) if extension == "webp": - new_path = convert_webp_to_png(str(image_path)) + new_image_path = convert_webp_to_png(str(image_path)) elif extension == "gif": await ctx.maybe_send_embed("gif is not supported yet, stay tuned") return elif extension == "png": - new_path = str(image_path) + new_image_path = str(image_path) elif extension == "jpg": - new_path = convert_jpg_to_png(str(image_path)) + new_image_path = convert_jpg_to_png(str(image_path)) else: await ctx.maybe_send_embed(f"{extension} is not supported yet, stay tuned") return myqr.run( invite, - picture=new_path, - save_name=ctx.guild.icon + "_qrcode.png", + picture=new_image_path, + save_name=f"{save_as_name}_qrcode.png", save_dir=str(cog_data_path(self)), colorized=colorized, ) - png_path: pathlib.Path = path / (ctx.guild.icon + "_qrcode.png") + png_path: pathlib.Path = path / f"{save_as_name}_qrcode.png" # with png_path.open("rb") as png_fp: await ctx.send(file=discord.File(png_path, "qrcode.png")) From d32de1586ffb35c3b052d53044fdd89773ef41dc Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 15 Mar 2021 15:40:25 -0400 Subject: [PATCH 20/40] Reply enabled by default cause it's cool. --- chatter/chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatter/chat.py b/chatter/chat.py index 971492c..1419bbf 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -58,7 +58,7 @@ class Chatter(Cog): "days": 1, "convo_delta": 15, "chatchannel": None, - "reply": False, + "reply": True, } path: pathlib.Path = cog_data_path(self) self.data_path = path / "database.sqlite3" From 8acbc5d9645e1e23e65d60eb00e929d202c4a3e5 Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 15 Mar 2021 15:48:34 -0400 Subject: [PATCH 21/40] Whatever this commit is --- chatter/chat.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chatter/chat.py b/chatter/chat.py index e29c317..500284c 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -620,7 +620,9 @@ class Chatter(Cog): replying = message if future and str(future): - self._last_message_per_channel[ctx.channel.id] = await channel.send(str(future), reference=replying) + self._last_message_per_channel[ctx.channel.id] = await channel.send( + str(future), reference=replying + ) else: await ctx.send(":thinking:") From 7811c71edbcb7b059e0181fc87cfb3934ba95c53 Mon Sep 17 00:00:00 2001 From: bobloy Date: Tue, 16 Mar 2021 16:00:42 -0400 Subject: [PATCH 22/40] Use is_reply to train --- chatter/chat.py | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/chatter/chat.py b/chatter/chat.py index 500284c..81d09a8 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -564,13 +564,13 @@ class Chatter(Cog): # Thank you Cog-Creators channel: discord.TextChannel = message.channel - # is_reply = False # this is only useful with in_response_to + is_reply = False # this is only useful with in_response_to if ( message.reference is not None and isinstance(message.reference.resolved, discord.Message) and message.reference.resolved.author.id == self.bot.user.id ): - # is_reply = True # this is only useful with in_response_to + is_reply = True # this is only useful with in_response_to pass # this is a reply to the bot, good to go elif guild is not None and channel.id == await self.config.guild(guild).chatchannel(): pass # good to go @@ -592,7 +592,9 @@ class Chatter(Cog): if not self._guild_cache[ctx.guild.id]: self._guild_cache[ctx.guild.id] = await self.config.guild(ctx.guild).all() - if self._last_message_per_channel[ctx.channel.id] is not None: + if is_reply: + in_response_to = message.reference.resolved.content + elif self._last_message_per_channel[ctx.channel.id] is not None: last_m: discord.Message = self._last_message_per_channel[ctx.channel.id] minutes = self._guild_cache[ctx.guild.id]["convo_delta"] if (datetime.utcnow() - last_m.created_at).seconds > minutes * 60: @@ -602,16 +604,25 @@ class Chatter(Cog): else: in_response_to = None - if in_response_to is None: - log.debug("Generating response") - Statement = self.chatbot.storage.get_object("statement") - future = await self.loop.run_in_executor( - None, self.chatbot.generate_response, Statement(text) - ) - else: - log.debug("Getting response") - future = await self.loop.run_in_executor( - None, partial(self.chatbot.get_response, text, in_response_to=in_response_to) + # Always use generate reponse + # Chatterbot tries to learn based on the result it comes up with, which is dumb + log.debug("Generating response") + Statement = self.chatbot.storage.get_object("statement") + future = await self.loop.run_in_executor( + None, self.chatbot.generate_response, Statement(text) + ) + + if in_response_to is not None: + log.debug("learning response") + learning_task = asyncio.create_task( + self.loop.run_in_executor( + None, + partial( + self.chatbot.learn_response, + Statement(text), + previous_statement=in_response_to, + ), + ) ) replying = None From 0475b18437845b2de1db44062340e9706d687b1c Mon Sep 17 00:00:00 2001 From: Sourcery AI Date: Thu, 18 Mar 2021 17:20:04 +0000 Subject: [PATCH 23/40] 'Refactored by Sourcery' --- announcedaily/announcedaily.py | 3 +- audiotrivia/audiotrivia.py | 2 +- ccrole/ccrole.py | 12 ++-- chatter/chat.py | 3 +- coglint/coglint.py | 6 +- conquest/conquest.py | 10 ++- conquest/mapmaker.py | 3 +- conquest/regioner.py | 2 +- exclusiverole/exclusiverole.py | 7 +- fifo/fifo.py | 15 ++--- flag/flag.py | 3 +- hangman/hangman.py | 9 +-- infochannel/infochannel.py | 10 +-- launchlib/launchlib.py | 61 +---------------- leaver/leaver.py | 5 +- lseen/lseen.py | 6 +- planttycoon/planttycoon.py | 115 +++++++++++++++------------------ reactrestrict/reactrestrict.py | 8 ++- rpsls/rpsls.py | 11 ++-- stealemoji/stealemoji.py | 60 +++++++++-------- timerole/timerole.py | 7 +- unicode/unicode.py | 3 +- werewolf/builder.py | 9 +-- werewolf/game.py | 26 ++++---- werewolf/werewolf.py | 9 +-- 25 files changed, 149 insertions(+), 256 deletions(-) diff --git a/announcedaily/announcedaily.py b/announcedaily/announcedaily.py index aa50e6c..98690f7 100644 --- a/announcedaily/announcedaily.py +++ b/announcedaily/announcedaily.py @@ -54,8 +54,7 @@ class AnnounceDaily(Cog): Do `[p]help annd ` for more details """ - if ctx.invoked_subcommand is None: - pass + pass @commands.command() @checks.guildowner() diff --git a/audiotrivia/audiotrivia.py b/audiotrivia/audiotrivia.py index 9617f32..73eca95 100644 --- a/audiotrivia/audiotrivia.py +++ b/audiotrivia/audiotrivia.py @@ -168,7 +168,7 @@ class AudioTrivia(Trivia): @commands.guild_only() async def audiotrivia_list(self, ctx: commands.Context): """List available trivia including audio categories.""" - lists = set(p.stem for p in self._all_audio_lists()) + lists = {p.stem for p in self._all_audio_lists()} if await ctx.embed_requested(): await ctx.send( embed=discord.Embed( diff --git a/ccrole/ccrole.py b/ccrole/ccrole.py index 5248766..e3691f8 100644 --- a/ccrole/ccrole.py +++ b/ccrole/ccrole.py @@ -48,8 +48,7 @@ class CCRole(commands.Cog): """Custom commands management with roles Highly customizable custom commands with role management.""" - if not ctx.invoked_subcommand: - pass + pass @ccrole.command(name="add") @checks.mod_or_permissions(administrator=True) @@ -228,7 +227,8 @@ class CCRole(commands.Cog): if not role_list: return "None" return ", ".join( - [discord.utils.get(ctx.guild.roles, id=roleid).name for roleid in role_list] + discord.utils.get(ctx.guild.roles, id=roleid).name + for roleid in role_list ) embed.add_field(name="Text", value="```{}```".format(cmd["text"]), inline=False) @@ -252,7 +252,7 @@ class CCRole(commands.Cog): ) return - cmd_list = ", ".join([ctx.prefix + c for c in sorted(cmd_list.keys())]) + cmd_list = ", ".join(ctx.prefix + c for c in sorted(cmd_list.keys())) cmd_list = "Custom commands:\n\n" + cmd_list if ( @@ -325,8 +325,8 @@ class CCRole(commands.Cog): async def eval_cc(self, cmd, message: discord.Message, ctx: commands.Context): """Does all the work""" - if cmd["proles"] and not ( - set(role.id for role in message.author.roles) & set(cmd["proles"]) + if cmd["proles"] and not {role.id for role in message.author.roles} & set( + cmd["proles"] ): log.debug(f"{message.author} missing required role to execute {ctx.invoked_with}") return # Not authorized, do nothing diff --git a/chatter/chat.py b/chatter/chat.py index 1419bbf..de0e20a 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -196,8 +196,7 @@ class Chatter(Cog): """ Base command for this cog. Check help for the commands list. """ - if ctx.invoked_subcommand is None: - pass + pass @checks.admin() @chatter.command(name="channel") diff --git a/coglint/coglint.py b/coglint/coglint.py index 6595980..9c4739c 100644 --- a/coglint/coglint.py +++ b/coglint/coglint.py @@ -58,11 +58,7 @@ class CogLint(Cog): future = await self.bot.loop.run_in_executor(None, lint.py_run, path, "return_std=True") - if future: - (pylint_stdout, pylint_stderr) = future - else: - (pylint_stdout, pylint_stderr) = None, None - + (pylint_stdout, pylint_stderr) = future or (None, None) # print(pylint_stderr) # print(pylint_stdout) diff --git a/conquest/conquest.py b/conquest/conquest.py index fdf5e96..fa70911 100644 --- a/conquest/conquest.py +++ b/conquest/conquest.py @@ -67,9 +67,8 @@ class Conquest(commands.Cog): """ Base command for conquest cog. Start with `[p]conquest set map` to select a map. """ - if ctx.invoked_subcommand is None: - if self.current_map is not None: - await self._conquest_current(ctx) + if ctx.invoked_subcommand is None and self.current_map is not None: + await self._conquest_current(ctx) @conquest.command(name="list") async def _conquest_list(self, ctx: commands.Context): @@ -80,14 +79,13 @@ class Conquest(commands.Cog): with maps_json.open() as maps: maps_json = json.load(maps) - map_list = "\n".join(map_name for map_name in maps_json["maps"]) + map_list = "\n".join(maps_json["maps"]) await ctx.maybe_send_embed(f"Current maps:\n{map_list}") @conquest.group(name="set") async def conquest_set(self, ctx: commands.Context): """Base command for admin actions like selecting a map""" - if ctx.invoked_subcommand is None: - pass + pass @conquest_set.command(name="resetzoom") async def _conquest_set_resetzoom(self, ctx: commands.Context): diff --git a/conquest/mapmaker.py b/conquest/mapmaker.py index 0cde96a..5fd90b2 100644 --- a/conquest/mapmaker.py +++ b/conquest/mapmaker.py @@ -30,8 +30,7 @@ class MapMaker(commands.Cog): """ Base command for managing current maps or creating new ones """ - if ctx.invoked_subcommand is None: - pass + pass @mapmaker.command(name="upload") async def _mapmaker_upload(self, ctx: commands.Context, map_path=""): diff --git a/conquest/regioner.py b/conquest/regioner.py index dc77373..b89bc5f 100644 --- a/conquest/regioner.py +++ b/conquest/regioner.py @@ -65,7 +65,7 @@ def floodfill(image, xy, value, border=None, thresh=0) -> set: if border is None: fill = _color_diff(p, background) <= thresh else: - fill = p != value and p != border + fill = p not in [value, border] if fill: pixel[s, t] = value new_edge.add((s, t)) diff --git a/exclusiverole/exclusiverole.py b/exclusiverole/exclusiverole.py index 19635b2..63b7460 100644 --- a/exclusiverole/exclusiverole.py +++ b/exclusiverole/exclusiverole.py @@ -27,8 +27,7 @@ class ExclusiveRole(Cog): async def exclusive(self, ctx): """Base command for managing exclusive roles""" - if not ctx.invoked_subcommand: - pass + pass @exclusive.command(name="add") @checks.mod_or_permissions(administrator=True) @@ -85,7 +84,7 @@ class ExclusiveRole(Cog): if role_set is None: role_set = set(await self.config.guild(member.guild).role_list()) - member_set = set([role.id for role in member.roles]) + member_set = {role.id for role in member.roles} to_remove = (member_set - role_set) - {member.guild.default_role.id} if to_remove and member_set & role_set: @@ -103,7 +102,7 @@ class ExclusiveRole(Cog): await asyncio.sleep(1) role_set = set(await self.config.guild(after.guild).role_list()) - member_set = set([role.id for role in after.roles]) + member_set = {role.id for role in after.roles} if role_set & member_set: try: diff --git a/fifo/fifo.py b/fifo/fifo.py index d152609..24d01f3 100644 --- a/fifo/fifo.py +++ b/fifo/fifo.py @@ -68,10 +68,7 @@ class CapturePrint: self.string = None def write(self, string): - if self.string is None: - self.string = string - else: - self.string = self.string + "\n" + string + self.string = string if self.string is None else self.string + "\n" + string class FIFO(commands.Cog): @@ -197,8 +194,8 @@ class FIFO(commands.Cog): async def _get_tz(self, user: Union[discord.User, discord.Member]) -> Union[None, tzinfo]: if self.tz_cog is None: self.tz_cog = self.bot.get_cog("Timezone") - if self.tz_cog is None: - self.tz_cog = False # only try once to get the timezone cog + if self.tz_cog is None: + self.tz_cog = False # only try once to get the timezone cog if not self.tz_cog: return None @@ -230,8 +227,7 @@ class FIFO(commands.Cog): """ Base command for handling scheduling of tasks """ - if ctx.invoked_subcommand is None: - pass + pass @fifo.command(name="wakeup") async def fifo_wakeup(self, ctx: commands.Context): @@ -522,8 +518,7 @@ class FIFO(commands.Cog): """ Add a new trigger for a task from the current guild. """ - if ctx.invoked_subcommand is None: - pass + pass @fifo_trigger.command(name="interval") async def fifo_trigger_interval( diff --git a/flag/flag.py b/flag/flag.py index 10f0334..e267297 100644 --- a/flag/flag.py +++ b/flag/flag.py @@ -55,8 +55,7 @@ class Flag(Cog): """ Commands for managing Flag settings """ - if ctx.invoked_subcommand is None: - pass + pass @flagset.command(name="expire") async def flagset_expire(self, ctx: commands.Context, days: int): diff --git a/hangman/hangman.py b/hangman/hangman.py index 2b6ab07..d737aea 100644 --- a/hangman/hangman.py +++ b/hangman/hangman.py @@ -147,8 +147,7 @@ class Hangman(Cog): @checks.mod_or_permissions(administrator=True) async def hangset(self, ctx): """Adjust hangman settings""" - if ctx.invoked_subcommand is None: - pass + pass @hangset.command() async def face(self, ctx: commands.Context, theface): @@ -250,7 +249,7 @@ class Hangman(Cog): self.winbool[guild] = True for i in self.the_data[guild]["answer"]: - if i == " " or i == "-": + if i in [" ", "-"]: out_str += i * 2 elif i in self.the_data[guild]["guesses"] or i not in "ABCDEFGHIJKLMNOPQRSTUVWXYZ": out_str += "__" + i + "__ " @@ -262,9 +261,7 @@ class Hangman(Cog): def _guesslist(self, guild): """Returns the current letter list""" - out_str = "" - for i in self.the_data[guild]["guesses"]: - out_str += str(i) + "," + out_str = "".join(str(i) + "," for i in self.the_data[guild]["guesses"]) out_str = out_str[:-1] return out_str diff --git a/infochannel/infochannel.py b/infochannel/infochannel.py index 33e2b10..fe8589f 100644 --- a/infochannel/infochannel.py +++ b/infochannel/infochannel.py @@ -65,9 +65,12 @@ class InfoChannel(Cog): "offline": "Offline: {count}", } - default_channel_ids = {k: None for k in self.default_channel_names.keys()} + default_channel_ids = {k: None for k in self.default_channel_names} # Only members is enabled by default - default_enabled_counts = {k: k == "members" for k in self.default_channel_names.keys()} + default_enabled_counts = { + k: k == "members" for k in self.default_channel_names + } + default_guild = { "category_id": None, @@ -159,8 +162,7 @@ class InfoChannel(Cog): """ Toggle different types of infochannels """ - if not ctx.invoked_subcommand: - pass + pass @infochannelset.command(name="togglechannel") async def _infochannelset_togglechannel( diff --git a/launchlib/launchlib.py b/launchlib/launchlib.py index 3d3eb0e..2a30c3e 100644 --- a/launchlib/launchlib.py +++ b/launchlib/launchlib.py @@ -36,58 +36,6 @@ class LaunchLib(commands.Cog): async def _embed_launch_data(self, launch: ll.AsyncLaunch): - if False: - example_launch = ll.AsyncLaunch( - id="9279744e-46b2-4eca-adea-f1379672ec81", - name="Atlas LV-3A | Samos 2", - tbddate=False, - tbdtime=False, - status={"id": 3, "name": "Success"}, - inhold=False, - windowstart="1961-01-31 20:21:19+00:00", - windowend="1961-01-31 20:21:19+00:00", - net="1961-01-31 20:21:19+00:00", - info_urls=[], - vid_urls=[], - holdreason=None, - failreason=None, - probability=0, - hashtag=None, - agency=None, - changed=None, - pad=ll.Pad( - id=93, - name="Space Launch Complex 3W", - latitude=34.644, - longitude=-120.593, - map_url="http://maps.google.com/maps?q=34.644+N,+120.593+W", - retired=None, - total_launch_count=3, - agency_id=161, - wiki_url=None, - info_url=None, - location=ll.Location( - id=11, - name="Vandenberg AFB, CA, USA", - country_code="USA", - total_launch_count=83, - total_landing_count=3, - pads=None, - ), - map_image="https://spacelaunchnow-prod-east.nyc3.digitaloceanspaces.com/media/launch_images/pad_93_20200803143225.jpg", - ), - rocket=ll.Rocket( - id=2362, - name=None, - default_pads=None, - family=None, - wiki_url=None, - info_url=None, - image_url=None, - ), - missions=None, - ) - # status: ll.AsyncLaunchStatus = await launch.get_status() status = launch.status @@ -102,11 +50,7 @@ class LaunchLib(commands.Cog): if launch.pad: urls += [launch.pad.info_url, launch.pad.wiki_url] - if urls: - url = next((url for url in urls if urls is not None), None) - else: - url = None - + url = next((url for url in urls if urls is not None), None) if urls else None color = discord.Color.green() if status["id"] in [1, 3] else discord.Color.red() em = discord.Embed(title=title, description=description, url=url, color=color) @@ -171,8 +115,7 @@ class LaunchLib(commands.Cog): @commands.group() async def launchlib(self, ctx: commands.Context): """Base command for getting launches""" - if ctx.invoked_subcommand is None: - pass + pass @launchlib.command() async def next(self, ctx: commands.Context, num_launches: int = 1): diff --git a/leaver/leaver.py b/leaver/leaver.py index 0c0d947..76f29f5 100644 --- a/leaver/leaver.py +++ b/leaver/leaver.py @@ -25,8 +25,7 @@ class Leaver(Cog): @checks.mod_or_permissions(administrator=True) async def leaverset(self, ctx): """Adjust leaver settings""" - if ctx.invoked_subcommand is None: - pass + pass @leaverset.command() async def channel(self, ctx: Context): @@ -57,5 +56,3 @@ class Leaver(Cog): ) else: await channel.send(out) - else: - pass diff --git a/lseen/lseen.py b/lseen/lseen.py index 69bcf87..2ddb0e1 100644 --- a/lseen/lseen.py +++ b/lseen/lseen.py @@ -45,14 +45,12 @@ class LastSeen(Cog): @staticmethod def get_date_time(s): - d = dateutil.parser.parse(s) - return d + return dateutil.parser.parse(s) @commands.group(aliases=["setlseen"], name="lseenset") async def lset(self, ctx: commands.Context): """Change settings for lseen""" - if ctx.invoked_subcommand is None: - pass + pass @lset.command(name="toggle") async def lset_toggle(self, ctx: commands.Context): diff --git a/planttycoon/planttycoon.py b/planttycoon/planttycoon.py index 4209b53..54d0119 100644 --- a/planttycoon/planttycoon.py +++ b/planttycoon/planttycoon.py @@ -111,9 +111,8 @@ async def _withdraw_points(gardener: Gardener, amount): if (gardener.points - amount) < 0: return False - else: - gardener.points -= amount - return True + gardener.points -= amount + return True class PlantTycoon(commands.Cog): @@ -245,13 +244,12 @@ class PlantTycoon(commands.Cog): await self._load_plants_products() modifiers = sum( - [ - self.products[product]["modifier"] - for product in gardener.products - if gardener.products[product] > 0 - ] + self.products[product]["modifier"] + for product in gardener.products + if gardener.products[product] > 0 ) + degradation = ( 100 / (gardener.current["time"] / 60) @@ -290,38 +288,33 @@ class PlantTycoon(commands.Cog): product = product.lower() product_category = product_category.lower() 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"] - gardener.products[product] -= 1 - if gardener.products[product] == 0: - del gardener.products[product.lower()] - if product_category == "water": - emoji = ":sweat_drops:" - elif product_category == "fertilizer": - emoji = ":poop:" - # elif product_category == "tool": - else: - emoji = ":scissors:" - message = "Your plant got some health back! {}".format(emoji) - if gardener.current["health"] > gardener.current["threshold"]: - gardener.current["health"] -= self.products[product]["damage"] - if product_category == "tool": - damage_msg = "You used {} too many times!".format(product) - else: - damage_msg = "You gave too much of {}.".format(product) - message = "{} Your plant lost some health. :wilted_rose:".format( - damage_msg - ) - gardener.points += self.defaults["points"]["add_health"] - await gardener.save_gardener() + if product in gardener.products and gardener.products[product] > 0: + gardener.current["health"] += self.products[product]["health"] + gardener.products[product] -= 1 + if gardener.products[product] == 0: + del gardener.products[product.lower()] + if product_category == "fertilizer": + emoji = ":poop:" + elif product_category == "water": + emoji = ":sweat_drops:" else: - message = "You have no {}. Go buy some!".format(product) + emoji = ":scissors:" + message = "Your plant got some health back! {}".format(emoji) + if gardener.current["health"] > gardener.current["threshold"]: + gardener.current["health"] -= self.products[product]["damage"] + if product_category == "tool": + damage_msg = "You used {} too many times!".format(product) + else: + damage_msg = "You gave too much of {}.".format(product) + message = "{} Your plant lost some health. :wilted_rose:".format( + damage_msg + ) + gardener.points += self.defaults["points"]["add_health"] + await gardener.save_gardener() + elif product in gardener.products or product_category != "tool": + message = "You have no {}. Go buy some!".format(product) else: - if product_category == "tool": - message = "You don't have a {}. Go buy one!".format(product) - else: - message = "You have no {}. Go buy some!".format(product) + message = "You don't have a {}. Go buy one!".format(product) else: message = "Are you sure you are using {}?".format(product_category) @@ -412,24 +405,18 @@ class PlantTycoon(commands.Cog): gardener.current = plant await gardener.save_gardener() - em = discord.Embed(description=message, color=discord.Color.green()) else: plant = gardener.current message = "You're already growing {} **{}**, silly.".format( plant["article"], plant["name"] ) - em = discord.Embed(description=message, color=discord.Color.green()) - + em = discord.Embed(description=message, color=discord.Color.green()) await ctx.send(embed=em) @_gardening.command(name="profile") async def _profile(self, ctx: commands.Context, *, member: discord.Member = None): """Check your gardening profile.""" - if member is not None: - author = member - else: - author = ctx.author - + author = member if member is not None else ctx.author gardener = await self._gardener(author) try: await self._apply_degradation(gardener) @@ -440,9 +427,7 @@ class PlantTycoon(commands.Cog): 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.add_field(name="**Thneeds**", value=str(gardener.points)) - if not gardener.current: - em.add_field(name="**Currently growing**", value="None") - else: + if gardener.current: em.set_thumbnail(url=gardener.current["image"]) em.add_field( name="**Currently growing**", @@ -450,16 +435,17 @@ class PlantTycoon(commands.Cog): gardener.current["name"], gardener.current["health"] ), ) + else: + em.add_field(name="**Currently growing**", value="None") if not gardener.badges: em.add_field(name="**Badges**", value="None") else: - badges = "" - for badge in gardener.badges: - badges += "{}\n".format(badge.capitalize()) + badges = "".join( + "{}\n".format(badge.capitalize()) for badge in gardener.badges + ) + em.add_field(name="**Badges**", value=badges) - if not gardener.products: - em.add_field(name="**Products**", value="None") - else: + if gardener.products: products = "" for product_name, product_data in gardener.products.items(): if self.products[product_name] is None: @@ -470,6 +456,8 @@ class PlantTycoon(commands.Cog): self.products[product_name]["modifier"], ) em.add_field(name="**Products**", value=products) + else: + em.add_field(name="**Products**", value="None") if gardener.current: degradation = await self._degradation(gardener) die_in = await _die_in(gardener, degradation) @@ -600,7 +588,6 @@ class PlantTycoon(commands.Cog): self.products[pd]["category"], ), ) - await ctx.send(embed=em) else: if amount <= 0: message = "Invalid amount! Must be greater than 1" @@ -629,7 +616,8 @@ class PlantTycoon(commands.Cog): else: message = "I don't have this product." em = discord.Embed(description=message, color=discord.Color.green()) - await ctx.send(embed=em) + + await ctx.send(embed=em) @_gardening.command(name="convert") async def _convert(self, ctx: commands.Context, amount: int): @@ -663,8 +651,7 @@ class PlantTycoon(commands.Cog): else: gardener.current = {} message = "You successfully shovelled your plant out." - if gardener.points < 0: - gardener.points = 0 + gardener.points = max(gardener.points, 0) await gardener.save_gardener() em = discord.Embed(description=message, color=discord.Color.dark_grey()) @@ -681,12 +668,12 @@ class PlantTycoon(commands.Cog): except discord.Forbidden: # Couldn't DM the degradation await ctx.send("ERROR\nYou blocked me, didn't you?") - product = "water" - product_category = "water" if not gardener.current: message = "You're currently not growing a plant." await _send_message(channel, message) else: + product = "water" + product_category = "water" await self._add_health(channel, gardener, product, product_category) @commands.command(name="fertilize") @@ -700,11 +687,11 @@ class PlantTycoon(commands.Cog): await ctx.send("ERROR\nYou blocked me, didn't you?") channel = ctx.channel product = fertilizer - product_category = "fertilizer" if not gardener.current: message = "You're currently not growing a plant." await _send_message(channel, message) else: + product_category = "fertilizer" await self._add_health(channel, gardener, product, product_category) @commands.command(name="prune") @@ -717,12 +704,12 @@ class PlantTycoon(commands.Cog): # Couldn't DM the degradation await ctx.send("ERROR\nYou blocked me, didn't you?") channel = ctx.channel - product = "pruner" - product_category = "tool" if not gardener.current: message = "You're currently not growing a plant." await _send_message(channel, message) else: + product = "pruner" + product_category = "tool" await self._add_health(channel, gardener, product, product_category) # async def check_degradation(self): diff --git a/reactrestrict/reactrestrict.py b/reactrestrict/reactrestrict.py index 79c3c1c..8329c46 100644 --- a/reactrestrict/reactrestrict.py +++ b/reactrestrict/reactrestrict.py @@ -98,9 +98,12 @@ class ReactRestrict(Cog): current_combos = await self.combo_list() to_keep = [ - c for c in current_combos if not (c.message_id == message_id and c.role_id == role.id) + c + for c in current_combos + if c.message_id != message_id or c.role_id != role.id ] + if to_keep != current_combos: await self.set_combo_list(to_keep) @@ -210,8 +213,7 @@ class ReactRestrict(Cog): """ Base command for this cog. Check help for the commands list. """ - if ctx.invoked_subcommand is None: - pass + pass @reactrestrict.command() async def add(self, ctx: commands.Context, message_id: int, *, role: discord.Role): diff --git a/rpsls/rpsls.py b/rpsls/rpsls.py index ed2e1dc..bada2c1 100644 --- a/rpsls/rpsls.py +++ b/rpsls/rpsls.py @@ -69,13 +69,12 @@ class RPSLS(Cog): def get_emote(self, choice): if choice == "rock": - emote = ":moyai:" + return ":moyai:" elif choice == "spock": - emote = ":vulcan:" + return ":vulcan:" elif choice == "paper": - emote = ":page_facing_up:" + return ":page_facing_up:" elif choice in ["scissors", "lizard"]: - emote = ":{}:".format(choice) + return ":{}:".format(choice) else: - emote = None - return emote + return None diff --git a/stealemoji/stealemoji.py b/stealemoji/stealemoji.py index 8f32d74..be9903c 100644 --- a/stealemoji/stealemoji.py +++ b/stealemoji/stealemoji.py @@ -69,8 +69,7 @@ class StealEmoji(Cog): """ Base command for this cog. Check help for the commands list. """ - if ctx.invoked_subcommand is None: - pass + pass @checks.is_owner() @stealemoji.command(name="clearemojis") @@ -268,37 +267,36 @@ class StealEmoji(Cog): break if guildbank is None: - if await self.config.autobank(): - try: - guildbank: discord.Guild = await self.bot.create_guild( - "StealEmoji Guildbank", code="S93bqTqKQ9rM" - ) - except discord.HTTPException: - await self.config.autobank.set(False) - log.exception("Unable to create guilds, disabling autobank") - return - async with self.config.guildbanks() as guildbanks: - guildbanks.append(guildbank.id) - # Track generated guilds for easier deletion - async with self.config.autobanked_guilds() as autobanked_guilds: - autobanked_guilds.append(guildbank.id) - - await asyncio.sleep(2) - - if guildbank.text_channels: - channel = guildbank.text_channels[0] - else: - # Always hits the else. - # Maybe create_guild doesn't return guild object with - # the template channel? - channel = await guildbank.create_text_channel("invite-channel") - invite = await channel.create_invite() - - await self.bot.send_to_owners(invite) - log.info(f"Guild created id {guildbank.id}. Invite: {invite}") - else: + if not await self.config.autobank(): + return + + try: + guildbank: discord.Guild = await self.bot.create_guild( + "StealEmoji Guildbank", code="S93bqTqKQ9rM" + ) + except discord.HTTPException: + await self.config.autobank.set(False) + log.exception("Unable to create guilds, disabling autobank") return + async with self.config.guildbanks() as guildbanks: + guildbanks.append(guildbank.id) + # Track generated guilds for easier deletion + async with self.config.autobanked_guilds() as autobanked_guilds: + autobanked_guilds.append(guildbank.id) + await asyncio.sleep(2) + + if guildbank.text_channels: + channel = guildbank.text_channels[0] + else: + # Always hits the else. + # Maybe create_guild doesn't return guild object with + # the template channel? + channel = await guildbank.create_text_channel("invite-channel") + invite = await channel.create_invite() + + await self.bot.send_to_owners(invite) + log.info(f"Guild created id {guildbank.id}. Invite: {invite}") # Next, have I saved this emoji before (because uploaded emoji != orignal emoji) if str(emoji.id) in await self.config.stolemoji(): diff --git a/timerole/timerole.py b/timerole/timerole.py index 714bcc8..b3fe843 100644 --- a/timerole/timerole.py +++ b/timerole/timerole.py @@ -77,8 +77,7 @@ class Timerole(Cog): @commands.guild_only() async def timerole(self, ctx): """Adjust timerole settings""" - if ctx.invoked_subcommand is None: - pass + pass @timerole.command() async def addrole( @@ -201,7 +200,7 @@ class Timerole(Cog): reapply = all_guilds[guild_id]["reapply"] role_dict = all_guilds[guild_id]["roles"] - if not any(role_data for role_data in role_dict.values()): # No roles + if not any(role_dict.values()): # No roles log.debug(f"No roles are configured for guild: {guild}") continue @@ -232,7 +231,7 @@ class Timerole(Cog): log.debug(f"{member.display_name} - Not time to check again yet") continue member: discord.Member - has_roles = set(r.id for r in member.roles) + has_roles = {r.id for r in member.roles} # Stop if they currently have or don't have the role, and mark had_role if (int(role_id) in has_roles and not role_data["remove"]) or ( diff --git a/unicode/unicode.py b/unicode/unicode.py index 4705f5d..297546b 100644 --- a/unicode/unicode.py +++ b/unicode/unicode.py @@ -19,8 +19,7 @@ class Unicode(Cog): @commands.group(name="unicode", pass_context=True) async def unicode(self, ctx): """Encode/Decode a Unicode character.""" - if ctx.invoked_subcommand is None: - pass + pass @unicode.command() async def decode(self, ctx: commands.Context, character): diff --git a/werewolf/builder.py b/werewolf/builder.py index da85b40..28098be 100644 --- a/werewolf/builder.py +++ b/werewolf/builder.py @@ -90,7 +90,7 @@ async def parse_code(code, game): if len(built) < digits: built += c - if built == "T" or built == "W" or built == "N": + if built in ["T", "W", "N"]: # Random Towns category = built built = "" @@ -116,8 +116,6 @@ async def parse_code(code, game): options = [role for role in ROLE_LIST if 10 + idx in role.category] elif category == "N": options = [role for role in ROLE_LIST if 20 + idx in role.category] - pass - if not options: raise IndexError("No Match Found") @@ -130,11 +128,8 @@ async def parse_code(code, game): async def encode(role_list, rand_roles): """Convert role list to code""" - out_code = "" - digit_sort = sorted(role for role in role_list if role < 10) - for role in digit_sort: - out_code += str(role) + out_code = "".join(str(role) for role in digit_sort) digit_sort = sorted(role for role in role_list if 10 <= role < 100) if digit_sort: diff --git a/werewolf/game.py b/werewolf/game.py index 668bf16..949381c 100644 --- a/werewolf/game.py +++ b/werewolf/game.py @@ -526,9 +526,10 @@ class Game: async def _notify(self, event_name, **kwargs): for i in range(1, 7): # action guide 1-6 (0 is no action) - tasks = [] - for event in self.listeners.get(event_name, {}).get(i, []): - tasks.append(asyncio.create_task(event(**kwargs))) + tasks = [ + asyncio.create_task(event(**kwargs)) + for event in self.listeners.get(event_name, {}).get(i, []) + ] # Run same-priority task simultaneously await asyncio.gather(*tasks) @@ -555,10 +556,7 @@ class Game: async def generate_targets(self, channel, with_roles=False): embed = discord.Embed(title="Remaining Players", description="[ID] - [Name]") for i, player in enumerate(self.players): - if player.alive: - status = "" - else: - status = "*[Dead]*-" + status = "" if player.alive else "*[Dead]*-" if with_roles or not player.alive: embed.add_field( name=f"{i} - {status}{player.member.display_name}", @@ -579,7 +577,7 @@ class Game: if channel_id not in self.p_channels: self.p_channels[channel_id] = self.default_secret_channel.copy() - for x in range(10): # Retry 10 times + for _ in range(10): # Retry 10 times try: await asyncio.sleep(1) # This will have multiple calls self.p_channels[channel_id]["players"].append(role.player) @@ -706,9 +704,7 @@ class Game: if not self.any_votes_remaining: await channel.send("Voting is not allowed right now") return - elif channel.name in self.p_channels: - pass - else: + elif channel.name not in self.p_channels: # Not part of the game await channel.send("Cannot vote in this channel") return @@ -757,14 +753,14 @@ class Game: await self._at_voted(target) async def eval_results(self, target, source=None, method=None): - if method is not None: - out = "**{ID}** - " + method - return out.format(ID=target.id, target=target.member.display_name) - else: + if method is None: return "**{ID}** - {target} the {role} was found dead".format( ID=target.id, target=target.member.display_name, role=await target.role.get_role() ) + out = "**{ID}** - " + method + return out.format(ID=target.id, target=target.member.display_name) + async def _quit(self, player): """ Have player quit the game diff --git a/werewolf/werewolf.py b/werewolf/werewolf.py index a4083a9..903bb54 100644 --- a/werewolf/werewolf.py +++ b/werewolf/werewolf.py @@ -75,8 +75,7 @@ class Werewolf(Cog): """ Base command to adjust settings. Check help for command list. """ - if ctx.invoked_subcommand is None: - pass + pass @commands.guild_only() @wwset.command(name="list") @@ -166,8 +165,7 @@ class Werewolf(Cog): """ Base command for this cog. Check help for the commands list. """ - if ctx.invoked_subcommand is None: - pass + pass @commands.guild_only() @ww.command(name="new") @@ -348,8 +346,7 @@ class Werewolf(Cog): """ Find custom roles by name, alignment, category, or ID """ - if ctx.invoked_subcommand is None or ctx.invoked_subcommand == self.ww_search: - pass + pass @ww_search.command(name="name") async def ww_search_name(self, ctx: commands.Context, *, name): From ea88addc423eaf52cee261ed79cdad261d00a0be Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 18 Mar 2021 14:18:38 -0400 Subject: [PATCH 24/40] black refactoring --- ccrole/ccrole.py | 7 ++----- infochannel/infochannel.py | 5 +---- planttycoon/planttycoon.py | 9 ++------- reactrestrict/reactrestrict.py | 7 +------ 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/ccrole/ccrole.py b/ccrole/ccrole.py index e3691f8..5d1e40b 100644 --- a/ccrole/ccrole.py +++ b/ccrole/ccrole.py @@ -227,8 +227,7 @@ class CCRole(commands.Cog): if not role_list: return "None" return ", ".join( - discord.utils.get(ctx.guild.roles, id=roleid).name - for roleid in role_list + discord.utils.get(ctx.guild.roles, id=roleid).name for roleid in role_list ) embed.add_field(name="Text", value="```{}```".format(cmd["text"]), inline=False) @@ -325,9 +324,7 @@ class CCRole(commands.Cog): async def eval_cc(self, cmd, message: discord.Message, ctx: commands.Context): """Does all the work""" - if cmd["proles"] and not {role.id for role in message.author.roles} & set( - cmd["proles"] - ): + if cmd["proles"] and not {role.id for role in message.author.roles} & set(cmd["proles"]): log.debug(f"{message.author} missing required role to execute {ctx.invoked_with}") return # Not authorized, do nothing diff --git a/infochannel/infochannel.py b/infochannel/infochannel.py index fe8589f..c196e20 100644 --- a/infochannel/infochannel.py +++ b/infochannel/infochannel.py @@ -67,10 +67,7 @@ class InfoChannel(Cog): default_channel_ids = {k: None for k in self.default_channel_names} # Only members is enabled by default - default_enabled_counts = { - k: k == "members" for k in self.default_channel_names - } - + default_enabled_counts = {k: k == "members" for k in self.default_channel_names} default_guild = { "category_id": None, diff --git a/planttycoon/planttycoon.py b/planttycoon/planttycoon.py index 54d0119..0dbded9 100644 --- a/planttycoon/planttycoon.py +++ b/planttycoon/planttycoon.py @@ -249,7 +249,6 @@ class PlantTycoon(commands.Cog): if gardener.products[product] > 0 ) - degradation = ( 100 / (gardener.current["time"] / 60) @@ -306,9 +305,7 @@ class PlantTycoon(commands.Cog): damage_msg = "You used {} too many times!".format(product) else: damage_msg = "You gave too much of {}.".format(product) - message = "{} Your plant lost some health. :wilted_rose:".format( - damage_msg - ) + message = "{} Your plant lost some health. :wilted_rose:".format(damage_msg) gardener.points += self.defaults["points"]["add_health"] await gardener.save_gardener() elif product in gardener.products or product_category != "tool": @@ -440,9 +437,7 @@ class PlantTycoon(commands.Cog): if not gardener.badges: em.add_field(name="**Badges**", value="None") else: - badges = "".join( - "{}\n".format(badge.capitalize()) for badge in gardener.badges - ) + badges = "".join("{}\n".format(badge.capitalize()) for badge in gardener.badges) em.add_field(name="**Badges**", value=badges) if gardener.products: diff --git a/reactrestrict/reactrestrict.py b/reactrestrict/reactrestrict.py index 8329c46..887d4ab 100644 --- a/reactrestrict/reactrestrict.py +++ b/reactrestrict/reactrestrict.py @@ -97,12 +97,7 @@ class ReactRestrict(Cog): """ current_combos = await self.combo_list() - to_keep = [ - c - for c in current_combos - if c.message_id != message_id or c.role_id != role.id - ] - + to_keep = [c for c in current_combos if c.message_id != message_id or c.role_id != role.id] if to_keep != current_combos: await self.set_combo_list(to_keep) From dad14fe972fa9382b58adcc226d65e0cca4ad620 Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 18 Mar 2021 16:08:10 -0400 Subject: [PATCH 25/40] black reformatting --- chatter/trainers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatter/trainers.py b/chatter/trainers.py index 0b765b7..dc0e0b1 100644 --- a/chatter/trainers.py +++ b/chatter/trainers.py @@ -64,7 +64,7 @@ class UbuntuCorpusTrainer2(KaggleTrainer): datapath, downloadpath="ubuntu_data_v2", kaggle_dataset="rtatman/ubuntu-dialogue-corpus", - **kwargs + **kwargs, ) async def asynctrain(self, *args, **kwargs): From 8200cd9af1dfd33edd04e85e1f34af5988214501 Mon Sep 17 00:00:00 2001 From: bobloy Date: Fri, 19 Mar 2021 15:54:19 -0400 Subject: [PATCH 26/40] Run futures correctly --- chatter/chat.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/chatter/chat.py b/chatter/chat.py index 727efc2..7d3c40f 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -613,15 +613,13 @@ class Chatter(Cog): if in_response_to is not None: log.debug("learning response") - learning_task = asyncio.create_task( - self.loop.run_in_executor( - None, - partial( - self.chatbot.learn_response, - Statement(text), - previous_statement=in_response_to, - ), - ) + await self.loop.run_in_executor( + None, + partial( + self.chatbot.learn_response, + Statement(text), + previous_statement=in_response_to, + ), ) replying = None @@ -637,4 +635,6 @@ class Chatter(Cog): await ctx.send(":thinking:") async def check_for_kaggle(self): + """Check whether Kaggle is installed and configured properly""" + # TODO: This return False From eac7aee82c4ab29a40f79d2f1dbb16556d58672f Mon Sep 17 00:00:00 2001 From: bobloy Date: Fri, 19 Mar 2021 15:54:35 -0400 Subject: [PATCH 27/40] Save every 50 instead of all at once, so it can be cancelled --- chatter/trainers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/chatter/trainers.py b/chatter/trainers.py index dc0e0b1..1fe5f62 100644 --- a/chatter/trainers.py +++ b/chatter/trainers.py @@ -107,19 +107,27 @@ class UbuntuCorpusTrainer2(KaggleTrainer): previous_statement_search_text = "" statements_from_file = [] + save_every = 50 + count = 0 + async for row in AsyncIter(reader): dialogue_id = row["dialogueID"] if dialogue_id != last_dialogue_id: previous_statement_text = None previous_statement_search_text = "" last_dialogue_id = dialogue_id + count += 1 + if count >= save_every: + if statements_from_file: + self.chatbot.storage.create_many(statements_from_file) + count = 0 if len(row) > 0: statement = Statement( text=row["text"], in_response_to=previous_statement_text, conversation="training", - created_at=date_parser.parse(row["date"]), + # created_at=date_parser.parse(row["date"]), persona=row["from"], ) From 04ccb435f8512b79a1c02759cd8a459d04f120a0 Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 25 Mar 2021 09:51:41 -0400 Subject: [PATCH 28/40] Implement `check_same_thread` = False storage adapter. Add start of AsyncSQLStorageAdapter --- chatter/storage_adapters.py | 73 +++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 chatter/storage_adapters.py diff --git a/chatter/storage_adapters.py b/chatter/storage_adapters.py new file mode 100644 index 0000000..4de2f00 --- /dev/null +++ b/chatter/storage_adapters.py @@ -0,0 +1,73 @@ +from chatterbot.storage import StorageAdapter, SQLStorageAdapter + + +class MyDumbSQLStorageAdapter(SQLStorageAdapter): + def __init__(self, **kwargs): + super(SQLStorageAdapter, self).__init__(**kwargs) + + from sqlalchemy import create_engine + from sqlalchemy.orm import sessionmaker + + self.database_uri = kwargs.get("database_uri", False) + + # None results in a sqlite in-memory database as the default + if self.database_uri is None: + self.database_uri = "sqlite://" + + # Create a file database if the database is not a connection string + if not self.database_uri: + self.database_uri = "sqlite:///db.sqlite3" + + self.engine = create_engine( + self.database_uri, convert_unicode=True, connect_args={"check_same_thread": False} + ) + + if self.database_uri.startswith("sqlite://"): + from sqlalchemy.engine import Engine + from sqlalchemy import event + + @event.listens_for(Engine, "connect") + def set_sqlite_pragma(dbapi_connection, connection_record): + dbapi_connection.execute("PRAGMA journal_mode=WAL") + dbapi_connection.execute("PRAGMA synchronous=NORMAL") + + if not self.engine.dialect.has_table(self.engine, "Statement"): + self.create_database() + + self.Session = sessionmaker(bind=self.engine, expire_on_commit=True) + + +class AsyncSQLStorageAdapter(SQLStorageAdapter): + def __init__(self, **kwargs): + super(SQLStorageAdapter, self).__init__(**kwargs) + + self.database_uri = kwargs.get("database_uri", False) + + # None results in a sqlite in-memory database as the default + if self.database_uri is None: + self.database_uri = "sqlite://" + + # Create a file database if the database is not a connection string + if not self.database_uri: + self.database_uri = "sqlite:///db.sqlite3" + + async def initialize(self): + # from sqlalchemy import create_engine + from aiomysql.sa import create_engine + from sqlalchemy.orm import sessionmaker + + self.engine = await create_engine(self.database_uri, convert_unicode=True) + + if self.database_uri.startswith("sqlite://"): + from sqlalchemy.engine import Engine + from sqlalchemy import event + + @event.listens_for(Engine, "connect") + def set_sqlite_pragma(dbapi_connection, connection_record): + dbapi_connection.execute("PRAGMA journal_mode=WAL") + dbapi_connection.execute("PRAGMA synchronous=NORMAL") + + if not self.engine.dialect.has_table(self.engine, "Statement"): + self.create_database() + + self.Session = sessionmaker(bind=self.engine, expire_on_commit=True) From 8feb21e34b70f26acf12c7d5af46e673032c9dc6 Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 25 Mar 2021 09:52:20 -0400 Subject: [PATCH 29/40] Add new kaggle trainers --- chatter/trainers.py | 155 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 151 insertions(+), 4 deletions(-) diff --git a/chatter/trainers.py b/chatter/trainers.py index 1fe5f62..d8de22c 100644 --- a/chatter/trainers.py +++ b/chatter/trainers.py @@ -1,5 +1,6 @@ import asyncio import csv +import html import logging import os import pathlib @@ -56,13 +57,159 @@ class KaggleTrainer(Trainer): ), ) + def train(self, *args, **kwargs): + log.error("See asynctrain instead") -class UbuntuCorpusTrainer2(KaggleTrainer): + def asynctrain(self, *args, **kwargs): + raise self.TrainerInitializationException() + + +class SouthParkTrainer(KaggleTrainer): def __init__(self, chatbot, datapath: pathlib.Path, **kwargs): super().__init__( chatbot, datapath, downloadpath="ubuntu_data_v2", + kaggle_dataset="tovarischsukhov/southparklines", + **kwargs, + ) + + +class MovieTrainer(KaggleTrainer): + def __init__(self, chatbot, datapath: pathlib.Path, **kwargs): + super().__init__( + chatbot, + datapath, + downloadpath="kaggle_movies", + kaggle_dataset="Cornell-University/movie-dialog-corpus", + **kwargs, + ) + + async def run_movie_training(self): + dialogue_file = "movie_lines.tsv" + conversation_file = "movie_conversations.tsv" + log.info(f"Beginning dialogue training on {dialogue_file}") + start_time = time.time() + + tagger = PosLemmaTagger(language=self.chatbot.storage.tagger.language) + + # [lineID, characterID, movieID, character name, text of utterance] + # File parsing from https://www.kaggle.com/mushaya/conversation-chatbot + + with open(self.data_directory / conversation_file, "r", encoding="utf-8-sig") as conv_tsv: + conv_lines = conv_tsv.readlines() + with open(self.data_directory / dialogue_file, "r", encoding="utf-8-sig") as lines_tsv: + dialog_lines = lines_tsv.readlines() + + # trans_dict = str.maketrans({"": "__", "": "__", '""': '"'}) + + lines_dict = {} + for line in dialog_lines: + _line = line[:-1].strip('"').split("\t") + if len(_line) >= 5: # Only good lines + lines_dict[_line[0]] = ( + html.unescape(("".join(_line[4:])).strip()) + .replace("", "__") + .replace("", "__") + .replace('""', '"') + ) + else: + log.debug(f"Bad line {_line}") + + # collecting line ids for each conversation + conv = [] + for line in conv_lines[:-1]: + _line = line[:-1].split("\t")[-1][1:-1].replace("'", "").replace(" ", ",") + conv.append(_line.split(",")) + + # conversations = csv.reader(conv_tsv, delimiter="\t") + # + # reader = csv.reader(lines_tsv, delimiter="\t") + # + # + # + # lines_dict = {} + # for row in reader: + # try: + # lines_dict[row[0].strip('"')] = row[4] + # except: + # log.exception(f"Bad line: {row}") + # pass + # else: + # # print(f"Good line: {row}") + # pass + # + # # lines_dict = {row[0].strip('"'): row[4] for row in reader_list} + + statements_from_file = [] + + # [characterID of first, characterID of second, movieID, list of utterances] + async for lines in AsyncIter(conv): + previous_statement_text = None + previous_statement_search_text = "" + + for line in lines: + text = lines_dict[line] + statement = Statement( + text=text, + in_response_to=previous_statement_text, + conversation="training", + ) + + for preprocessor in self.chatbot.preprocessors: + statement = preprocessor(statement) + + statement.search_text = tagger.get_text_index_string(statement.text) + statement.search_in_response_to = previous_statement_search_text + + previous_statement_text = statement.text + previous_statement_search_text = statement.search_text + + statements_from_file.append(statement) + + if statements_from_file: + print(statements_from_file) + self.chatbot.storage.create_many(statements_from_file) + statements_from_file = [] + + print("Training took", time.time() - start_time, "seconds.") + + async def asynctrain(self, *args, **kwargs): + extracted_lines = self.data_directory / "movie_lines.tsv" + extracted_lines: pathlib.Path + + # Download and extract the Ubuntu dialog corpus if needed + if not extracted_lines.exists(): + await self.download(self.kaggle_dataset) + else: + log.info("Movie dialog already downloaded") + if not extracted_lines.exists(): + raise FileNotFoundError(f"{extracted_lines}") + + await self.run_movie_training() + + return True + + # train_dialogue = kwargs.get("train_dialogue", True) + # train_196_dialogue = kwargs.get("train_196", False) + # train_301_dialogue = kwargs.get("train_301", False) + # + # if train_dialogue: + # await self.run_dialogue_training(extracted_dir, "dialogueText.csv") + # + # if train_196_dialogue: + # await self.run_dialogue_training(extracted_dir, "dialogueText_196.csv") + # + # if train_301_dialogue: + # await self.run_dialogue_training(extracted_dir, "dialogueText_301.csv") + + +class UbuntuCorpusTrainer2(KaggleTrainer): + def __init__(self, chatbot, datapath: pathlib.Path, **kwargs): + super().__init__( + chatbot, + datapath, + downloadpath="kaggle_ubuntu", kaggle_dataset="rtatman/ubuntu-dialogue-corpus", **kwargs, ) @@ -91,6 +238,8 @@ class UbuntuCorpusTrainer2(KaggleTrainer): if train_301_dialogue: await self.run_dialogue_training(extracted_dir, "dialogueText_301.csv") + return True + async def run_dialogue_training(self, extracted_dir, dialogue_file): log.info(f"Beginning dialogue training on {dialogue_file}") start_time = time.time() @@ -120,6 +269,7 @@ class UbuntuCorpusTrainer2(KaggleTrainer): if count >= save_every: if statements_from_file: self.chatbot.storage.create_many(statements_from_file) + statements_from_file = [] count = 0 if len(row) > 0: @@ -147,9 +297,6 @@ class UbuntuCorpusTrainer2(KaggleTrainer): print("Training took", time.time() - start_time, "seconds.") - def train(self, *args, **kwargs): - log.error("See asynctrain instead") - class TwitterCorpusTrainer(Trainer): pass From ac9cf1e589308e3489a4e4b2d3759faa129009f9 Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 25 Mar 2021 09:52:43 -0400 Subject: [PATCH 30/40] Implement movie trainer, guild cache, and learning toggle --- chatter/chat.py | 145 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 109 insertions(+), 36 deletions(-) diff --git a/chatter/chat.py b/chatter/chat.py index 7d3c40f..65966fa 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -17,7 +17,7 @@ from redbot.core.commands import Cog from redbot.core.data_manager import cog_data_path from redbot.core.utils.predicates import MessagePredicate -from chatter.trainers import TwitterCorpusTrainer, UbuntuCorpusTrainer2 +from chatter.trainers import MovieTrainer, TwitterCorpusTrainer, UbuntuCorpusTrainer2 log = logging.getLogger("red.fox_v3.chatter") @@ -63,6 +63,7 @@ class Chatter(Cog): "convo_delta": 15, "chatchannel": None, "reply": True, + "learning": True, } path: pathlib.Path = cog_data_path(self) self.data_path = path / "database.sqlite3" @@ -95,7 +96,8 @@ class Chatter(Cog): return ChatBot( "ChatterBot", - storage_adapter="chatterbot.storage.SQLStorageAdapter", + # storage_adapter="chatterbot.storage.SQLStorageAdapter", + storage_adapter="chatter.storage_adapters.MyDumbSQLStorageAdapter", database_uri="sqlite:///" + str(self.data_path), statement_comparison_function=self.similarity_algo, response_selection_method=get_random_response, @@ -176,10 +178,30 @@ class Chatter(Cog): trainer.train() return True - async def _train_ubuntu2(self): - trainer = UbuntuCorpusTrainer2(self.chatbot, cog_data_path(self)) + async def _train_movies(self): + trainer = MovieTrainer(self.chatbot, cog_data_path(self)) await trainer.asynctrain() + async def _train_ubuntu2(self, intensity): + train_kwarg = {} + if intensity == 196: + train_kwarg["train_dialogue"] = False + train_kwarg["train_196"] = True + elif intensity == 301: + train_kwarg["train_dialogue"] = False + train_kwarg["train_301"] = True + elif intensity == 497: + train_kwarg["train_dialogue"] = False + train_kwarg["train_196"] = True + train_kwarg["train_301"] = True + elif intensity >= 9000: # NOT 9000! + train_kwarg["train_dialogue"] = True + train_kwarg["train_196"] = True + train_kwarg["train_301"] = True + + trainer = UbuntuCorpusTrainer2(self.chatbot, cog_data_path(self)) + return await trainer.asynctrain(**train_kwarg) + def _train_english(self): trainer = ChatterBotCorpusTrainer(self.chatbot) # try: @@ -205,7 +227,7 @@ class Chatter(Cog): """ Base command for this cog. Check help for the commands list. """ - pass + self._guild_cache[ctx.guild.id] = {} # Clear cache when modifying values @commands.admin() @chatter.command(name="channel") @@ -240,19 +262,39 @@ class Chatter(Cog): await self.config.guild(ctx.guild).reply.set(toggle) if toggle: - await ctx.send("I will now respond to you if conversation continuity is not present") + await ctx.maybe_send_embed("I will now respond to you if conversation continuity is not present") else: - await ctx.send( + await ctx.maybe_send_embed( "I will not reply to your message if conversation continuity is not present, anymore" ) + @commands.admin() + @chatter.command(name="learning") + async def chatter_learning(self, ctx: commands.Context, toggle: Optional[bool] = None): + """ + Toggle the bot learning from its conversations. + + This is on by default. + """ + learning = await self.config.guild(ctx.guild).learning() + if toggle is None: + toggle = not learning + await self.config.guild(ctx.guild).learning.set(toggle) + + if toggle: + await ctx.maybe_send_embed("I will now learn from conversations.") + else: + await ctx.maybe_send_embed("I will no longer learn from conversations.") + @commands.is_owner() @chatter.command(name="cleardata") async def chatter_cleardata(self, ctx: commands.Context, confirm: bool = False): """ - This command will erase all training data and reset your configuration settings + This command will erase all training data and reset your configuration settings. - Use `[p]chatter cleardata True` + This applies to all guilds. + + Use `[p]chatter cleardata True` to confirm. """ if not confirm: @@ -364,7 +406,6 @@ class Chatter(Cog): return await self.config.guild(ctx.guild).convo_delta.set(minutes) - self._guild_cache[ctx.guild.id]["convo_delta"] = minutes await ctx.tick() @@ -420,51 +461,85 @@ class Chatter(Cog): """Commands for training the bot""" pass - @commands.is_owner() - @chatter_train.command(name="ubuntu") - async def chatter_train_ubuntu(self, ctx: commands.Context, confirmation: bool = False): + @chatter_train.group(name="kaggle") + async def chatter_train_kaggle(self, ctx: commands.Context): """ - WARNING: Large Download! Trains the bot using Ubuntu Dialog Corpus data. + Base command for kaggle training sets. + + See `[p]chatter kaggle` for details on how to enable this option + """ + pass + + @chatter_train_kaggle.command(name="ubuntu") + async def chatter_train_kaggle_ubuntu( + self, ctx: commands.Context, confirmation: bool = False, intensity=0 + ): + """ + WARNING: Large Download! Trains the bot using *NEW* Ubuntu Dialog Corpus data. """ if not confirmation: await ctx.maybe_send_embed( - "Warning: This command downloads ~500MB then eats your CPU for training\n" - "If you're sure you want to continue, run `[p]chatter train ubuntu True`" + "Warning: This command downloads ~800 then eats your CPU for training\n" + "If you're sure you want to continue, run `[p]chatter train kaggle ubuntu True`" ) return async with ctx.typing(): - future = await self.loop.run_in_executor(None, self._train_ubuntu) + future = await self._train_ubuntu2(intensity) if future: - await ctx.send("Training successful!") + await ctx.maybe_send_embed("Training successful!") else: - await ctx.send("Error occurred :(") + await ctx.maybe_send_embed("Error occurred :(") - @commands.is_owner() - @chatter_train.command(name="ubuntu2") - async def chatter_train_ubuntu2(self, ctx: commands.Context, confirmation: bool = False): + @chatter_train_kaggle.command(name="movies") + async def chatter_train_kaggle_movies(self, ctx: commands.Context, confirmation: bool = False): """ - WARNING: Large Download! Trains the bot using *NEW* Ubuntu Dialog Corpus data. + WARNING: Language! Trains the bot using Cornell University's "Movie Dialog Corpus". + + This training set contains dialog from a spread of movies with different MPAA. + This dialog includes racism, sexism, and any number of sensitive topics. + + Use at your own risk. """ if not confirmation: await ctx.maybe_send_embed( "Warning: This command downloads ~800 then eats your CPU for training\n" - "If you're sure you want to continue, run `[p]chatter train ubuntu2 True`" + "If you're sure you want to continue, run `[p]chatter train kaggle movies True`" ) return async with ctx.typing(): - future = await self._train_ubuntu2() + future = await self._train_movies() if future: - await ctx.send("Training successful!") + await ctx.maybe_send_embed("Training successful!") else: - await ctx.send("Error occurred :(") + await ctx.maybe_send_embed("Error occurred :(") + + @chatter_train.command(name="ubuntu") + async def chatter_train_ubuntu(self, ctx: commands.Context, confirmation: bool = False): + """ + WARNING: Large Download! Trains the bot using Ubuntu Dialog Corpus data. + """ + + if not confirmation: + await ctx.maybe_send_embed( + "Warning: This command downloads ~500MB then eats your CPU for training\n" + "If you're sure you want to continue, run `[p]chatter train ubuntu True`" + ) + return + + async with ctx.typing(): + future = await self.loop.run_in_executor(None, self._train_ubuntu) + + if future: + await ctx.maybe_send_embed("Training successful!") + else: + await ctx.maybe_send_embed("Error occurred :(") - @commands.is_owner() @chatter_train.command(name="english") async def chatter_train_english(self, ctx: commands.Context): """ @@ -478,7 +553,6 @@ class Chatter(Cog): else: await ctx.maybe_send_embed("Error occurred :(") - @commands.is_owner() @chatter_train.command(name="list") async def chatter_train_list(self, ctx: commands.Context): """Trains the bot based on an uploaded list. @@ -495,7 +569,6 @@ class Chatter(Cog): await ctx.send("Not yet implemented") - @commands.is_owner() @chatter_train.command(name="channel") async def chatter_train_channel(self, ctx: commands.Context, channel: discord.TextChannel): """ @@ -563,6 +636,9 @@ class Chatter(Cog): # Thank you Cog-Creators channel: discord.TextChannel = message.channel + if not self._guild_cache[guild.id]: + self._guild_cache[guild.id] = await self.config.guild(guild).all() + is_reply = False # this is only useful with in_response_to if ( message.reference is not None @@ -571,7 +647,7 @@ class Chatter(Cog): ): is_reply = True # this is only useful with in_response_to pass # this is a reply to the bot, good to go - elif guild is not None and channel.id == await self.config.guild(guild).chatchannel(): + elif guild is not None and channel.id == self._guild_cache[guild.id]["chatchannel"]: pass # good to go else: when_mentionables = commands.when_mentioned(self.bot, message) @@ -588,9 +664,6 @@ class Chatter(Cog): async with ctx.typing(): - if not self._guild_cache[ctx.guild.id]: - self._guild_cache[ctx.guild.id] = await self.config.guild(ctx.guild).all() - if is_reply: in_response_to = message.reference.resolved.content elif self._last_message_per_channel[ctx.channel.id] is not None: @@ -611,7 +684,7 @@ class Chatter(Cog): None, self.chatbot.generate_response, Statement(text) ) - if in_response_to is not None: + if in_response_to is not None and self._guild_cache[guild.id]["learning"]: log.debug("learning response") await self.loop.run_in_executor( None, @@ -623,7 +696,7 @@ class Chatter(Cog): ) replying = None - if await self.config.guild(guild).reply(): + if self._guild_cache[guild.id]["reply"]: if message != ctx.channel.last_message: replying = message From b4f20dd7d283ed64ab7429824839a533c8abf2e7 Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 25 Mar 2021 09:54:14 -0400 Subject: [PATCH 31/40] Don't print everything, use log --- chatter/trainers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/chatter/trainers.py b/chatter/trainers.py index d8de22c..962fa08 100644 --- a/chatter/trainers.py +++ b/chatter/trainers.py @@ -136,7 +136,7 @@ class MovieTrainer(KaggleTrainer): # log.exception(f"Bad line: {row}") # pass # else: - # # print(f"Good line: {row}") + # # log.info(f"Good line: {row}") # pass # # # lines_dict = {row[0].strip('"'): row[4] for row in reader_list} @@ -168,11 +168,10 @@ class MovieTrainer(KaggleTrainer): statements_from_file.append(statement) if statements_from_file: - print(statements_from_file) self.chatbot.storage.create_many(statements_from_file) statements_from_file = [] - print("Training took", time.time() - start_time, "seconds.") + log.info("Training took", time.time() - start_time, "seconds.") async def asynctrain(self, *args, **kwargs): extracted_lines = self.data_directory / "movie_lines.tsv" @@ -295,7 +294,7 @@ class UbuntuCorpusTrainer2(KaggleTrainer): if statements_from_file: self.chatbot.storage.create_many(statements_from_file) - print("Training took", time.time() - start_time, "seconds.") + log.info("Training took", time.time() - start_time, "seconds.") class TwitterCorpusTrainer(Trainer): From 59fd96fc5af9d1a0ab9e5c70199f40369381c6ba Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 25 Mar 2021 10:01:56 -0400 Subject: [PATCH 32/40] add save_every for less disk intensive work. --- chatter/trainers.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/chatter/trainers.py b/chatter/trainers.py index 962fa08..adf042f 100644 --- a/chatter/trainers.py +++ b/chatter/trainers.py @@ -142,6 +142,8 @@ class MovieTrainer(KaggleTrainer): # # lines_dict = {row[0].strip('"'): row[4] for row in reader_list} statements_from_file = [] + save_every = 50 + count = 0 # [characterID of first, characterID of second, movieID, list of utterances] async for lines in AsyncIter(conv): @@ -167,9 +169,15 @@ class MovieTrainer(KaggleTrainer): statements_from_file.append(statement) - if statements_from_file: - self.chatbot.storage.create_many(statements_from_file) - statements_from_file = [] + count += 1 + if count >= save_every: + if statements_from_file: + self.chatbot.storage.create_many(statements_from_file) + statements_from_file = [] + count = 0 + + if statements_from_file: + self.chatbot.storage.create_many(statements_from_file) log.info("Training took", time.time() - start_time, "seconds.") From 802929d757458f9ff4fac99203ced1f033c9bdbc Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 25 Mar 2021 10:02:02 -0400 Subject: [PATCH 33/40] better wording --- chatter/chat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chatter/chat.py b/chatter/chat.py index 65966fa..9e3379f 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -480,7 +480,7 @@ class Chatter(Cog): if not confirmation: await ctx.maybe_send_embed( - "Warning: This command downloads ~800 then eats your CPU for training\n" + "Warning: This command downloads ~800MB and is CPU intensive during training\n" "If you're sure you want to continue, run `[p]chatter train kaggle ubuntu True`" ) return @@ -506,7 +506,7 @@ class Chatter(Cog): if not confirmation: await ctx.maybe_send_embed( - "Warning: This command downloads ~800 then eats your CPU for training\n" + "Warning: This command downloads ~29MB and is CPU intensive during training\n" "If you're sure you want to continue, run `[p]chatter train kaggle movies True`" ) return @@ -527,7 +527,7 @@ class Chatter(Cog): if not confirmation: await ctx.maybe_send_embed( - "Warning: This command downloads ~500MB then eats your CPU for training\n" + "Warning: This command downloads ~500MB and is CPU intensive during training\n" "If you're sure you want to continue, run `[p]chatter train ubuntu True`" ) return From 1319d98972e0b79677a34585fc0b1be2786802e2 Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 25 Mar 2021 10:56:48 -0400 Subject: [PATCH 34/40] Less often, still writing too much. --- chatter/trainers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatter/trainers.py b/chatter/trainers.py index adf042f..4f80b79 100644 --- a/chatter/trainers.py +++ b/chatter/trainers.py @@ -142,7 +142,7 @@ class MovieTrainer(KaggleTrainer): # # lines_dict = {row[0].strip('"'): row[4] for row in reader_list} statements_from_file = [] - save_every = 50 + save_every = 300 count = 0 # [characterID of first, characterID of second, movieID, list of utterances] From db24bb4db4f81d2248b82219d7953798be4dc585 Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 25 Mar 2021 10:57:35 -0400 Subject: [PATCH 35/40] No differences --- chatter/chat.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chatter/chat.py b/chatter/chat.py index 9e3379f..fe50588 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -262,7 +262,9 @@ class Chatter(Cog): await self.config.guild(ctx.guild).reply.set(toggle) if toggle: - await ctx.maybe_send_embed("I will now respond to you if conversation continuity is not present") + await ctx.maybe_send_embed( + "I will now respond to you if conversation continuity is not present" + ) else: await ctx.maybe_send_embed( "I will not reply to your message if conversation continuity is not present, anymore" From 87187abbb3423fc6539864c4239b858d54b280e7 Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 25 Mar 2021 11:11:57 -0400 Subject: [PATCH 36/40] Fix logging --- chatter/trainers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chatter/trainers.py b/chatter/trainers.py index 4f80b79..3cc92da 100644 --- a/chatter/trainers.py +++ b/chatter/trainers.py @@ -179,7 +179,7 @@ class MovieTrainer(KaggleTrainer): if statements_from_file: self.chatbot.storage.create_many(statements_from_file) - log.info("Training took", time.time() - start_time, "seconds.") + log.info(f"Training took {time.time() - start_time} seconds.") async def asynctrain(self, *args, **kwargs): extracted_lines = self.data_directory / "movie_lines.tsv" @@ -302,7 +302,7 @@ class UbuntuCorpusTrainer2(KaggleTrainer): if statements_from_file: self.chatbot.storage.create_many(statements_from_file) - log.info("Training took", time.time() - start_time, "seconds.") + log.info(f"Training took {time.time() - start_time} seconds.") class TwitterCorpusTrainer(Trainer): From e1297a4dcaec7b12bc1958a728f47d96cfdac5dc Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 25 Mar 2021 11:12:05 -0400 Subject: [PATCH 37/40] Return success value --- chatter/chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatter/chat.py b/chatter/chat.py index fe50588..d999d94 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -180,7 +180,7 @@ class Chatter(Cog): async def _train_movies(self): trainer = MovieTrainer(self.chatbot, cog_data_path(self)) - await trainer.asynctrain() + return await trainer.asynctrain() async def _train_ubuntu2(self, intensity): train_kwarg = {} From 9f22dfb790ab8421dd205ff0d975e8bf448cc7f8 Mon Sep 17 00:00:00 2001 From: bobloy Date: Thu, 25 Mar 2021 17:24:16 -0400 Subject: [PATCH 38/40] Swap learning to global config --- chatter/chat.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/chatter/chat.py b/chatter/chat.py index d999d94..9caf050 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -56,14 +56,13 @@ class Chatter(Cog): super().__init__() self.bot = bot self.config = Config.get_conf(self, identifier=6710497116116101114) - default_global = {} + default_global = {"learning": True} default_guild = { "whitelist": None, "days": 1, "convo_delta": 15, "chatchannel": None, "reply": True, - "learning": True, } path: pathlib.Path = cog_data_path(self) self.data_path = path / "database.sqlite3" @@ -85,6 +84,7 @@ class Chatter(Cog): self.loop = asyncio.get_event_loop() self._guild_cache = defaultdict(dict) + self._global_cache = {} self._last_message_per_channel: Dict[Optional[discord.Message]] = defaultdict(lambda: None) @@ -228,6 +228,7 @@ class Chatter(Cog): Base command for this cog. Check help for the commands list. """ self._guild_cache[ctx.guild.id] = {} # Clear cache when modifying values + self._global_cache = {} @commands.admin() @chatter.command(name="channel") @@ -270,18 +271,19 @@ class Chatter(Cog): "I will not reply to your message if conversation continuity is not present, anymore" ) - @commands.admin() + @commands.is_owner() @chatter.command(name="learning") async def chatter_learning(self, ctx: commands.Context, toggle: Optional[bool] = None): """ Toggle the bot learning from its conversations. + This is a global setting. This is on by default. """ - learning = await self.config.guild(ctx.guild).learning() + learning = await self.config.learning() if toggle is None: toggle = not learning - await self.config.guild(ctx.guild).learning.set(toggle) + await self.config.learning.set(toggle) if toggle: await ctx.maybe_send_embed("I will now learn from conversations.") @@ -686,7 +688,10 @@ class Chatter(Cog): None, self.chatbot.generate_response, Statement(text) ) - if in_response_to is not None and self._guild_cache[guild.id]["learning"]: + if not self._global_cache: + self._global_cache = await self.config.all() + + if in_response_to is not None and self._global_cache["learning"]: log.debug("learning response") await self.loop.run_in_executor( None, From 5f30bc1234995f371ff35ad175714ed6e618723c Mon Sep 17 00:00:00 2001 From: bobloy Date: Fri, 26 Mar 2021 13:53:00 -0400 Subject: [PATCH 39/40] Add multiple channel training --- chatter/chat.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/chatter/chat.py b/chatter/chat.py index 9caf050..8ecbf1a 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -5,7 +5,7 @@ import pathlib from collections import defaultdict from datetime import datetime, timedelta from functools import partial -from typing import Dict, Optional +from typing import Dict, List, Optional import discord from chatterbot import ChatBot @@ -107,7 +107,7 @@ class Chatter(Cog): logger=log, ) - async def _get_conversation(self, ctx, in_channel: discord.TextChannel = None): + async def _get_conversation(self, ctx, in_channels: List[discord.TextChannel]): """ Compiles all conversation in the Guild this bot can get it's hands on Currently takes a stupid long time @@ -124,9 +124,9 @@ class Chatter(Cog): # Should always be positive numbers return msg.created_at - sent >= delta - for channel in ctx.guild.text_channels: - if in_channel: - channel = in_channel + for channel in in_channels: + # if in_channel: + # channel = in_channel await ctx.maybe_send_embed("Gathering {}".format(channel.mention)) user = None i = 0 @@ -161,8 +161,8 @@ class Chatter(Cog): except discord.HTTPException: pass - if in_channel: - break + # if in_channel: + # break return out @@ -574,10 +574,15 @@ class Chatter(Cog): await ctx.send("Not yet implemented") @chatter_train.command(name="channel") - async def chatter_train_channel(self, ctx: commands.Context, channel: discord.TextChannel): + async def chatter_train_channel( + self, ctx: commands.Context, channels: commands.Greedy[discord.TextChannel] + ): """ Trains the bot based on language in this guild. """ + if not channels: + await ctx.send_help() + return await ctx.maybe_send_embed( "Warning: The cog may use significant RAM or CPU if trained on large data sets.\n" @@ -586,7 +591,7 @@ class Chatter(Cog): ) async with ctx.typing(): - conversation = await self._get_conversation(ctx, channel) + conversation = await self._get_conversation(ctx, channels) if not conversation: await ctx.maybe_send_embed("Failed to gather training data") From 93b403b35fb4c17b89b5e52218c2c39562c825eb Mon Sep 17 00:00:00 2001 From: bobloy Date: Mon, 5 Apr 2021 09:25:23 -0400 Subject: [PATCH 40/40] Better progress logging --- chatter/chat.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/chatter/chat.py b/chatter/chat.py index 8ecbf1a..5ad3efb 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -213,13 +213,10 @@ class Chatter(Cog): def _train(self, data): trainer = ListTrainer(self.chatbot) total = len(data) - # try: for c, convo in enumerate(data, 1): + log.info(f"{c} / {total}") if len(convo) > 1: # TODO: Toggleable skipping short conversations - print(f"{c} / {total}") trainer.train(convo) - # except: - # return False return True @commands.group(invoke_without_command=False)