Compare commits
	
		
			3 Commits
		
	
	
		
			master
			...
			ww_develop
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0250a297e1 | ||
|   | 711c83ddbb | ||
|   | 4523ffc98d | 
| @ -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_fox-v3 channel | ||||
| Feel free to @ me in the #support_othercogs channel | ||||
| 
 | ||||
| Discord: Bobloy#6513 | ||||
| 
 | ||||
|  | ||||
| @ -54,7 +54,8 @@ class AnnounceDaily(Cog): | ||||
| 
 | ||||
|         Do `[p]help annd <subcommand>` for more details | ||||
|         """ | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @commands.command() | ||||
|     @checks.guildowner() | ||||
|  | ||||
| @ -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 = {p.stem for p in self._all_audio_lists()} | ||||
|         lists = set(p.stem for p in self._all_audio_lists()) | ||||
|         if await ctx.embed_requested(): | ||||
|             await ctx.send( | ||||
|                 embed=discord.Embed( | ||||
|  | ||||
| @ -3,7 +3,6 @@ import logging | ||||
| import re | ||||
| 
 | ||||
| import discord | ||||
| from discord.ext.commands import RoleConverter, Greedy, CommandError, ArgumentParsingError | ||||
| from discord.ext.commands.view import StringView | ||||
| from redbot.core import Config, checks, commands | ||||
| from redbot.core.bot import Red | ||||
| @ -14,38 +13,15 @@ log = logging.getLogger("red.fox_v3.ccrole") | ||||
| 
 | ||||
| 
 | ||||
| async def _get_roles_from_content(ctx, content): | ||||
|     # greedy = Greedy[RoleConverter] | ||||
|     view = StringView(content) | ||||
|     rc = RoleConverter() | ||||
| 
 | ||||
|     # "Borrowed" from discord.ext.commands.Command._transform_greedy_pos | ||||
|     result = [] | ||||
|     while not view.eof: | ||||
|         # for use with a manual undo | ||||
|         previous = view.index | ||||
| 
 | ||||
|         view.skip_ws() | ||||
|         try: | ||||
|             argument = view.get_quoted_word() | ||||
|             value = await rc.convert(ctx, argument) | ||||
|         except (CommandError, ArgumentParsingError): | ||||
|             view.index = previous | ||||
|             break | ||||
|         else: | ||||
|             result.append(value) | ||||
| 
 | ||||
|     return [r.id for r in result] | ||||
| 
 | ||||
|     # Old method | ||||
|     # content_list = content.split(",") | ||||
|     # try: | ||||
|     #     role_list = [ | ||||
|     #         discord.utils.get(ctx.guild.roles, name=role.strip(" ")).id for role in content_list | ||||
|     #     ] | ||||
|     # except (discord.HTTPException, AttributeError):  # None.id is attribute error | ||||
|     #     return None | ||||
|     # else: | ||||
|     #     return role_list | ||||
|     content_list = content.split(",") | ||||
|     try: | ||||
|         role_list = [ | ||||
|             discord.utils.get(ctx.guild.roles, name=role.strip(" ")).id for role in content_list | ||||
|         ] | ||||
|     except (discord.HTTPException, AttributeError):  # None.id is attribute error | ||||
|         return None | ||||
|     else: | ||||
|         return role_list | ||||
| 
 | ||||
| 
 | ||||
| class CCRole(commands.Cog): | ||||
| @ -72,7 +48,8 @@ class CCRole(commands.Cog): | ||||
|         """Custom commands management with roles | ||||
| 
 | ||||
|         Highly customizable custom commands with role management.""" | ||||
|         pass | ||||
|         if not ctx.invoked_subcommand: | ||||
|             pass | ||||
| 
 | ||||
|     @ccrole.command(name="add") | ||||
|     @checks.mod_or_permissions(administrator=True) | ||||
| @ -108,7 +85,7 @@ class CCRole(commands.Cog): | ||||
| 
 | ||||
|         # Roles to add | ||||
|         await ctx.send( | ||||
|             "What roles should it add?\n" | ||||
|             "What roles should it add? (Must be **comma separated**)\n" | ||||
|             "Say `None` to skip adding roles" | ||||
|         ) | ||||
| 
 | ||||
| @ -130,7 +107,7 @@ class CCRole(commands.Cog): | ||||
| 
 | ||||
|         # Roles to remove | ||||
|         await ctx.send( | ||||
|             "What roles should it remove?\n" | ||||
|             "What roles should it remove? (Must be comma separated)\n" | ||||
|             "Say `None` to skip removing roles" | ||||
|         ) | ||||
|         try: | ||||
| @ -148,7 +125,7 @@ class CCRole(commands.Cog): | ||||
| 
 | ||||
|         # Roles to use | ||||
|         await ctx.send( | ||||
|             "What roles are allowed to use this command?\n" | ||||
|             "What roles are allowed to use this command? (Must be comma separated)\n" | ||||
|             "Say `None` to allow all roles" | ||||
|         ) | ||||
| 
 | ||||
| @ -251,7 +228,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) | ||||
| @ -275,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 ( | ||||
| @ -348,7 +325,9 @@ 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 ( | ||||
|             set(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 | ||||
| 
 | ||||
|  | ||||
| @ -59,50 +59,63 @@ Install these on your windows machine before attempting the installation: | ||||
| [Pandoc - Universal Document Converter](https://pandoc.org/installing.html) | ||||
| 
 | ||||
| ## Methods | ||||
| ### Automatic | ||||
| ### Windows - Manually | ||||
| #### Step 1: Built-in Downloader | ||||
| 
 | ||||
| This method requires some luck to pull off. | ||||
| You need to get a copy of the requirements.txt provided with chatter, I recommend this method. | ||||
| 
 | ||||
| #### Step 1: Add repo and install cog | ||||
| ``` | ||||
| [p]repo add Fox https://github.com/bobloy/Fox-V3 | ||||
| ``` | ||||
| 
 | ||||
| #### Step 2: Install Requirements | ||||
| 
 | ||||
| Make sure you have your virtual environment that you installed Red on activated before starting this step. See the Red Docs for details on how. | ||||
| 
 | ||||
| In a terminal running as an admin, navigate to the directory containing this repo.  | ||||
| 
 | ||||
| I've used my install directory as an example. | ||||
| 
 | ||||
| ``` | ||||
| cd C:\Users\Bobloy\AppData\Local\Red-DiscordBot\Red-DiscordBot\data\bobbot\cogs\RepoManager\repos\Fox\chatter | ||||
| pip install -r requirements.txt | ||||
| pip install --no-deps "chatterbot>=1.1" | ||||
| ``` | ||||
| 
 | ||||
| #### Step 3: Load Chatter | ||||
| 
 | ||||
| ``` | ||||
| [p]repo add Fox https://github.com/bobloy/Fox-V3  # If you didn't already do this in step 1 | ||||
| [p]cog install Fox chatter | ||||
| [p]load chatter | ||||
| ``` | ||||
| 
 | ||||
| ### Linux - Manually | ||||
| 
 | ||||
| #### Step 1: Built-in Downloader | ||||
| 
 | ||||
| ``` | ||||
| [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 Requirements | ||||
| 
 | ||||
| #### Step 2: Install additional dependencies | ||||
| 
 | ||||
| Here you need to decide which training models you want to have available to you. | ||||
| 
 | ||||
| Shutdown the bot and run any number of these in the console: | ||||
| In your console with your virtual environment activated: | ||||
| 
 | ||||
| ``` | ||||
| python -m spacy download en_core_web_sm  # ~15 MB | ||||
| 
 | ||||
| python -m spacy download en_core_web_md  # ~50 MB | ||||
| 
 | ||||
| python -m spacy download en_core_web_lg  # ~750 MB (CPU Optimized) | ||||
| 
 | ||||
| python -m spacy download en_core_web_trf  # ~500 MB (GPU Optimized) | ||||
| pip install --no-deps "chatterbot>=1.1" | ||||
| ``` | ||||
| 
 | ||||
| #### Step 3: Load the cog and get started | ||||
| ### Step 3: Load Chatter | ||||
| 
 | ||||
| ``` | ||||
| [p]load chatter | ||||
| ``` | ||||
| 
 | ||||
| ### Windows - Manually | ||||
| Deprecated | ||||
| 
 | ||||
| ### Linux - Manually | ||||
| Deprecated | ||||
| 
 | ||||
| # Configuration | ||||
| 
 | ||||
| Chatter works out the box without any training by learning as it goes,  | ||||
| Chatter works out the the box without any training by learning as it goes,  | ||||
| but will have very poor and repetitive responses at first. | ||||
| 
 | ||||
| Initial training is recommended to speed up its learning. | ||||
|  | ||||
| @ -1,10 +1,8 @@ | ||||
| from .chat import Chatter | ||||
| 
 | ||||
| 
 | ||||
| async def setup(bot): | ||||
|     cog = Chatter(bot) | ||||
|     await cog.initialize() | ||||
|     bot.add_cog(cog) | ||||
| def setup(bot): | ||||
|     bot.add_cog(Chatter(bot)) | ||||
| 
 | ||||
| 
 | ||||
| # __all__ = ( | ||||
|  | ||||
							
								
								
									
										398
									
								
								chatter/chat.py
									
									
									
									
									
								
							
							
						
						
									
										398
									
								
								chatter/chat.py
									
									
									
									
									
								
							| @ -2,10 +2,8 @@ import asyncio | ||||
| import logging | ||||
| import os | ||||
| import pathlib | ||||
| from collections import defaultdict | ||||
| from datetime import datetime, timedelta | ||||
| from functools import partial | ||||
| from typing import Dict, List, Optional | ||||
| from typing import Optional | ||||
| 
 | ||||
| import discord | ||||
| from chatterbot import ChatBot | ||||
| @ -17,9 +15,6 @@ 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 MovieTrainer, TwitterCorpusTrainer, UbuntuCorpusTrainer2 | ||||
| 
 | ||||
| chatterbot_log = logging.getLogger("red.fox_v3.chatterbot") | ||||
| log = logging.getLogger("red.fox_v3.chatter") | ||||
| 
 | ||||
| 
 | ||||
| @ -30,12 +25,6 @@ def my_local_get_prefix(prefixes, content): | ||||
|     return None | ||||
| 
 | ||||
| 
 | ||||
| class ENG_TRF: | ||||
|     ISO_639_1 = "en_core_web_trf" | ||||
|     ISO_639 = "eng" | ||||
|     ENGLISH_NAME = "English" | ||||
| 
 | ||||
| 
 | ||||
| class ENG_LG: | ||||
|     ISO_639_1 = "en_core_web_lg" | ||||
|     ISO_639 = "eng" | ||||
| @ -59,77 +48,50 @@ class Chatter(Cog): | ||||
|     This cog trains a chatbot that will talk like members of your Guild | ||||
|     """ | ||||
| 
 | ||||
|     models = [ENG_SM, ENG_MD, ENG_LG, ENG_TRF] | ||||
|     algos = [SpacySimilarity, JaccardSimilarity, LevenshteinDistance] | ||||
| 
 | ||||
|     def __init__(self, bot): | ||||
|         super().__init__() | ||||
|         self.bot = bot | ||||
|         self.config = Config.get_conf(self, identifier=6710497116116101114) | ||||
|         default_global = {"learning": True, "model_number": 0, "algo_number": 0, "threshold": 0.90} | ||||
|         self.default_guild = { | ||||
|             "whitelist": None, | ||||
|             "days": 1, | ||||
|             "convo_delta": 15, | ||||
|             "chatchannel": None, | ||||
|             "reply": True, | ||||
|         } | ||||
|         default_global = {} | ||||
|         default_guild = {"whitelist": None, "days": 1, "convo_delta": 15, "chatchannel": None} | ||||
|         path: pathlib.Path = cog_data_path(self) | ||||
|         self.data_path = path / "database.sqlite3" | ||||
| 
 | ||||
|         # TODO: Move training_model and similarity_algo to config | ||||
|         # TODO: Add an option to see current settings | ||||
| 
 | ||||
|         self.tagger_language = ENG_SM | ||||
|         self.tagger_language = ENG_MD | ||||
|         self.similarity_algo = SpacySimilarity | ||||
|         self.similarity_threshold = 0.90 | ||||
|         self.chatbot = None | ||||
|         self.chatbot = self._create_chatbot() | ||||
|         # self.chatbot.set_trainer(ListTrainer) | ||||
| 
 | ||||
|         # self.trainer = ListTrainer(self.chatbot) | ||||
| 
 | ||||
|         self.config.register_global(**default_global) | ||||
|         self.config.register_guild(**self.default_guild) | ||||
|         self.config.register_guild(**default_guild) | ||||
| 
 | ||||
|         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) | ||||
| 
 | ||||
|     async def red_delete_data_for_user(self, **kwargs): | ||||
|         """Nothing to delete""" | ||||
|         return | ||||
| 
 | ||||
|     async def initialize(self): | ||||
|         all_config = dict(self.config.defaults["GLOBAL"]) | ||||
|         all_config.update(await self.config.all()) | ||||
|         model_number = all_config["model_number"] | ||||
|         algo_number = all_config["algo_number"] | ||||
|         threshold = all_config["threshold"] | ||||
| 
 | ||||
|         self.tagger_language = self.models[model_number] | ||||
|         self.similarity_algo = self.algos[algo_number] | ||||
|         self.similarity_threshold = threshold | ||||
|         self.chatbot = self._create_chatbot() | ||||
| 
 | ||||
|     def _create_chatbot(self): | ||||
| 
 | ||||
|         return ChatBot( | ||||
|             "ChatterBot", | ||||
|             # storage_adapter="chatterbot.storage.SQLStorageAdapter", | ||||
|             storage_adapter="chatter.storage_adapters.MyDumbSQLStorageAdapter", | ||||
|             storage_adapter="chatterbot.storage.SQLStorageAdapter", | ||||
|             database_uri="sqlite:///" + str(self.data_path), | ||||
|             statement_comparison_function=self.similarity_algo, | ||||
|             response_selection_method=get_random_response, | ||||
|             logic_adapters=["chatterbot.logic.BestMatch"], | ||||
|             maximum_similarity_threshold=self.similarity_threshold, | ||||
|             tagger_language=self.tagger_language, | ||||
|             logger=chatterbot_log, | ||||
|             logger=log, | ||||
|         ) | ||||
| 
 | ||||
|     async def _get_conversation(self, ctx, in_channels: List[discord.TextChannel]): | ||||
|     async def _get_conversation(self, ctx, in_channel: discord.TextChannel = None): | ||||
|         """ | ||||
|         Compiles all conversation in the Guild this bot can get it's hands on | ||||
|         Currently takes a stupid long time | ||||
| @ -143,12 +105,20 @@ class Chatter(Cog): | ||||
|             return msg.clean_content | ||||
| 
 | ||||
|         def new_conversation(msg, sent, out_in, delta): | ||||
|             # Should always be positive numbers | ||||
|             # 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) | ||||
| 
 | ||||
|             return msg.created_at - sent >= delta | ||||
| 
 | ||||
|         for channel in in_channels: | ||||
|             # if in_channel: | ||||
|             #     channel = in_channel | ||||
|         for channel in ctx.guild.text_channels: | ||||
|             if in_channel: | ||||
|                 channel = in_channel | ||||
|             await ctx.maybe_send_embed("Gathering {}".format(channel.mention)) | ||||
|             user = None | ||||
|             i = 0 | ||||
| @ -183,16 +153,11 @@ class Chatter(Cog): | ||||
|             except discord.HTTPException: | ||||
|                 pass | ||||
| 
 | ||||
|             # if in_channel: | ||||
|             #     break | ||||
|             if in_channel: | ||||
|                 break | ||||
| 
 | ||||
|         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, ubuntu_corpus_data_directory=cog_data_path(self) / "ubuntu_data" | ||||
| @ -200,30 +165,6 @@ class Chatter(Cog): | ||||
|         trainer.train() | ||||
|         return True | ||||
| 
 | ||||
|     async def _train_movies(self): | ||||
|         trainer = MovieTrainer(self.chatbot, cog_data_path(self)) | ||||
|         return 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: | ||||
| @ -235,10 +176,13 @@ 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) | ||||
| @ -246,10 +190,10 @@ 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 = {} | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @commands.admin() | ||||
|     @checks.admin() | ||||
|     @chatter.command(name="channel") | ||||
|     async def chatter_channel( | ||||
|         self, ctx: commands.Context, channel: Optional[discord.TextChannel] = None | ||||
| @ -269,55 +213,13 @@ 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.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.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" | ||||
|             ) | ||||
| 
 | ||||
|     @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.learning() | ||||
|         if toggle is None: | ||||
|             toggle = not learning | ||||
|         await self.config.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() | ||||
|     @checks.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 | ||||
| 
 | ||||
|         This applies to all guilds. | ||||
| 
 | ||||
|         Use `[p]chatter cleardata True` to confirm. | ||||
|         Use `[p]chatter cleardata True` | ||||
|         """ | ||||
| 
 | ||||
|         if not confirm: | ||||
| @ -344,18 +246,21 @@ class Chatter(Cog): | ||||
| 
 | ||||
|         await ctx.tick() | ||||
| 
 | ||||
|     @commands.is_owner() | ||||
|     @checks.is_owner() | ||||
|     @chatter.command(name="algorithm", aliases=["algo"]) | ||||
|     async def chatter_algorithm( | ||||
|         self, ctx: commands.Context, algo_number: int, threshold: float = None | ||||
|     ): | ||||
|         """ | ||||
|         Switch the active logic algorithm to one of the three. Default is Spacy | ||||
|         Switch the active logic algorithm to one of the three. Default after reload is Spacy | ||||
| 
 | ||||
|         0: Spacy | ||||
|         1: Jaccard | ||||
|         2: Levenshtein | ||||
|         """ | ||||
| 
 | ||||
|         algos = [SpacySimilarity, JaccardSimilarity, LevenshteinDistance] | ||||
| 
 | ||||
|         if algo_number < 0 or algo_number > 2: | ||||
|             await ctx.send_help() | ||||
|             return | ||||
| @ -368,32 +273,31 @@ class Chatter(Cog): | ||||
|                 return | ||||
|             else: | ||||
|                 self.similarity_threshold = threshold | ||||
|                 await self.config.threshold.set(self.similarity_threshold) | ||||
| 
 | ||||
|         self.similarity_algo = self.algos[algo_number] | ||||
|         await self.config.algo_number.set(algo_number) | ||||
| 
 | ||||
|         self.similarity_algo = algos[algo_number] | ||||
|         async with ctx.typing(): | ||||
|             self.chatbot = self._create_chatbot() | ||||
| 
 | ||||
|             await ctx.tick() | ||||
| 
 | ||||
|     @commands.is_owner() | ||||
|     @checks.is_owner() | ||||
|     @chatter.command(name="model") | ||||
|     async def chatter_model(self, ctx: commands.Context, model_number: int): | ||||
|         """ | ||||
|         Switch the active model to one of the three. Default is Small | ||||
|         Switch the active model to one of the three. Default after reload is Medium | ||||
| 
 | ||||
|         0: Small | ||||
|         1: Medium (Requires additional setup) | ||||
|         1: Medium | ||||
|         2: Large (Requires additional setup) | ||||
|         3. Accurate (Requires additional setup) | ||||
|         """ | ||||
|         if model_number < 0 or model_number > 3: | ||||
| 
 | ||||
|         models = [ENG_SM, ENG_MD, ENG_LG] | ||||
| 
 | ||||
|         if model_number < 0 or model_number > 2: | ||||
|             await ctx.send_help() | ||||
|             return | ||||
| 
 | ||||
|         if model_number >= 0: | ||||
|         if model_number == 2: | ||||
|             await ctx.maybe_send_embed( | ||||
|                 "Additional requirements needed. See guide before continuing.\n" "Continue?" | ||||
|             ) | ||||
| @ -406,8 +310,7 @@ class Chatter(Cog): | ||||
|             if not pred.result: | ||||
|                 return | ||||
| 
 | ||||
|         self.tagger_language = self.models[model_number] | ||||
|         await self.config.model_number.set(model_number) | ||||
|         self.tagger_language = models[model_number] | ||||
|         async with ctx.typing(): | ||||
|             self.chatbot = self._create_chatbot() | ||||
| 
 | ||||
| @ -415,14 +318,8 @@ class Chatter(Cog): | ||||
|                 f"Model has been switched to {self.tagger_language.ISO_639_1}" | ||||
|             ) | ||||
| 
 | ||||
|     @commands.is_owner() | ||||
|     @chatter.group(name="trainset") | ||||
|     async def chatter_trainset(self, ctx: commands.Context): | ||||
|         """Commands for configuring training""" | ||||
|         pass | ||||
| 
 | ||||
|     @commands.is_owner() | ||||
|     @chatter_trainset.command(name="minutes") | ||||
|     @checks.is_owner() | ||||
|     @chatter.command(name="minutes") | ||||
|     async def minutes(self, ctx: commands.Context, minutes: int): | ||||
|         """ | ||||
|         Sets the number of minutes the bot will consider a break in a conversation during training | ||||
| @ -433,12 +330,12 @@ class Chatter(Cog): | ||||
|             await ctx.send_help() | ||||
|             return | ||||
| 
 | ||||
|         await self.config.guild(ctx.guild).convo_delta.set(minutes) | ||||
|         await self.config.guild(ctx.guild).convo_length.set(minutes) | ||||
| 
 | ||||
|         await ctx.tick() | ||||
| 
 | ||||
|     @commands.is_owner() | ||||
|     @chatter_trainset.command(name="age") | ||||
|     @checks.is_owner() | ||||
|     @chatter.command(name="age") | ||||
|     async def age(self, ctx: commands.Context, days: int): | ||||
|         """ | ||||
|         Sets the number of days to look back | ||||
| @ -452,16 +349,7 @@ 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() | ||||
|     @checks.is_owner() | ||||
|     @chatter.command(name="backup") | ||||
|     async def backup(self, ctx, backupname): | ||||
|         """ | ||||
| @ -483,71 +371,8 @@ class Chatter(Cog): | ||||
|         else: | ||||
|             await ctx.maybe_send_embed("Error occurred :(") | ||||
| 
 | ||||
|     @commands.is_owner() | ||||
|     @chatter.group(name="train") | ||||
|     async def chatter_train(self, ctx: commands.Context): | ||||
|         """Commands for training the bot""" | ||||
|         pass | ||||
| 
 | ||||
|     @chatter_train.group(name="kaggle") | ||||
|     async def chatter_train_kaggle(self, ctx: commands.Context): | ||||
|         """ | ||||
|         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 ~800MB and is CPU intensive during 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._train_ubuntu2(intensity) | ||||
| 
 | ||||
|         if future: | ||||
|             await ctx.maybe_send_embed("Training successful!") | ||||
|         else: | ||||
|             await ctx.maybe_send_embed("Error occurred :(") | ||||
| 
 | ||||
|     @chatter_train_kaggle.command(name="movies") | ||||
|     async def chatter_train_kaggle_movies(self, ctx: commands.Context, confirmation: bool = False): | ||||
|         """ | ||||
|         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 ~29MB and is CPU intensive during training\n" | ||||
|                 "If you're sure you want to continue, run `[p]chatter train kaggle movies True`" | ||||
|             ) | ||||
|             return | ||||
| 
 | ||||
|         async with ctx.typing(): | ||||
|             future = await self._train_movies() | ||||
| 
 | ||||
|         if future: | ||||
|             await ctx.maybe_send_embed("Training successful!") | ||||
|         else: | ||||
|             await ctx.maybe_send_embed("Error occurred :(") | ||||
| 
 | ||||
|     @chatter_train.command(name="ubuntu") | ||||
|     @checks.is_owner() | ||||
|     @chatter.command(name="trainubuntu") | ||||
|     async def chatter_train_ubuntu(self, ctx: commands.Context, confirmation: bool = False): | ||||
|         """ | ||||
|         WARNING: Large Download! Trains the bot using Ubuntu Dialog Corpus data. | ||||
| @ -555,8 +380,8 @@ class Chatter(Cog): | ||||
| 
 | ||||
|         if not confirmation: | ||||
|             await ctx.maybe_send_embed( | ||||
|                 "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`" | ||||
|                 "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`" | ||||
|             ) | ||||
|             return | ||||
| 
 | ||||
| @ -564,11 +389,12 @@ class Chatter(Cog): | ||||
|             future = await self.loop.run_in_executor(None, self._train_ubuntu) | ||||
| 
 | ||||
|         if future: | ||||
|             await ctx.maybe_send_embed("Training successful!") | ||||
|             await ctx.send("Training successful!") | ||||
|         else: | ||||
|             await ctx.maybe_send_embed("Error occurred :(") | ||||
|             await ctx.send("Error occurred :(") | ||||
| 
 | ||||
|     @chatter_train.command(name="english") | ||||
|     @checks.is_owner() | ||||
|     @chatter.command(name="trainenglish") | ||||
|     async def chatter_train_english(self, ctx: commands.Context): | ||||
|         """ | ||||
|         Trains the bot in english | ||||
| @ -581,32 +407,12 @@ class Chatter(Cog): | ||||
|         else: | ||||
|             await ctx.maybe_send_embed("Error occurred :(") | ||||
| 
 | ||||
|     @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'] | ||||
|     @checks.is_owner() | ||||
|     @chatter.command() | ||||
|     async def train(self, ctx: commands.Context, channel: discord.TextChannel): | ||||
|         """ | ||||
|         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") | ||||
| 
 | ||||
|     @chatter_train.command(name="channel") | ||||
|     async def chatter_train_channel( | ||||
|         self, ctx: commands.Context, channels: commands.Greedy[discord.TextChannel] | ||||
|     ): | ||||
|         Trains the bot based on language in this guild | ||||
|         """ | ||||
|         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" | ||||
| @ -615,7 +421,7 @@ class Chatter(Cog): | ||||
|         ) | ||||
| 
 | ||||
|         async with ctx.typing(): | ||||
|             conversation = await self._get_conversation(ctx, channels) | ||||
|             conversation = await self._get_conversation(ctx, channel) | ||||
| 
 | ||||
|         if not conversation: | ||||
|             await ctx.maybe_send_embed("Failed to gather training data") | ||||
| @ -657,7 +463,7 @@ class Chatter(Cog): | ||||
| 
 | ||||
|         guild: discord.Guild = getattr(message, "guild", None) | ||||
| 
 | ||||
|         if guild is None or await self.bot.cog_disabled_in_guild(self, guild): | ||||
|         if await self.bot.cog_disabled_in_guild(self, guild): | ||||
|             return | ||||
| 
 | ||||
|         ctx: commands.Context = await self.bot.get_context(message) | ||||
| @ -669,18 +475,7 @@ 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 | ||||
|             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 == self._guild_cache[guild.id]["chatchannel"]: | ||||
|         if 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) | ||||
| @ -695,57 +490,10 @@ class Chatter(Cog): | ||||
| 
 | ||||
|         text = message.clean_content | ||||
| 
 | ||||
|         async with ctx.typing(): | ||||
| 
 | ||||
|             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: | ||||
|                     in_response_to = None | ||||
|                 else: | ||||
|                     in_response_to = last_m.content | ||||
|             else: | ||||
|                 in_response_to = None | ||||
| 
 | ||||
|             # 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 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, | ||||
|                     partial( | ||||
|                         self.chatbot.learn_response, | ||||
|                         Statement(text), | ||||
|                         previous_statement=in_response_to, | ||||
|                     ), | ||||
|                 ) | ||||
| 
 | ||||
|             replying = None | ||||
|             if ( | ||||
|                 "reply" not in self._guild_cache[guild.id] and self.default_guild["reply"] | ||||
|             ) or self._guild_cache[guild.id]["reply"]: | ||||
|                 if message != ctx.channel.last_message: | ||||
|                     replying = message | ||||
|         async with channel.typing(): | ||||
|             future = await self.loop.run_in_executor(None, self.chatbot.get_response, text) | ||||
| 
 | ||||
|             if future and str(future): | ||||
|                 self._last_message_per_channel[ctx.channel.id] = await channel.send( | ||||
|                     str(future), reference=replying | ||||
|                 ) | ||||
|                 await channel.send(str(future)) | ||||
|             else: | ||||
|                 await ctx.send(":thinking:") | ||||
| 
 | ||||
|     async def check_for_kaggle(self): | ||||
|         """Check whether Kaggle is installed and configured properly""" | ||||
|         # TODO: This | ||||
|         return False | ||||
|                 await channel.send(":thinking:") | ||||
|  | ||||
| @ -2,15 +2,22 @@ | ||||
|   "author": [ | ||||
|     "Bobloy" | ||||
|   ], | ||||
|   "min_bot_version": "3.4.6", | ||||
|   "min_bot_version": "3.4.0", | ||||
|   "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`", | ||||
|   "requirements": [ | ||||
|     "git+git://github.com/bobloy/ChatterBot@fox#egg=ChatterBot>=1.1.0.dev4", | ||||
|     "kaggle", | ||||
|     "https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.1.0/en_core_web_sm-3.1.0.tar.gz#egg=en_core_web_sm", | ||||
|     "https://github.com/explosion/spacy-models/releases/download/en_core_web_md-3.1.0/en_core_web_md-3.1.0.tar.gz#egg=en_core_web_md" | ||||
|     "git+git://github.com/gunthercox/chatterbot-corpus@master#egg=chatterbot_corpus", | ||||
|     "mathparse>=0.1,<0.2", | ||||
|     "nltk>=3.2,<4.0", | ||||
|     "pint>=0.8.1", | ||||
|     "python-dateutil>=2.8,<2.9", | ||||
|     "pyyaml>=5.3,<5.4", | ||||
|     "sqlalchemy>=1.3,<1.4", | ||||
|     "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" | ||||
|   ], | ||||
|   "short": "Local Chatbot run on machine learning", | ||||
|   "end_user_data_statement": "This cog only stores anonymous conversations data; no End User Data is stored.", | ||||
|  | ||||
							
								
								
									
										12
									
								
								chatter/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								chatter/requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| git+git://github.com/gunthercox/chatterbot-corpus@master#egg=chatterbot_corpus | ||||
| mathparse>=0.1,<0.2 | ||||
| nltk>=3.2,<4.0 | ||||
| pint>=0.8.1 | ||||
| python-dateutil>=2.8,<2.9 | ||||
| pyyaml>=5.3,<5.4 | ||||
| sqlalchemy>=1.3,<1.4 | ||||
| pytz | ||||
| spacy>=2.3,<2.4 | ||||
| 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 | ||||
| # https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-2.3.1/en_core_web_lg-2.3.1.tar.gz#egg=en_core_web_lg | ||||
| @ -1,71 +0,0 @@ | ||||
| from chatterbot.storage import StorageAdapter, SQLStorageAdapter | ||||
| 
 | ||||
| 
 | ||||
| class MyDumbSQLStorageAdapter(SQLStorageAdapter): | ||||
|     def __init__(self, **kwargs): | ||||
|         super(SQLStorageAdapter, self).__init__(**kwargs) | ||||
| 
 | ||||
|         from sqlalchemy import create_engine, inspect | ||||
|         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, 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 inspect(self.engine).has_table("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) | ||||
| @ -1,351 +0,0 @@ | ||||
| import asyncio | ||||
| import csv | ||||
| import html | ||||
| 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, | ||||
|             ), | ||||
|         ) | ||||
| 
 | ||||
|     def train(self, *args, **kwargs): | ||||
|         log.error("See asynctrain instead") | ||||
| 
 | ||||
|     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({"<u>": "__", "</u>": "__", '""': '"'}) | ||||
| 
 | ||||
|         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("<u>", "__") | ||||
|                     .replace("</u>", "__") | ||||
|                     .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: | ||||
|         #         # log.info(f"Good line: {row}") | ||||
|         #         pass | ||||
|         # | ||||
|         # # lines_dict = {row[0].strip('"'): row[4] for row in reader_list} | ||||
| 
 | ||||
|         statements_from_file = [] | ||||
|         save_every = 300 | ||||
|         count = 0 | ||||
| 
 | ||||
|         # [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) | ||||
| 
 | ||||
|             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(f"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, | ||||
|         ) | ||||
| 
 | ||||
|     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") | ||||
| 
 | ||||
|         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() | ||||
| 
 | ||||
|         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 = [] | ||||
| 
 | ||||
|             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) | ||||
|                             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"]), | ||||
|                         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) | ||||
| 
 | ||||
|         log.info(f"Training took {time.time() - start_time} seconds.") | ||||
| 
 | ||||
| 
 | ||||
| class TwitterCorpusTrainer(Trainer): | ||||
|     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) | ||||
| @ -58,7 +58,11 @@ class CogLint(Cog): | ||||
| 
 | ||||
|         future = await self.bot.loop.run_in_executor(None, lint.py_run, path, "return_std=True") | ||||
| 
 | ||||
|         (pylint_stdout, pylint_stderr) = future or (None, None) | ||||
|         if future: | ||||
|             (pylint_stdout, pylint_stderr) = future | ||||
|         else: | ||||
|             (pylint_stdout, pylint_stderr) = None, None | ||||
| 
 | ||||
|         # print(pylint_stderr) | ||||
|         # print(pylint_stdout) | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| import asyncio | ||||
| import json | ||||
| import logging | ||||
| import os | ||||
| import pathlib | ||||
| from abc import ABC | ||||
| @ -14,8 +13,6 @@ from redbot.core import Config, commands | ||||
| from redbot.core.bot import Red | ||||
| from redbot.core.data_manager import bundled_data_path, cog_data_path | ||||
| 
 | ||||
| log = logging.getLogger("red.fox_v3.conquest") | ||||
| 
 | ||||
| 
 | ||||
| class Conquest(commands.Cog): | ||||
|     """ | ||||
| @ -56,28 +53,23 @@ class Conquest(commands.Cog): | ||||
|         self.current_map = await self.config.current_map() | ||||
| 
 | ||||
|         if self.current_map: | ||||
|             if not await self.current_map_load(): | ||||
|                 await self.config.current_map.clear() | ||||
|             await self.current_map_load() | ||||
| 
 | ||||
|     async def current_map_load(self): | ||||
|         map_data_path = self.asset_path / self.current_map / "data.json" | ||||
|         if not map_data_path.exists(): | ||||
|             log.warning(f"{map_data_path} does not exist. Clearing current map") | ||||
|             return False | ||||
| 
 | ||||
|         with map_data_path.open() as mapdata: | ||||
|             self.map_data: dict = json.load(mapdata) | ||||
|         self.ext = self.map_data["extension"] | ||||
|         self.ext_format = "JPEG" if self.ext.upper() == "JPG" else self.ext.upper() | ||||
|         return True | ||||
| 
 | ||||
|     @commands.group() | ||||
|     async def conquest(self, ctx: commands.Context): | ||||
|         """ | ||||
|         Base command for conquest cog. Start with `[p]conquest set map` to select a map. | ||||
|         """ | ||||
|         if ctx.invoked_subcommand is None and self.current_map is not None: | ||||
|             await self._conquest_current(ctx) | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             if self.current_map is not None: | ||||
|                 await self._conquest_current(ctx) | ||||
| 
 | ||||
|     @conquest.command(name="list") | ||||
|     async def _conquest_list(self, ctx: commands.Context): | ||||
| @ -88,13 +80,14 @@ class Conquest(commands.Cog): | ||||
| 
 | ||||
|         with maps_json.open() as maps: | ||||
|             maps_json = json.load(maps) | ||||
|             map_list = "\n".join(maps_json["maps"]) | ||||
|             map_list = "\n".join(map_name for map_name in 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""" | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @conquest_set.command(name="resetzoom") | ||||
|     async def _conquest_set_resetzoom(self, ctx: commands.Context): | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| { | ||||
|   "maps": [ | ||||
|     "simple", | ||||
| 	"ck2", | ||||
| 	"HoI" | ||||
|     "simple_blank_map", | ||||
| 	"test", | ||||
| 	"test2" | ||||
|   ] | ||||
| } | ||||
| @ -30,7 +30,8 @@ class MapMaker(commands.Cog): | ||||
|         """ | ||||
|         Base command for managing current maps or creating new ones | ||||
|         """ | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @mapmaker.command(name="upload") | ||||
|     async def _mapmaker_upload(self, ctx: commands.Context, map_path=""): | ||||
|  | ||||
| @ -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 not in [value, border] | ||||
|                         fill = p != value and p != border | ||||
|                     if fill: | ||||
|                         pixel[s, t] = value | ||||
|                         new_edge.add((s, t)) | ||||
|  | ||||
| @ -27,7 +27,8 @@ class ExclusiveRole(Cog): | ||||
|     async def exclusive(self, ctx): | ||||
|         """Base command for managing exclusive roles""" | ||||
| 
 | ||||
|         pass | ||||
|         if not ctx.invoked_subcommand: | ||||
|             pass | ||||
| 
 | ||||
|     @exclusive.command(name="add") | ||||
|     @checks.mod_or_permissions(administrator=True) | ||||
| @ -84,7 +85,7 @@ class ExclusiveRole(Cog): | ||||
|         if role_set is None: | ||||
|             role_set = set(await self.config.guild(member.guild).role_list()) | ||||
| 
 | ||||
|         member_set = {role.id for role in member.roles} | ||||
|         member_set = 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: | ||||
| @ -102,7 +103,7 @@ class ExclusiveRole(Cog): | ||||
|         await asyncio.sleep(1) | ||||
| 
 | ||||
|         role_set = set(await self.config.guild(after.guild).role_list()) | ||||
|         member_set = {role.id for role in after.roles} | ||||
|         member_set = set([role.id for role in after.roles]) | ||||
| 
 | ||||
|         if role_set & member_set: | ||||
|             try: | ||||
|  | ||||
							
								
								
									
										16
									
								
								fifo/fifo.py
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								fifo/fifo.py
									
									
									
									
									
								
							| @ -68,7 +68,10 @@ class CapturePrint: | ||||
|         self.string = None | ||||
| 
 | ||||
|     def write(self, string): | ||||
|         self.string = string if self.string is None else self.string + "\n" + string | ||||
|         if self.string is None: | ||||
|             self.string = string | ||||
|         else: | ||||
|             self.string = self.string + "\n" + string | ||||
| 
 | ||||
| 
 | ||||
| class FIFO(commands.Cog): | ||||
| @ -194,8 +197,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 | ||||
| @ -227,7 +230,8 @@ class FIFO(commands.Cog): | ||||
|         """ | ||||
|         Base command for handling scheduling of tasks | ||||
|         """ | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @fifo.command(name="wakeup") | ||||
|     async def fifo_wakeup(self, ctx: commands.Context): | ||||
| @ -441,7 +445,6 @@ class FIFO(commands.Cog): | ||||
|         self.scheduler.print_jobs(out=cp) | ||||
| 
 | ||||
|         out = cp.string | ||||
|         out=out.replace("*","\*") | ||||
| 
 | ||||
|         if out: | ||||
|             if len(out) > 2000: | ||||
| @ -519,7 +522,8 @@ class FIFO(commands.Cog): | ||||
|         """ | ||||
|         Add a new trigger for a task from the current guild. | ||||
|         """ | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @fifo_trigger.command(name="interval") | ||||
|     async def fifo_trigger_interval( | ||||
|  | ||||
| @ -90,7 +90,6 @@ things_for_fakemessage_to_steal = [ | ||||
|     "content", | ||||
|     "nonce", | ||||
|     "reference", | ||||
|     "_edited_timestamp"  # New 7/23/21 | ||||
| ] | ||||
| 
 | ||||
| things_fakemessage_sets_by_default = { | ||||
| @ -143,7 +142,7 @@ class FakeMessage(discord.Message): | ||||
|         self._update( | ||||
|             { | ||||
|                 "mention_roles": self.raw_role_mentions, | ||||
|                 "mentions": [{"id": _id} for _id in self.raw_mentions], | ||||
|                 "mentions": self.raw_mentions, | ||||
|             } | ||||
|         ) | ||||
| 
 | ||||
|  | ||||
| @ -55,7 +55,8 @@ class Flag(Cog): | ||||
|         """ | ||||
|         Commands for managing Flag settings | ||||
|         """ | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @flagset.command(name="expire") | ||||
|     async def flagset_expire(self, ctx: commands.Context, days: int): | ||||
|  | ||||
| @ -147,7 +147,8 @@ class Hangman(Cog): | ||||
|     @checks.mod_or_permissions(administrator=True) | ||||
|     async def hangset(self, ctx): | ||||
|         """Adjust hangman settings""" | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @hangset.command() | ||||
|     async def face(self, ctx: commands.Context, theface): | ||||
| @ -249,7 +250,7 @@ class Hangman(Cog): | ||||
| 
 | ||||
|         self.winbool[guild] = True | ||||
|         for i in self.the_data[guild]["answer"]: | ||||
|             if i in [" ", "-"]: | ||||
|             if i == " " or i == "-": | ||||
|                 out_str += i * 2 | ||||
|             elif i in self.the_data[guild]["guesses"] or i not in "ABCDEFGHIJKLMNOPQRSTUVWXYZ": | ||||
|                 out_str += "__" + i + "__ " | ||||
| @ -261,7 +262,9 @@ class Hangman(Cog): | ||||
| 
 | ||||
|     def _guesslist(self, guild): | ||||
|         """Returns the current letter list""" | ||||
|         out_str = "".join(str(i) + "," for i in self.the_data[guild]["guesses"]) | ||||
|         out_str = "" | ||||
|         for i in self.the_data[guild]["guesses"]: | ||||
|             out_str += str(i) + "," | ||||
|         out_str = out_str[:-1] | ||||
| 
 | ||||
|         return out_str | ||||
|  | ||||
| @ -28,12 +28,9 @@ async def get_channel_counts(category, guild): | ||||
|     online_num = members - offline_num | ||||
|     # Gets count of actual users | ||||
|     human_num = members - bot_num | ||||
|     # count amount of premium subs/nitro subs. | ||||
|     boosters = guild.premium_subscription_count | ||||
|     return { | ||||
|         "members": members, | ||||
|         "humans": human_num, | ||||
|         "boosters": boosters, | ||||
|         "bots": bot_num, | ||||
|         "roles": roles_num, | ||||
|         "channels": channels_num, | ||||
| @ -61,7 +58,6 @@ class InfoChannel(Cog): | ||||
|         self.default_channel_names = { | ||||
|             "members": "Members: {count}", | ||||
|             "humans": "Humans: {count}", | ||||
|             "boosters": "Boosters: {count}", | ||||
|             "bots": "Bots: {count}", | ||||
|             "roles": "Roles: {count}", | ||||
|             "channels": "Channels: {count}", | ||||
| @ -69,9 +65,9 @@ class InfoChannel(Cog): | ||||
|             "offline": "Offline: {count}", | ||||
|         } | ||||
| 
 | ||||
|         default_channel_ids = {k: None for k in self.default_channel_names} | ||||
|         default_channel_ids = {k: None for k in self.default_channel_names.keys()} | ||||
|         # 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.keys()} | ||||
| 
 | ||||
|         default_guild = { | ||||
|             "category_id": None, | ||||
| @ -163,7 +159,8 @@ class InfoChannel(Cog): | ||||
|         """ | ||||
|         Toggle different types of infochannels | ||||
|         """ | ||||
|         pass | ||||
|         if not ctx.invoked_subcommand: | ||||
|             pass | ||||
| 
 | ||||
|     @infochannelset.command(name="togglechannel") | ||||
|     async def _infochannelset_togglechannel( | ||||
| @ -174,7 +171,6 @@ class InfoChannel(Cog): | ||||
|         Valid Types are: | ||||
|         - `members`: Total members on the server | ||||
|         - `humans`: Total members that aren't bots | ||||
|         - `boosters`: Total amount of boosters | ||||
|         - `bots`: Total bots | ||||
|         - `roles`: Total number of roles | ||||
|         - `channels`: Total number of channels excluding infochannels, | ||||
| @ -229,7 +225,6 @@ class InfoChannel(Cog): | ||||
|         Valid Types are: | ||||
|         - `members`: Total members on the server | ||||
|         - `humans`: Total members that aren't bots | ||||
|         - `boosters`: Total amount of boosters | ||||
|         - `bots`: Total bots | ||||
|         - `roles`: Total number of roles | ||||
|         - `channels`: Total number of channels excluding infochannels | ||||
| @ -447,7 +442,6 @@ class InfoChannel(Cog): | ||||
|                 guild, | ||||
|                 members=True, | ||||
|                 humans=True, | ||||
|                 boosters=True, | ||||
|                 bots=True, | ||||
|                 roles=True, | ||||
|                 channels=True, | ||||
| @ -504,16 +498,14 @@ class InfoChannel(Cog): | ||||
|         guild_data = await self.config.guild(guild).all() | ||||
| 
 | ||||
|         to_update = ( | ||||
|             kwargs.keys() & [key for key, value in guild_data["enabled_channels"].items() if value] | ||||
|             kwargs.keys() & guild_data["enabled_channels"].keys() | ||||
|         )  # Value in kwargs doesn't matter | ||||
| 
 | ||||
|         if to_update or extra_roles: | ||||
|             log.debug(f"{to_update=}\n" | ||||
|                       f"{extra_roles=}") | ||||
|         log.debug(f"{to_update=}") | ||||
| 
 | ||||
|         if to_update or extra_roles: | ||||
|             category = guild.get_channel(guild_data["category_id"]) | ||||
|             if category is None: | ||||
|                 log.debug('Channel category is missing, updating must be off') | ||||
|                 return  # Nothing to update, must be off | ||||
| 
 | ||||
|             channel_data = await get_channel_counts(category, guild) | ||||
|  | ||||
| @ -36,6 +36,58 @@ 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 | ||||
| 
 | ||||
| @ -50,7 +102,11 @@ class LaunchLib(commands.Cog): | ||||
|         if launch.pad: | ||||
|             urls += [launch.pad.info_url, launch.pad.wiki_url] | ||||
| 
 | ||||
|         url = next((url for url in urls if urls is not None), None) if urls else None | ||||
|         if urls: | ||||
|             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() | ||||
| 
 | ||||
|         em = discord.Embed(title=title, description=description, url=url, color=color) | ||||
| @ -96,9 +152,7 @@ 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) | ||||
| @ -115,7 +169,8 @@ class LaunchLib(commands.Cog): | ||||
|     @commands.group() | ||||
|     async def launchlib(self, ctx: commands.Context): | ||||
|         """Base command for getting launches""" | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @launchlib.command() | ||||
|     async def next(self, ctx: commands.Context, num_launches: int = 1): | ||||
|  | ||||
| @ -25,7 +25,8 @@ class Leaver(Cog): | ||||
|     @checks.mod_or_permissions(administrator=True) | ||||
|     async def leaverset(self, ctx): | ||||
|         """Adjust leaver settings""" | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @leaverset.command() | ||||
|     async def channel(self, ctx: Context): | ||||
| @ -56,3 +57,5 @@ class Leaver(Cog): | ||||
|                 ) | ||||
|             else: | ||||
|                 await channel.send(out) | ||||
|         else: | ||||
|             pass | ||||
|  | ||||
| @ -40,20 +40,16 @@ class LoveCalculator(Cog): | ||||
|         log.debug(f"{resp=}") | ||||
|         soup_object = BeautifulSoup(resp, "html.parser") | ||||
| 
 | ||||
|         description = soup_object.find("div", class_="result__score") | ||||
|         description = soup_object.find("div", class_="result__score").get_text() | ||||
| 
 | ||||
|         if description is None: | ||||
|             description = "Dr. Love is busy right now" | ||||
|         else: | ||||
|             description = description.get_text().strip() | ||||
|             description = description.strip() | ||||
| 
 | ||||
|         result_image = soup_object.find("img", class_="result__image").get("src") | ||||
| 
 | ||||
|         result_text = soup_object.find("div", class_="result-text") | ||||
|         if result_text is None: | ||||
|             result_text = f"{x} and {y} aren't compatible 😔" | ||||
|         else: | ||||
|             result_text = result_text.get_text() | ||||
|         result_text = soup_object.find("div", class_="result-text").get_text() | ||||
|         result_text = " ".join(result_text.split()) | ||||
| 
 | ||||
|         try: | ||||
|  | ||||
| @ -45,12 +45,14 @@ class LastSeen(Cog): | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def get_date_time(s): | ||||
|         return dateutil.parser.parse(s) | ||||
|         d = dateutil.parser.parse(s) | ||||
|         return d | ||||
| 
 | ||||
|     @commands.group(aliases=["setlseen"], name="lseenset") | ||||
|     async def lset(self, ctx: commands.Context): | ||||
|         """Change settings for lseen""" | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @lset.command(name="toggle") | ||||
|     async def lset_toggle(self, ctx: commands.Context): | ||||
| @ -77,13 +79,11 @@ class LastSeen(Cog): | ||||
|                 return | ||||
|             last_seen = self.get_date_time(last_seen) | ||||
| 
 | ||||
|         embed = discord.Embed( | ||||
|             description="{} was last seen at this date and time".format(member.display_name), | ||||
|             timestamp=last_seen, | ||||
|             color=await self.bot.get_embed_color(ctx), | ||||
|         ) | ||||
|         # embed = discord.Embed( | ||||
|         #     description="{} was last seen at this date and time".format(member.display_name), | ||||
|         #     timestamp=last_seen) | ||||
| 
 | ||||
|         # embed = discord.Embed(timestamp=last_seen, color=await self.bot.get_embed_color(ctx)) | ||||
|         embed = discord.Embed(timestamp=last_seen, color=await self.bot.get_embed_color(ctx)) | ||||
|         await ctx.send(embed=embed) | ||||
| 
 | ||||
|     @commands.Cog.listener() | ||||
|  | ||||
| @ -111,8 +111,9 @@ async def _withdraw_points(gardener: Gardener, amount): | ||||
| 
 | ||||
|     if (gardener.points - amount) < 0: | ||||
|         return False | ||||
|     gardener.points -= amount | ||||
|     return True | ||||
|     else: | ||||
|         gardener.points -= amount | ||||
|         return True | ||||
| 
 | ||||
| 
 | ||||
| class PlantTycoon(commands.Cog): | ||||
| @ -244,9 +245,11 @@ 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 = ( | ||||
| @ -287,31 +290,38 @@ 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 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: | ||||
|                     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) | ||||
|             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: | ||||
|                         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) | ||||
|                         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() | ||||
|                 else: | ||||
|                     message = "You have no {}. Go buy some!".format(product) | ||||
|             else: | ||||
|                 message = "You don't have a {}. Go buy one!".format(product) | ||||
|                 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) | ||||
|         else: | ||||
|             message = "Are you sure you are using {}?".format(product_category) | ||||
| 
 | ||||
| @ -402,18 +412,24 @@ 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.""" | ||||
|         author = member if member is not None else ctx.author | ||||
|         if member is not None: | ||||
|             author = member | ||||
|         else: | ||||
|             author = ctx.author | ||||
| 
 | ||||
|         gardener = await self._gardener(author) | ||||
|         try: | ||||
|             await self._apply_degradation(gardener) | ||||
| @ -424,7 +440,9 @@ 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 gardener.current: | ||||
|         if not gardener.current: | ||||
|             em.add_field(name="**Currently growing**", value="None") | ||||
|         else: | ||||
|             em.set_thumbnail(url=gardener.current["image"]) | ||||
|             em.add_field( | ||||
|                 name="**Currently growing**", | ||||
| @ -432,15 +450,16 @@ 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 = "".join("{}\n".format(badge.capitalize()) for badge in gardener.badges) | ||||
| 
 | ||||
|             badges = "" | ||||
|             for badge in gardener.badges: | ||||
|                 badges += "{}\n".format(badge.capitalize()) | ||||
|             em.add_field(name="**Badges**", value=badges) | ||||
|         if gardener.products: | ||||
|         if not gardener.products: | ||||
|             em.add_field(name="**Products**", value="None") | ||||
|         else: | ||||
|             products = "" | ||||
|             for product_name, product_data in gardener.products.items(): | ||||
|                 if self.products[product_name] is None: | ||||
| @ -451,8 +470,6 @@ 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) | ||||
| @ -583,6 +600,7 @@ 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" | ||||
| @ -611,8 +629,7 @@ 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): | ||||
| @ -646,7 +663,8 @@ class PlantTycoon(commands.Cog): | ||||
|         else: | ||||
|             gardener.current = {} | ||||
|             message = "You successfully shovelled your plant out." | ||||
|             gardener.points = max(gardener.points, 0) | ||||
|             if gardener.points < 0: | ||||
|                 gardener.points = 0 | ||||
|             await gardener.save_gardener() | ||||
| 
 | ||||
|         em = discord.Embed(description=message, color=discord.Color.dark_grey()) | ||||
| @ -663,12 +681,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") | ||||
| @ -682,11 +700,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") | ||||
| @ -699,12 +717,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): | ||||
|  | ||||
| @ -67,10 +67,8 @@ 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"{save_as_name}.{extension}" | ||||
|         image_path = path / (ctx.guild.icon + "." + extension) | ||||
|         async with aiohttp.ClientSession() as session: | ||||
|             async with session.get(image_url) as response: | ||||
|                 image = await response.read() | ||||
| @ -79,29 +77,27 @@ class QRInvite(Cog): | ||||
|             file.write(image) | ||||
| 
 | ||||
|         if extension == "webp": | ||||
|             new_image_path = convert_webp_to_png(str(image_path)) | ||||
|             new_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_image_path = str(image_path) | ||||
|         elif extension == "jpg": | ||||
|             new_image_path = convert_jpg_to_png(str(image_path)) | ||||
|             new_path = str(image_path) | ||||
|         else: | ||||
|             await ctx.maybe_send_embed(f"{extension} is not supported yet, stay tuned") | ||||
|             return | ||||
| 
 | ||||
|         myqr.run( | ||||
|             invite, | ||||
|             picture=new_image_path, | ||||
|             save_name=f"{save_as_name}_qrcode.png", | ||||
|             picture=new_path, | ||||
|             save_name=ctx.guild.icon + "_qrcode.png", | ||||
|             save_dir=str(cog_data_path(self)), | ||||
|             colorized=colorized, | ||||
|         ) | ||||
| 
 | ||||
|         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")) | ||||
|         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")) | ||||
| 
 | ||||
| 
 | ||||
| def convert_webp_to_png(path): | ||||
| @ -114,10 +110,3 @@ 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 | ||||
|  | ||||
| @ -97,7 +97,9 @@ 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 not (c.message_id == message_id and c.role_id == role.id) | ||||
|         ] | ||||
| 
 | ||||
|         if to_keep != current_combos: | ||||
|             await self.set_combo_list(to_keep) | ||||
| @ -208,7 +210,8 @@ class ReactRestrict(Cog): | ||||
|         """ | ||||
|         Base command for this cog. Check help for the commands list. | ||||
|         """ | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @reactrestrict.command() | ||||
|     async def add(self, ctx: commands.Context, message_id: int, *, role: discord.Role): | ||||
|  | ||||
| @ -32,7 +32,6 @@ class RecyclingPlant(Cog): | ||||
| 
 | ||||
|         x = 0 | ||||
|         reward = 0 | ||||
|         timeoutcount = 0 | ||||
|         await ctx.send( | ||||
|             "{0} has signed up for a shift at the Recycling Plant! Type ``exit`` to terminate it early.".format( | ||||
|                 ctx.author.display_name | ||||
| @ -54,25 +53,14 @@ class RecyclingPlant(Cog): | ||||
|                 return m.author == ctx.author and m.channel == ctx.channel | ||||
| 
 | ||||
|             try: | ||||
|                 answer = await self.bot.wait_for("message", timeout=20, check=check) | ||||
|                 answer = await self.bot.wait_for("message", timeout=120, check=check) | ||||
|             except asyncio.TimeoutError: | ||||
|                 answer = None | ||||
| 
 | ||||
|             if answer is None: | ||||
|                 if timeoutcount == 2: | ||||
|                     await ctx.send( | ||||
|                         "{} slacked off at work, so they were sacked with no pay.".format( | ||||
|                             ctx.author.display_name | ||||
|                         ) | ||||
|                     ) | ||||
|                     break | ||||
|                 else: | ||||
|                     await ctx.send( | ||||
|                         "{} is slacking, and if they carry on not working, they'll be fired.".format( | ||||
|                             ctx.author.display_name | ||||
|                         ) | ||||
|                     ) | ||||
|                     timeoutcount += 1 | ||||
|                 await ctx.send( | ||||
|                     "``{}`` fell down the conveyor belt to be sorted again!".format(used["object"]) | ||||
|                 ) | ||||
|             elif answer.content.lower().strip() == used["action"]: | ||||
|                 await ctx.send( | ||||
|                     "Congratulations! You put ``{}`` down the correct chute! (**+50**)".format( | ||||
|  | ||||
| @ -69,12 +69,13 @@ class RPSLS(Cog): | ||||
| 
 | ||||
|     def get_emote(self, choice): | ||||
|         if choice == "rock": | ||||
|             return ":moyai:" | ||||
|             emote = ":moyai:" | ||||
|         elif choice == "spock": | ||||
|             return ":vulcan:" | ||||
|             emote = ":vulcan:" | ||||
|         elif choice == "paper": | ||||
|             return ":page_facing_up:" | ||||
|             emote = ":page_facing_up:" | ||||
|         elif choice in ["scissors", "lizard"]: | ||||
|             return ":{}:".format(choice) | ||||
|             emote = ":{}:".format(choice) | ||||
|         else: | ||||
|             return None | ||||
|             emote = None | ||||
|         return emote | ||||
|  | ||||
| @ -177,3 +177,7 @@ class SCP(Cog): | ||||
| 
 | ||||
|         msg = "http://www.scp-wiki.net/log-of-unexplained-locations" | ||||
|         await ctx.maybe_send_embed(msg) | ||||
| 
 | ||||
| 
 | ||||
| def setup(bot): | ||||
|     bot.add_cog(SCP(bot)) | ||||
|  | ||||
| @ -6,7 +6,6 @@ import discord | ||||
| from redbot.core import Config, checks, commands | ||||
| from redbot.core.bot import Red | ||||
| from redbot.core.commands import Cog | ||||
| from redbot.core.utils.chat_formatting import pagify | ||||
| 
 | ||||
| log = logging.getLogger("red.fox_v3.stealemoji") | ||||
| # Replaced with discord.Asset.read() | ||||
| @ -70,7 +69,8 @@ class StealEmoji(Cog): | ||||
|         """ | ||||
|         Base command for this cog. Check help for the commands list. | ||||
|         """ | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @checks.is_owner() | ||||
|     @stealemoji.command(name="clearemojis") | ||||
| @ -100,8 +100,7 @@ class StealEmoji(Cog): | ||||
|             await ctx.maybe_send_embed("No stolen emojis yet") | ||||
|             return | ||||
| 
 | ||||
|         for page in pagify(emoj, delims=[" "]): | ||||
|             await ctx.maybe_send_embed(page) | ||||
|         await ctx.maybe_send_embed(emoj) | ||||
| 
 | ||||
|     @checks.is_owner() | ||||
|     @stealemoji.command(name="notify") | ||||
| @ -269,36 +268,37 @@ class StealEmoji(Cog): | ||||
|                 break | ||||
| 
 | ||||
|         if guildbank is None: | ||||
|             if not await self.config.autobank(): | ||||
|                 return | ||||
|             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) | ||||
| 
 | ||||
|             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) | ||||
| 
 | ||||
|             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() | ||||
| 
 | ||||
|             if guildbank.text_channels: | ||||
|                 channel = guildbank.text_channels[0] | ||||
|                 await self.bot.send_to_owners(invite) | ||||
|                 log.info(f"Guild created id {guildbank.id}. Invite: {invite}") | ||||
|             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() | ||||
|                 return | ||||
| 
 | ||||
|             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(): | ||||
|  | ||||
| @ -37,7 +37,7 @@ class Timerole(Cog): | ||||
|         self.bot = bot | ||||
|         self.config = Config.get_conf(self, identifier=9811198108111121, force_registration=True) | ||||
|         default_global = {} | ||||
|         default_guild = {"announce": None, "reapply": True, "roles": {}, "skipbots": True} | ||||
|         default_guild = {"announce": None, "reapply": True, "roles": {}} | ||||
|         default_rolemember = {"had_role": False, "check_again_time": None} | ||||
| 
 | ||||
|         self.config.register_global(**default_global) | ||||
| @ -77,7 +77,8 @@ class Timerole(Cog): | ||||
|     @commands.guild_only() | ||||
|     async def timerole(self, ctx): | ||||
|         """Adjust timerole settings""" | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @timerole.command() | ||||
|     async def addrole( | ||||
| @ -92,9 +93,6 @@ class Timerole(Cog): | ||||
|             await ctx.maybe_send_embed("Error: Invalid time string.") | ||||
|             return | ||||
| 
 | ||||
|         if parsed_time is None: | ||||
|             return await ctx.maybe_send_embed("Error: Invalid time string.") | ||||
| 
 | ||||
|         days = parsed_time.days | ||||
|         hours = parsed_time.seconds // 60 // 60 | ||||
| 
 | ||||
| @ -154,14 +152,6 @@ class Timerole(Cog): | ||||
|         await self.config.guild(guild).reapply.set(not current_setting) | ||||
|         await ctx.maybe_send_embed(f"Reapplying roles is now set to: {not current_setting}") | ||||
| 
 | ||||
|     @timerole.command() | ||||
|     async def skipbots(self, ctx: commands.Context): | ||||
|         """Toggle skipping bots when adding/removing roles. Defaults to True""" | ||||
|         guild = ctx.guild | ||||
|         current_setting = await self.config.guild(guild).skipbots() | ||||
|         await self.config.guild(guild).skipbots.set(not current_setting) | ||||
|         await ctx.maybe_send_embed(f"Skipping bots is now set to: {not current_setting}") | ||||
| 
 | ||||
|     @timerole.command() | ||||
|     async def delrole(self, ctx: commands.Context, role: discord.Role): | ||||
|         """Deletes a role from being added/removed after specified time""" | ||||
| @ -210,9 +200,8 @@ class Timerole(Cog): | ||||
|             remove_results = "" | ||||
|             reapply = all_guilds[guild_id]["reapply"] | ||||
|             role_dict = all_guilds[guild_id]["roles"] | ||||
|             skipbots = all_guilds[guild_id]["skipbots"] | ||||
| 
 | ||||
|             if not any(role_dict.values()):  # No roles | ||||
|             if not any(role_data for role_data in role_dict.values()):  # No roles | ||||
|                 log.debug(f"No roles are configured for guild: {guild}") | ||||
|                 continue | ||||
| 
 | ||||
| @ -220,10 +209,6 @@ class Timerole(Cog): | ||||
|             # log.debug(f"{all_mr=}") | ||||
| 
 | ||||
|             async for member in AsyncIter(guild.members, steps=10): | ||||
| 
 | ||||
|                 if member.bot and skipbots: | ||||
|                     continue | ||||
| 
 | ||||
|                 addlist = [] | ||||
|                 removelist = [] | ||||
| 
 | ||||
| @ -247,7 +232,7 @@ class Timerole(Cog): | ||||
|                         log.debug(f"{member.display_name} - Not time to check again yet") | ||||
|                         continue | ||||
|                     member: discord.Member | ||||
|                     has_roles = {r.id for r in member.roles} | ||||
|                     has_roles = set(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 ( | ||||
| @ -311,11 +296,8 @@ class Timerole(Cog): | ||||
|                         log.exception("Failed Adding Roles") | ||||
|                         add_results += f"{member.display_name} : **(Failed Adding Roles)**\n" | ||||
|                     else: | ||||
|                         add_results += ( | ||||
|                             " \n".join( | ||||
|                                 f"{member.display_name} : {role.name}" for role in add_roles | ||||
|                             ) | ||||
|                             + "\n" | ||||
|                         add_results += " \n".join( | ||||
|                             f"{member.display_name} : {role.name}" for role in add_roles | ||||
|                         ) | ||||
|                         for role_id in addlist: | ||||
|                             await self.config.custom( | ||||
| @ -329,11 +311,8 @@ class Timerole(Cog): | ||||
|                         log.exception("Failed Removing Roles") | ||||
|                         remove_results += f"{member.display_name} : **(Failed Removing Roles)**\n" | ||||
|                     else: | ||||
|                         remove_results += ( | ||||
|                             " \n".join( | ||||
|                                 f"{member.display_name} : {role.name}" for role in remove_roles | ||||
|                             ) | ||||
|                             + "\n" | ||||
|                         remove_results += " \n".join( | ||||
|                             f"{member.display_name} : {role.name}" for role in remove_roles | ||||
|                         ) | ||||
|                         for role_id in removelist: | ||||
|                             await self.config.custom( | ||||
|  | ||||
							
								
								
									
										46
									
								
								tts/tts.py
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								tts/tts.py
									
									
									
									
									
								
							| @ -1,35 +1,11 @@ | ||||
| import io | ||||
| import logging | ||||
| from typing import Optional, TYPE_CHECKING | ||||
| 
 | ||||
| import discord | ||||
| from discord.ext.commands import BadArgument, Converter | ||||
| from gtts import gTTS | ||||
| from gtts.lang import _fallback_deprecated_lang, tts_langs | ||||
| from redbot.core import Config, commands | ||||
| from redbot.core.bot import Red | ||||
| from redbot.core.commands import Cog | ||||
| 
 | ||||
| log = logging.getLogger("red.fox_v3.tts") | ||||
| 
 | ||||
| if TYPE_CHECKING: | ||||
|     ISO639Converter = str | ||||
| else: | ||||
| 
 | ||||
|     class ISO639Converter(Converter): | ||||
|         async def convert(self, ctx, argument) -> str: | ||||
|             lang = _fallback_deprecated_lang(argument) | ||||
| 
 | ||||
|             try: | ||||
|                 langs = tts_langs() | ||||
|                 if lang not in langs: | ||||
|                     raise BadArgument("Language not supported: %s" % lang) | ||||
|             except RuntimeError as e: | ||||
|                 log.debug(str(e), exc_info=True) | ||||
|                 log.warning(str(e)) | ||||
| 
 | ||||
|             return lang | ||||
| 
 | ||||
| 
 | ||||
| class TTS(Cog): | ||||
|     """ | ||||
| @ -42,7 +18,7 @@ class TTS(Cog): | ||||
| 
 | ||||
|         self.config = Config.get_conf(self, identifier=9811198108111121, force_registration=True) | ||||
|         default_global = {} | ||||
|         default_guild = {"language": "en"} | ||||
|         default_guild = {} | ||||
| 
 | ||||
|         self.config.register_global(**default_global) | ||||
|         self.config.register_guild(**default_guild) | ||||
| @ -51,29 +27,13 @@ class TTS(Cog): | ||||
|         """Nothing to delete""" | ||||
|         return | ||||
| 
 | ||||
|     @commands.mod() | ||||
|     @commands.command() | ||||
|     async def ttslang(self, ctx: commands.Context, lang: ISO639Converter): | ||||
|         """ | ||||
|         Sets the default language for TTS in this guild. | ||||
| 
 | ||||
|         Default is `en` for English | ||||
|         """ | ||||
|         await self.config.guild(ctx.guild).language.set(lang) | ||||
|         await ctx.send(f"Default tts language set to {lang}") | ||||
| 
 | ||||
|     @commands.command(aliases=["t2s", "text2"]) | ||||
|     async def tts( | ||||
|         self, ctx: commands.Context, lang: Optional[ISO639Converter] = None, *, text: str | ||||
|     ): | ||||
|     async def tts(self, ctx: commands.Context, *, text: str): | ||||
|         """ | ||||
|         Send Text to speech messages as an mp3 | ||||
|         """ | ||||
|         if lang is None: | ||||
|             lang = await self.config.guild(ctx.guild).language() | ||||
| 
 | ||||
|         mp3_fp = io.BytesIO() | ||||
|         tts = gTTS(text, lang=lang) | ||||
|         tts = gTTS(text, lang="en") | ||||
|         tts.write_to_fp(mp3_fp) | ||||
|         mp3_fp.seek(0) | ||||
|         await ctx.send(file=discord.File(mp3_fp, "text.mp3")) | ||||
|  | ||||
| @ -19,7 +19,8 @@ class Unicode(Cog): | ||||
|     @commands.group(name="unicode", pass_context=True) | ||||
|     async def unicode(self, ctx): | ||||
|         """Encode/Decode a Unicode character.""" | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @unicode.command() | ||||
|     async def decode(self, ctx: commands.Context, character): | ||||
|  | ||||
| @ -90,7 +90,7 @@ async def parse_code(code, game): | ||||
|         if len(built) < digits: | ||||
|             built += c | ||||
| 
 | ||||
|         if built in ["T", "W", "N"]: | ||||
|         if built == "T" or built == "W" or built == "N": | ||||
|             # Random Towns | ||||
|             category = built | ||||
|             built = "" | ||||
| @ -116,6 +116,8 @@ 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") | ||||
| 
 | ||||
| @ -128,8 +130,11 @@ 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) | ||||
|     out_code = "".join(str(role) for role in digit_sort) | ||||
|     for role in digit_sort: | ||||
|         out_code += str(role) | ||||
| 
 | ||||
|     digit_sort = sorted(role for role in role_list if 10 <= role < 100) | ||||
|     if digit_sort: | ||||
|  | ||||
							
								
								
									
										103
									
								
								werewolf/game.py
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								werewolf/game.py
									
									
									
									
									
								
							| @ -62,6 +62,8 @@ class Game: | ||||
|         village: discord.TextChannel = None, | ||||
|         log_channel: discord.TextChannel = None, | ||||
|         game_code=None, | ||||
|         day_length=HALF_DAY_LENGTH * 2, | ||||
|         night_length=HALF_NIGHT_LENGTH * 2, | ||||
|     ): | ||||
|         self.bot = bot | ||||
|         self.guild = guild | ||||
| @ -73,6 +75,9 @@ class Game: | ||||
|         self.day_vote = {}  # author: target | ||||
|         self.vote_totals = {}  # id: total_votes | ||||
| 
 | ||||
|         self.half_day_length = day_length // 2 | ||||
|         self.half_night_length = night_length // 2 | ||||
| 
 | ||||
|         self.started = False | ||||
|         self.game_over = False | ||||
|         self.any_votes_remaining = False | ||||
| @ -97,6 +102,7 @@ class Game: | ||||
| 
 | ||||
|         self.loop = asyncio.get_event_loop() | ||||
| 
 | ||||
|         self.cycle_task = None | ||||
|         self.action_queue = deque() | ||||
|         self.current_action = None | ||||
|         self.listeners = {} | ||||
| @ -116,6 +122,13 @@ class Game: | ||||
|     #     for c_data in self.p_channels.values(): | ||||
|     #         asyncio.ensure_future(c_data["channel"].delete("Werewolf game-over")) | ||||
| 
 | ||||
|     def _prestart_status(self, ctx): | ||||
|         return ( | ||||
|             f"Currently **{len(self.players)} / {len(self.roles)}**\n" | ||||
|             f"Use `{ctx.prefix}ww code` to pick a game setup\n" | ||||
|             f"Use `{ctx.prefix}buildgame` to generate a new game" | ||||
|         ) | ||||
| 
 | ||||
|     async def setup(self, ctx: commands.Context): | ||||
|         """ | ||||
|         Runs the initial setup | ||||
| @ -127,15 +140,20 @@ class Game: | ||||
|         4. Start game | ||||
|         """ | ||||
|         if self.game_code: | ||||
|             # Turn random roles into real roles | ||||
|             await self.get_roles(ctx) | ||||
|         else: | ||||
|             await ctx.maybe_send_embed( | ||||
|                 f"No game code has been assigned, cannot start\n{self._prestart_status(ctx)}" | ||||
|             ) | ||||
|             return False | ||||
| 
 | ||||
|         if len(self.players) != len(self.roles): | ||||
|             await ctx.maybe_send_embed( | ||||
|                 f"Player count does not match role count, cannot start\n" | ||||
|                 f"Currently **{len(self.players)} / {len(self.roles)}**\n" | ||||
|                 f"Use `{ctx.prefix}ww code` to pick a game setup\n" | ||||
|                 f"Use `{ctx.prefix}buildgame` to generate a new game" | ||||
|                 f"{self._prestart_status(ctx)}" | ||||
|             ) | ||||
|             # Clear the roles to be randomly generated again | ||||
|             self.roles = [] | ||||
|             return False | ||||
| 
 | ||||
| @ -156,6 +174,7 @@ class Game: | ||||
|                 self.roles = [] | ||||
|                 return False | ||||
| 
 | ||||
|         # Check if the role is already in use. If so, cannot continue until removed | ||||
|         anyone_with_role = await anyone_has_role(self.guild.members, self.game_role) | ||||
|         if anyone_with_role is not None: | ||||
|             await ctx.maybe_send_embed( | ||||
| @ -164,6 +183,7 @@ class Game: | ||||
|             ) | ||||
|             return False | ||||
| 
 | ||||
|         # Add the game role to those who are playing | ||||
|         try: | ||||
|             for player in self.players: | ||||
|                 await player.member.add_roles(*[self.game_role]) | ||||
| @ -175,6 +195,7 @@ class Game: | ||||
|             ) | ||||
|             return False | ||||
| 
 | ||||
|         # Randomly assign the roles in the game to the players | ||||
|         await self.assign_roles() | ||||
| 
 | ||||
|         # Create category and channel with individual overwrites | ||||
| @ -219,14 +240,16 @@ class Game: | ||||
|                 return False | ||||
|         else: | ||||
|             self.save_perms[self.village_channel] = self.village_channel.overwrites | ||||
|             try: | ||||
|                 await self.village_channel.edit( | ||||
|                     name="🔵werewolf", | ||||
|                     reason="(BOT) New game of werewolf", | ||||
|                 ) | ||||
|             except discord.Forbidden as e: | ||||
|                 log.exception("Unable to rename Game Channel") | ||||
|                 await ctx.maybe_send_embed("Unable to rename Game Channel, ignoring") | ||||
| 
 | ||||
|             # Disable renaming channels, too easy to get rate limited. | ||||
|             # try: | ||||
|             #     await self.village_channel.edit( | ||||
|             #         name="🔵werewolf", | ||||
|             #         reason="(BOT) New game of werewolf", | ||||
|             #     ) | ||||
|             # except discord.Forbidden as e: | ||||
|             #     log.exception("Unable to rename Game Channel") | ||||
|             #     await ctx.maybe_send_embed("Unable to rename Game Channel, ignoring") | ||||
| 
 | ||||
|             try: | ||||
|                 for target, ow in overwrite.items(): | ||||
| @ -283,9 +306,9 @@ class Game: | ||||
|                 self.vote_groups[channel_id] = vote_group | ||||
| 
 | ||||
|         log.debug("Pre-cycle") | ||||
|         await asyncio.sleep(0) | ||||
|         await asyncio.sleep(0)  # Pass back to controller to avoid heartbeat issues | ||||
| 
 | ||||
|         asyncio.create_task(self._cycle())  # Start the loop | ||||
|         self.cycle_task = asyncio.create_task(self._cycle())  # Start the loop | ||||
|         return True | ||||
| 
 | ||||
|     # ###########START Notify structure############ | ||||
| @ -360,13 +383,15 @@ class Game: | ||||
|         self.any_votes_remaining = True | ||||
| 
 | ||||
|         # Now we sleep and let the day happen. Print the remaining daylight half way through | ||||
|         await asyncio.sleep(HALF_DAY_LENGTH)  # 4 minute days FixMe to 120 later | ||||
|         await asyncio.sleep(self.half_day_length) | ||||
|         if check(): | ||||
|             return | ||||
|         await self.village_channel.send( | ||||
|             embed=discord.Embed(title=f"*{HALF_DAY_LENGTH / 60} minutes of daylight remain...*") | ||||
|             embed=discord.Embed( | ||||
|                 title=f"*{self.half_day_length/ 60} minutes of daylight remain...*" | ||||
|             ) | ||||
|         ) | ||||
|         await asyncio.sleep(HALF_DAY_LENGTH)  # 4 minute days FixMe to 120 later | ||||
|         await asyncio.sleep(self.half_day_length) | ||||
| 
 | ||||
|         # Need a loop here to wait for trial to end | ||||
|         while self.ongoing_vote: | ||||
| @ -501,13 +526,13 @@ class Game: | ||||
| 
 | ||||
|         await self._notify("at_night_start") | ||||
| 
 | ||||
|         await asyncio.sleep(HALF_NIGHT_LENGTH)  # 2 minutes FixMe to 120 later | ||||
|         await asyncio.sleep(self.half_night_length) | ||||
|         await self.village_channel.send( | ||||
|             embed=discord.Embed(title=f"**{HALF_NIGHT_LENGTH / 60} minutes of night remain...**") | ||||
|             embed=discord.Embed( | ||||
|                 title=f"**{self.half_night_length / 60} minutes of night remain...**" | ||||
|             ) | ||||
|         ) | ||||
|         await asyncio.sleep(HALF_NIGHT_LENGTH)  # 1.5 minutes FixMe to 90 later | ||||
| 
 | ||||
|         await asyncio.sleep(3)  # .5 minutes FixMe to 30 Later | ||||
|         await asyncio.sleep(self.half_night_length) | ||||
| 
 | ||||
|         self.action_queue.append(self._at_night_end()) | ||||
| 
 | ||||
| @ -526,10 +551,9 @@ class Game: | ||||
| 
 | ||||
|     async def _notify(self, event_name, **kwargs): | ||||
|         for i in range(1, 7):  # action guide 1-6 (0 is no action) | ||||
|             tasks = [ | ||||
|                 asyncio.create_task(event(**kwargs)) | ||||
|                 for event in self.listeners.get(event_name, {}).get(i, []) | ||||
|             ] | ||||
|             tasks = [] | ||||
|             for event in self.listeners.get(event_name, {}).get(i, []): | ||||
|                 tasks.append(asyncio.create_task(event(**kwargs))) | ||||
| 
 | ||||
|             # Run same-priority task simultaneously | ||||
|             await asyncio.gather(*tasks) | ||||
| @ -554,18 +578,23 @@ class Game: | ||||
|     # ###########END Notify structure############ | ||||
| 
 | ||||
|     async def generate_targets(self, channel, with_roles=False): | ||||
|         embed = discord.Embed(title="Remaining Players", description="[ID] - [Name]") | ||||
|         embed = discord.Embed(title="Remaining Players", description="ID || Name") | ||||
|         for i, player in enumerate(self.players): | ||||
|             status = "" if player.alive else "*[Dead]*-" | ||||
|             if player.alive: | ||||
|                 status = "" | ||||
|             else: | ||||
|                 status = "*[Dead]*-" | ||||
|             if with_roles or not player.alive: | ||||
|                 embed.add_field( | ||||
|                     name=f"{i} - {status}{player.member.display_name}", | ||||
|                     name=f"{i} || {status}{player.member.display_name}", | ||||
|                     value=f"{player.role}", | ||||
|                     inline=False, | ||||
|                 ) | ||||
|             else: | ||||
|                 embed.add_field( | ||||
|                     name=f"{i} - {status}{player.member.display_name}", inline=False, value="____" | ||||
|                     name=f"{i} || {status}{player.member.display_name}", | ||||
|                     inline=False, | ||||
|                     value="\N{Zero Width Space}", | ||||
|                 ) | ||||
| 
 | ||||
|         return await channel.send(embed=embed) | ||||
| @ -577,7 +606,7 @@ class Game: | ||||
|         if channel_id not in self.p_channels: | ||||
|             self.p_channels[channel_id] = self.default_secret_channel.copy() | ||||
| 
 | ||||
|         for _ in range(10):  # Retry 10 times | ||||
|         for x 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) | ||||
| @ -704,7 +733,9 @@ class Game: | ||||
|             if not self.any_votes_remaining: | ||||
|                 await channel.send("Voting is not allowed right now") | ||||
|                 return | ||||
|         elif channel.name not in self.p_channels: | ||||
|         elif channel.name in self.p_channels: | ||||
|             pass | ||||
|         else: | ||||
|             # Not part of the game | ||||
|             await channel.send("Cannot vote in this channel") | ||||
|             return | ||||
| @ -753,14 +784,14 @@ class Game: | ||||
|             await self._at_voted(target) | ||||
| 
 | ||||
|     async def eval_results(self, target, source=None, method=None): | ||||
|         if method is None: | ||||
|         if method is not None: | ||||
|             out = "**{ID}** - " + method | ||||
|             return out.format(ID=target.id, target=target.member.display_name) | ||||
|         else: | ||||
|             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 | ||||
| @ -855,7 +886,7 @@ class Game: | ||||
|         self.players.sort(key=lambda pl: pl.member.display_name.lower()) | ||||
| 
 | ||||
|         if len(self.roles) != len(self.players): | ||||
|             await self.village_channel.send("Unhandled error - roles!=players") | ||||
|             await self.village_channel.send("Unhandled error - roles != # players") | ||||
|             return False | ||||
| 
 | ||||
|         for idx, role in enumerate(self.roles): | ||||
|  | ||||
| @ -36,6 +36,7 @@ class Werewolf(Cog): | ||||
|             "category_id": None, | ||||
|             "channel_id": None, | ||||
|             "log_channel_id": None, | ||||
|             "default_game": {"daytime": 60 * 5, "nighttime": 60 * 5}, | ||||
|         } | ||||
| 
 | ||||
|         self.config.register_global(**default_global) | ||||
| @ -75,7 +76,8 @@ class Werewolf(Cog): | ||||
|         """ | ||||
|         Base command to adjust settings. Check help for command list. | ||||
|         """ | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @commands.guild_only() | ||||
|     @wwset.command(name="list") | ||||
| @ -165,7 +167,8 @@ class Werewolf(Cog): | ||||
|         """ | ||||
|         Base command for this cog. Check help for the commands list. | ||||
|         """ | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None: | ||||
|             pass | ||||
| 
 | ||||
|     @commands.guild_only() | ||||
|     @ww.command(name="new") | ||||
| @ -346,7 +349,8 @@ class Werewolf(Cog): | ||||
|         """ | ||||
|         Find custom roles by name, alignment, category, or ID | ||||
|         """ | ||||
|         pass | ||||
|         if ctx.invoked_subcommand is None or ctx.invoked_subcommand == self.ww_search: | ||||
|             pass | ||||
| 
 | ||||
|     @ww_search.command(name="name") | ||||
|     async def ww_search_name(self, ctx: commands.Context, *, name): | ||||
| @ -395,12 +399,13 @@ class Werewolf(Cog): | ||||
|             # Private message, can't get guild | ||||
|             await ctx.maybe_send_embed("Cannot start game from DM!") | ||||
|             return None | ||||
| 
 | ||||
|         if guild.id not in self.games or self.games[guild.id].game_over: | ||||
|             await ctx.maybe_send_embed("Starting a new game...") | ||||
|             valid, role, category, channel, log_channel = await self._get_settings(ctx) | ||||
| 
 | ||||
|             if not valid: | ||||
|                 await ctx.maybe_send_embed("Cannot start a new game") | ||||
|                 await ctx.maybe_send_embed("Cannot start a new game, check server settings.") | ||||
|                 return None | ||||
| 
 | ||||
|             who_has_the_role = await anyone_has_role(guild.members, role) | ||||
| @ -410,7 +415,15 @@ class Werewolf(Cog): | ||||
|                 ) | ||||
|                 return None | ||||
|             self.games[guild.id] = Game( | ||||
|                 self.bot, guild, role, category, channel, log_channel, game_code | ||||
|                 bot=self.bot, | ||||
|                 guild=guild, | ||||
|                 role=role, | ||||
|                 category=category, | ||||
|                 village=channel, | ||||
|                 log_channel=log_channel, | ||||
|                 game_code=game_code, | ||||
|                 day_length=await self.config.guild(guild).default_game.day_length(), | ||||
|                 night_length=await self.config.guild(guild).default_game.night_length(), | ||||
|             ) | ||||
| 
 | ||||
|         return self.games[guild.id] | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user