diff --git a/.gitignore b/.gitignore
index 9ec1673..7a224ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
venv/
v-data/
database.sqlite3
+/venv3.4/
diff --git a/README.md b/README.md
index 94f3a2a..c37f84f 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ Cog Function
| ccrole | **Release** | Create custom commands that also assign roles
May have some bugs, please create an issue if you find any |
| chatter | **Beta** | Chat-bot trained to talk like your guild
Missing some key features, but currently functional. See [Chatter](https://github.com/bobloy/Fox-V3/tree/master/chatter) for install instructions |
| coglint | **Alpha** | Error check code in python syntax posted to discord
Works, but probably needs more turning to work for cogs |
+| conquest | **Alpha** | Manage maps for war games and RPGs
Lots of additional features are planned, currently function with simple map |
| dad | **Beta** | Tell dad jokes
Works great! |
| exclusiverole | **Alpha** | Prevent certain roles from getting any other roles
Fully functional, but pretty simple |
| fight | **Incomplete** | Organize bracket tournaments within discord
Still in-progress, a massive project |
@@ -20,7 +21,7 @@ Cog Function
| infochannel | **Beta** | Create a channel to display server info
Just released, please report bugs |
| lovecalculator | **Alpha** | Calculate the love between two users
[Snap-Ons] Just updated to V3 |
| lseen | **Alpha** | Track when a member was last online
Alpha release, please report bugs |
-| nudity | **Incomplete** | Checks for NSFW images posted in non-NSFW channels
Library this is based on has a bug, waiting for author to merge my PR |
+| nudity | **Alpha** | Checks for NSFW images posted in non-NSFW channels
Switched libraries, now functional |
| planttycoon | **Alpha** | Grow your own plants!
[Snap-Ons] Updated to V3, likely to contain bugs |
| qrinvite | **Alpha** | Create a QR code invite for the server
Alpha release, please report any bugs |
| reactrestrict | **Alpha** | Removes reactions by role per channel
A bit clunky, but functional |
diff --git a/announcedaily/announcedaily.py b/announcedaily/announcedaily.py
index fb02756..aa50e6c 100644
--- a/announcedaily/announcedaily.py
+++ b/announcedaily/announcedaily.py
@@ -38,6 +38,10 @@ class AnnounceDaily(Cog):
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
async def _get_msgs(self):
return DEFAULT_MESSAGES + await self.config.messages()
diff --git a/announcedaily/info.json b/announcedaily/info.json
index 84315bf..57aa7f1 100644
--- a/announcedaily/info.json
+++ b/announcedaily/info.json
@@ -2,15 +2,12 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Send daily announcements to all servers at a specified times",
- "hidden": true,
+ "hidden": false,
"install_msg": "Thank you for installing AnnounceDaily! Get started with `[p]load announcedaily` and `[p]help AnnounceDaily`",
"short": "Send daily announcements",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy"
]
diff --git a/audiotrivia/audiotrivia.py b/audiotrivia/audiotrivia.py
index 586f5b7..9465d9a 100644
--- a/audiotrivia/audiotrivia.py
+++ b/audiotrivia/audiotrivia.py
@@ -12,6 +12,7 @@ from redbot.core import commands, Config, checks
from redbot.core.bot import Red
from redbot.core.data_manager import cog_data_path
from redbot.core.utils.chat_formatting import box
+
# from redbot.cogs.audio.utils import userlimit
@@ -65,7 +66,9 @@ class AudioTrivia(Trivia):
"""Set whether or not short audio will be repeated"""
settings = self.audioconf.guild(ctx.guild)
await settings.repeat.set(true_or_false)
- await ctx.send("Done. Repeating short audio is now set to {}.".format(true_or_false))
+ await ctx.send(
+ "Done. Repeating short audio is now set to {}.".format(true_or_false)
+ )
@commands.group(invoke_without_command=True)
@commands.guild_only()
@@ -89,19 +92,25 @@ class AudioTrivia(Trivia):
categories = [c.lower() for c in categories]
session = self._get_trivia_session(ctx.channel)
if session is not None:
- await ctx.send("There is already an ongoing trivia session in this channel.")
+ await ctx.send(
+ "There is already an ongoing trivia session in this channel."
+ )
return
status = await self.audio.config.status()
notify = await self.audio.config.guild(ctx.guild).notify()
if status:
await ctx.send(
- "It is recommended to disable audio status with `{}audioset status`".format(ctx.prefix)
+ "It is recommended to disable audio status with `{}audioset status`".format(
+ ctx.prefix
+ )
)
if notify:
await ctx.send(
- "It is recommended to disable audio notify with `{}audioset notify`".format(ctx.prefix)
+ "It is recommended to disable audio notify with `{}audioset notify`".format(
+ ctx.prefix
+ )
)
if not self.audio._player_check(ctx):
@@ -109,7 +118,9 @@ class AudioTrivia(Trivia):
if not ctx.author.voice.channel.permissions_for(
ctx.me
).connect or self.audio.is_vc_full(ctx.author.voice.channel):
- return await ctx.send("I don't have permission to connect to your channel.")
+ return await ctx.send(
+ "I don't have permission to connect to your channel."
+ )
await lavalink.connect(ctx.author.voice.channel)
lavaplayer = lavalink.get_player(ctx.guild.id)
lavaplayer.store("connect", datetime.datetime.utcnow())
@@ -166,7 +177,10 @@ class AudioTrivia(Trivia):
# Delay in audiosettings overwrites delay in settings
combined_settings = {**settings, **audiosettings}
session = AudioSession.start(
- ctx=ctx, question_list=trivia_dict, settings=combined_settings, player=lavaplayer
+ ctx=ctx,
+ question_list=trivia_dict,
+ settings=combined_settings,
+ player=lavaplayer,
)
self.trivia_sessions.append(session)
LOG.debug("New audio trivia session; #%s in %d", ctx.channel, ctx.guild.id)
@@ -200,7 +214,9 @@ class AudioTrivia(Trivia):
try:
path = next(p for p in self._audio_lists() if p.stem == category)
except StopIteration:
- raise FileNotFoundError("Could not find the `{}` category.".format(category))
+ raise FileNotFoundError(
+ "Could not find the `{}` category.".format(category)
+ )
with path.open(encoding="utf-8") as file:
try:
diff --git a/audiotrivia/info.json b/audiotrivia/info.json
index 697875d..655aa0d 100644
--- a/audiotrivia/info.json
+++ b/audiotrivia/info.json
@@ -2,15 +2,12 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Start an Audio Trivia game",
"hidden": false,
"install_msg": "Thank you for installing Audio trivia!\n You **MUST** unload trivia to use this (`[p]unload trivia`)\n Then you can get started with `[p]load audiotrivia` and `[p]help AudioTrivia`",
"short": "Start an Audio Trivia game",
+ "end_user_data_statement": "This cog expands the core Audio and Trivia cogs without collecting any additional End User Data.\nSee the core End User Data storage for more information",
"tags": [
"fox",
"bobloy",
diff --git a/ccrole/ccrole.py b/ccrole/ccrole.py
index 9be63f2..5dbbf1f 100644
--- a/ccrole/ccrole.py
+++ b/ccrole/ccrole.py
@@ -22,6 +22,10 @@ class CCRole(commands.Cog):
self.config.register_guild(**default_guild)
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.guild_only()
@commands.group()
async def ccrole(self, ctx: commands.Context):
@@ -29,7 +33,7 @@ class CCRole(commands.Cog):
Highly customizable custom commands with role management."""
if not ctx.invoked_subcommand:
- await ctx.send_help()
+ pass
@ccrole.command(name="add")
@checks.mod_or_permissions(administrator=True)
diff --git a/ccrole/info.json b/ccrole/info.json
index 6e0d8f7..8021be9 100644
--- a/ccrole/info.json
+++ b/ccrole/info.json
@@ -2,15 +2,12 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
- "description": "[Incomplete] Creates custom commands to adjust roles and send custom messages",
+ "min_bot_version": "3.3.0",
+ "description": "Creates custom commands to adjust roles and send custom messages",
"hidden": false,
"install_msg": "Thank you for installing Custom Commands w/ Roles. Get started with `[p]load ccrole` and `[p]help CCRole`",
- "short": "[Incomplete] Creates commands that adjust roles",
+ "short": "Creates commands that adjust roles",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"fox",
"bobloy",
diff --git a/chatter/README.md b/chatter/README.md
index 933162a..e8c03d6 100644
--- a/chatter/README.md
+++ b/chatter/README.md
@@ -162,12 +162,53 @@ This command trains Chatter on the specified channel based on the configured
settings. This can take a long time to process.
+### Train Ubuntu
+
+```
+[p]chatter trainubuntu
+```
+*WARNING:* This will trigger a large download and use a lot of processing power
+
+This command trains Chatter on the publicly available Ubuntu Dialogue Corpus. (It'll talk like a geek)
+
+
## Switching Algorithms
```
[p]chatter algorithm X
```
+or
+```
+[p]chatter algo X 0.95
+```
Chatter can be configured to use one of three different Similarity algorithms.
Changing this can help if the response speed is too slow, but can reduce the accuracy of results.
+
+The second argument is the maximum similarity threshold,
+lowering that will make the bot stop searching sooner.
+
+Default maximum similarity threshold is 0.90
+
+
+## Switching Pretrained Models
+
+```
+[p]chatter model X
+```
+
+Chatter can be configured to use one of three pretrained statistical models for English.
+
+I have not noticed any advantage to changing this,
+but supposedly it would help by splitting the search term into more useful parts.
+
+See [here](https://spacy.io/models) for more info on spaCy models.
+
+Before you're able to use the *large* model (option 3), you must install it through pip.
+
+*Warning:* This is ~800MB download.
+
+```
+[p]pipinstall 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
+```
diff --git a/chatter/chat.py b/chatter/chat.py
index 10388e0..20d13ff 100644
--- a/chatter/chat.py
+++ b/chatter/chat.py
@@ -1,19 +1,25 @@
import asyncio
+import logging
import os
import pathlib
from datetime import datetime, timedelta
+from typing import Literal
import discord
from chatterbot import ChatBot
from chatterbot.comparisons import JaccardSimilarity, LevenshteinDistance, SpacySimilarity
from chatterbot.response_selection import get_random_response
-from chatterbot.trainers import ChatterBotCorpusTrainer, ListTrainer
+from chatterbot.trainers import ChatterBotCorpusTrainer, ListTrainer, UbuntuCorpusTrainer
from redbot.core import Config, commands
from redbot.core.commands import Cog
from redbot.core.data_manager import cog_data_path
+from redbot.core.utils.predicates import MessagePredicate
+from redbot.core.utils import AsyncIter
+log = logging.getLogger("red.fox_v3.chat")
-class ENG_LG: # TODO: Add option to use this large model
+
+class ENG_LG:
ISO_639_1 = "en_core_web_lg"
ISO_639 = "eng"
ENGLISH_NAME = "English"
@@ -25,6 +31,12 @@ class ENG_MD:
ENGLISH_NAME = "English"
+class ENG_SM:
+ ISO_639_1 = "en_core_web_sm"
+ ISO_639 = "eng"
+ ENGLISH_NAME = "English"
+
+
class Chatter(Cog):
"""
This cog trains a chatbot that will talk like members of your Guild
@@ -39,7 +51,13 @@ class Chatter(Cog):
path: pathlib.Path = cog_data_path(self)
self.data_path = path / "database.sqlite3"
- self.chatbot = self._create_chatbot(self.data_path, SpacySimilarity, 0.45, ENG_MD)
+ # TODO: Move training_model and similarity_algo to config
+ # TODO: Add an option to see current settings
+
+ self.tagger_language = ENG_MD
+ self.similarity_algo = SpacySimilarity
+ self.similarity_threshold = 0.90
+ self.chatbot = self._create_chatbot()
# self.chatbot.set_trainer(ListTrainer)
# self.trainer = ListTrainer(self.chatbot)
@@ -49,18 +67,22 @@ class Chatter(Cog):
self.loop = asyncio.get_event_loop()
- def _create_chatbot(
- self, data_path, similarity_algorithm, similarity_threshold, tagger_language
- ):
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
+ def _create_chatbot(self):
+
return ChatBot(
"ChatterBot",
storage_adapter="chatterbot.storage.SQLStorageAdapter",
- database_uri="sqlite:///" + str(data_path),
- statement_comparison_function=similarity_algorithm,
+ 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=similarity_threshold,
- tagger_language=tagger_language,
+ maximum_similarity_threshold=self.similarity_threshold,
+ tagger_language=self.tagger_language,
+ logger=log,
)
async def _get_conversation(self, ctx, in_channel: discord.TextChannel = None):
@@ -99,7 +121,7 @@ class Chatter(Cog):
try:
async for message in channel.history(
- limit=None, after=after, oldest_first=True
+ limit=None, after=after, oldest_first=True
).filter(
predicate=predicate
): # type: discord.Message
@@ -130,6 +152,11 @@ class Chatter(Cog):
return out
+ def _train_ubuntu(self):
+ trainer = UbuntuCorpusTrainer(self.chatbot)
+ trainer.train()
+ return True
+
def _train_english(self):
trainer = ChatterBotCorpusTrainer(self.chatbot)
# try:
@@ -182,14 +209,18 @@ class Chatter(Cog):
try:
os.remove(self.data_path)
except PermissionError:
- await ctx.maybe_send_embed("Failed to clear training database. Please wait a bit and try again")
+ await ctx.maybe_send_embed(
+ "Failed to clear training database. Please wait a bit and try again"
+ )
- self._create_chatbot(self.data_path, SpacySimilarity, 0.45, ENG_MD)
+ self._create_chatbot()
await ctx.tick()
- @chatter.command(name="algorithm")
- async def chatter_algorithm(self, ctx: commands.Context, algo_number: int):
+ @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 after reload is Spacy
@@ -198,17 +229,61 @@ class Chatter(Cog):
2: Levenshtein
"""
- algos = [(SpacySimilarity, 0.45), (JaccardSimilarity, 0.75), (LevenshteinDistance, 0.75)]
+ algos = [SpacySimilarity, JaccardSimilarity, LevenshteinDistance]
if algo_number < 0 or algo_number > 2:
await ctx.send_help()
return
- self.chatbot = self._create_chatbot(
- self.data_path, algos[algo_number][0], algos[algo_number][1], ENG_MD
- )
+ if threshold is not None:
+ if threshold >= 1 or threshold <= 0:
+ await ctx.maybe_send_embed(
+ "Threshold must be a number between 0 and 1 (exclusive)"
+ )
+ return
+ else:
+ self.similarity_algo = threshold
- await ctx.tick()
+ self.similarity_algo = algos[algo_number]
+ async with ctx.typing():
+ self.chatbot = self._create_chatbot()
+
+ await ctx.tick()
+
+ @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 after reload is Medium
+
+ 0: Small
+ 1: Medium
+ 2: Large (Requires additional setup)
+ """
+
+ models = [ENG_SM, ENG_MD, ENG_LG]
+
+ if model_number < 0 or model_number > 2:
+ await ctx.send_help()
+ return
+
+ if model_number == 2:
+ await ctx.maybe_send_embed(
+ "Additional requirements needed. See guide before continuing.\n" "Continue?"
+ )
+ pred = MessagePredicate.yes_or_no(ctx)
+ try:
+ await self.bot.wait_for("message", check=pred, timeout=30)
+ except TimeoutError:
+ await ctx.send("Response timed out, please try again later.")
+ return
+ if not pred.result:
+ return
+
+ self.tagger_language = models[model_number]
+ async with ctx.typing():
+ self.chatbot = self._create_chatbot()
+
+ await ctx.maybe_send_embed(f"Model has been switched to {self.tagger_language.ISO_639_1}")
@chatter.command(name="minutes")
async def minutes(self, ctx: commands.Context, minutes: int):
@@ -260,6 +335,27 @@ class Chatter(Cog):
else:
await ctx.maybe_send_embed("Error occurred :(")
+ @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.
+ """
+
+ if not confirmation:
+ await ctx.maybe_send_embed(
+ "Warning: This command downloads ~500MB then eats your CPU for training\n"
+ "If you're sure you want to continue, run `[p]chatter trainubuntu True`"
+ )
+ return
+
+ async with ctx.typing():
+ future = await self.loop.run_in_executor(None, self._train_ubuntu)
+
+ if future:
+ await ctx.send("Training successful!")
+ else:
+ await ctx.send("Error occurred :(")
+
@chatter.command(name="trainenglish")
async def chatter_train_english(self, ctx: commands.Context):
"""
diff --git a/chatter/info.json b/chatter/info.json
index fe1f554..abf51a8 100644
--- a/chatter/info.json
+++ b/chatter/info.json
@@ -2,11 +2,7 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 3,
- 10
- ],
+ "min_bot_version": "3.3.10",
"description": "Create an offline chatbot that talks like your average member using Machine Learning",
"hidden": false,
"install_msg": "Thank you for installing Chatter! Get started ith `[p]load chatter` and `[p]help Chatter`",
@@ -24,6 +20,7 @@
"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.",
"tags": [
"chat",
"chatbot",
diff --git a/coglint/coglint.py b/coglint/coglint.py
index f1a58ee..6595980 100644
--- a/coglint/coglint.py
+++ b/coglint/coglint.py
@@ -28,6 +28,10 @@ class CogLint(Cog):
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.command()
async def autolint(self, ctx: commands.Context):
"""Toggles automatically linting code"""
diff --git a/coglint/info.json b/coglint/info.json
index aa3f665..420b8af 100644
--- a/coglint/info.json
+++ b/coglint/info.json
@@ -2,16 +2,15 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Lint python code posted in chat",
"hidden": true,
"install_msg": "Thank you for installing CogLint! Get started with `[p]load coglint` and `[p]help CogLint`",
- "requirements": ["pylint"],
+ "requirements": [
+ "pylint"
+ ],
"short": "Python cog linter",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"utils",
diff --git a/conquest/Map Ideas/AxisAllies_MAP_006L.jpg b/conquest/Map Ideas/AxisAllies_MAP_006L.jpg
new file mode 100644
index 0000000..9bb2755
Binary files /dev/null and b/conquest/Map Ideas/AxisAllies_MAP_006L.jpg differ
diff --git a/conquest/Map Ideas/j0e88vlp28pz.png b/conquest/Map Ideas/j0e88vlp28pz.png
new file mode 100644
index 0000000..3c97e53
Binary files /dev/null and b/conquest/Map Ideas/j0e88vlp28pz.png differ
diff --git a/conquest/__init__.py b/conquest/__init__.py
new file mode 100644
index 0000000..bb8992e
--- /dev/null
+++ b/conquest/__init__.py
@@ -0,0 +1,15 @@
+from redbot.core import data_manager
+
+from .conquest import Conquest
+from .mapmaker import MapMaker
+
+
+async def setup(bot):
+ cog = Conquest(bot)
+ data_manager.bundled_data_path(cog)
+ await cog.load_data()
+
+ bot.add_cog(cog)
+
+ cog2 = MapMaker(bot)
+ bot.add_cog(cog2)
diff --git a/conquest/conquest.py b/conquest/conquest.py
new file mode 100644
index 0000000..fb8b280
--- /dev/null
+++ b/conquest/conquest.py
@@ -0,0 +1,410 @@
+import asyncio
+import json
+import os
+import pathlib
+from abc import ABC
+from shutil import copyfile
+from typing import Optional
+
+import discord
+from PIL import Image, ImageChops, ImageColor, ImageOps
+from discord.ext.commands import Greedy
+from redbot.core import Config, commands
+from redbot.core.bot import Red
+from redbot.core.data_manager import bundled_data_path, cog_data_path
+
+
+class Conquest(commands.Cog):
+ """
+ Cog for
+ """
+
+ default_zoom_json = {"enabled": False, "x": -1, "y": -1, "zoom": 1.0}
+
+ def __init__(self, bot: Red):
+ super().__init__()
+ self.bot = bot
+ self.config = Config.get_conf(
+ self, identifier=67111110113117101115116, force_registration=True
+ )
+
+ default_guild = {}
+ default_global = {"current_map": None}
+ self.config.register_guild(**default_guild)
+ self.config.register_global(**default_global)
+
+ self.data_path: pathlib.Path = cog_data_path(self)
+ self.asset_path: Optional[pathlib.Path] = None
+
+ self.current_map = None
+ self.map_data = None
+ self.ext = None
+ self.ext_format = None
+
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
+ async def load_data(self):
+ """
+ Initial loading of data from bundled_data_path and config
+ """
+ self.asset_path = bundled_data_path(self) / "assets"
+ self.current_map = await self.config.current_map()
+
+ if self.current_map:
+ await self.current_map_load()
+
+ async def current_map_load(self):
+ map_data_path = self.asset_path / self.current_map / "data.json"
+ 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()
+
+ @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:
+ if self.current_map is not None:
+ await self._conquest_current(ctx)
+
+ @conquest.command(name="list")
+ async def _conquest_list(self, ctx: commands.Context):
+ """
+ List currently available maps
+ """
+ maps_json = self.asset_path / "maps.json"
+
+ with maps_json.open() as maps:
+ maps_json = json.load(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"""
+ if ctx.invoked_subcommand is None:
+ pass
+
+ @conquest_set.command(name="resetzoom")
+ async def _conquest_set_resetzoom(self, ctx: commands.Context):
+ """Resets the zoom level of the current map"""
+ if self.current_map is None:
+ await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
+ return
+
+ zoom_json_path = self.data_path / self.current_map / "settings.json"
+ if not zoom_json_path.exists():
+ await ctx.maybe_send_embed(
+ f"No zoom data found for {self.current_map}, reset not needed"
+ )
+ return
+
+ with zoom_json_path.open("w+") as zoom_json:
+ json.dump({"enabled": False}, zoom_json)
+
+ await ctx.tick()
+
+ @conquest_set.command(name="zoom")
+ async def _conquest_set_zoom(self, ctx: commands.Context, x: int, y: int, zoom: float):
+ """
+ Set the zoom level and position of the current map
+
+ x: positive integer
+ y: positive integer
+ zoom: float greater than or equal to 1
+ """
+ if self.current_map is None:
+ await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
+ return
+
+ if x < 0 or y < 0 or zoom < 1:
+ await ctx.send_help()
+ return
+
+ zoom_json_path = self.data_path / self.current_map / "settings.json"
+
+ zoom_data = self.default_zoom_json.copy()
+ zoom_data["enabled"] = True
+ zoom_data["x"] = x
+ zoom_data["y"] = y
+ zoom_data["zoom"] = zoom
+
+ with zoom_json_path.open("w+") as zoom_json:
+ json.dump(zoom_data, zoom_json)
+
+ await ctx.tick()
+
+ @conquest_set.command(name="zoomtest")
+ async def _conquest_set_zoomtest(self, ctx: commands.Context, x: int, y: int, zoom: float):
+ """
+ Test the zoom level and position of the current map
+
+ x: positive integer
+ y: positive integer
+ zoom: float greater than or equal to 1
+ """
+ if self.current_map is None:
+ await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
+ return
+
+ if x < 0 or y < 0 or zoom < 1:
+ await ctx.send_help()
+ return
+
+ zoomed_path = await self._create_zoomed_map(
+ self.data_path / self.current_map / f"current.{self.ext}", x, y, zoom
+ )
+
+ await ctx.send(file=discord.File(fp=zoomed_path, filename=f"current_zoomed.{self.ext}",))
+
+ async def _create_zoomed_map(self, map_path, x, y, zoom, **kwargs):
+ current_map = Image.open(map_path)
+
+ w, h = current_map.size
+ zoom2 = zoom * 2
+ zoomed_map = current_map.crop((x - w / zoom2, y - h / zoom2, x + w / zoom2, y + h / zoom2))
+ # zoomed_map = zoomed_map.resize((w, h), Image.LANCZOS)
+ zoomed_map.save(self.data_path / self.current_map / f"zoomed.{self.ext}", self.ext_format)
+ return self.data_path / self.current_map / f"zoomed.{self.ext}"
+
+ @conquest_set.command(name="save")
+ async def _conquest_set_save(self, ctx: commands.Context, *, save_name):
+ """Save the current map to be loaded later"""
+ if self.current_map is None:
+ await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
+ return
+
+ current_map_folder = self.data_path / self.current_map
+ current_map = current_map_folder / f"current.{self.ext}"
+
+ if not current_map_folder.exists() or not current_map.exists():
+ await ctx.maybe_send_embed("Current map doesn't exist! Try setting a new one")
+ return
+
+ copyfile(current_map, current_map_folder / f"{save_name}.{self.ext}")
+ await ctx.tick()
+
+ @conquest_set.command(name="load")
+ async def _conquest_set_load(self, ctx: commands.Context, *, save_name):
+ """Load a saved map to be the current map"""
+ if self.current_map is None:
+ await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
+ return
+
+ current_map_folder = self.data_path / self.current_map
+ current_map = current_map_folder / f"current.{self.ext}"
+ saved_map = current_map_folder / f"{save_name}.{self.ext}"
+
+ if not current_map_folder.exists() or not saved_map.exists():
+ await ctx.maybe_send_embed(f"Saved map not found in the {self.current_map} folder")
+ return
+
+ copyfile(saved_map, current_map)
+ await ctx.tick()
+
+ @conquest_set.command(name="map")
+ async def _conquest_set_map(self, ctx: commands.Context, mapname: str, reset: bool = False):
+ """
+ Select a map from current available maps
+
+ To add more maps, see the guide (WIP)
+ """
+ map_dir = self.asset_path / mapname
+ if not map_dir.exists() or not map_dir.is_dir():
+ await ctx.maybe_send_embed(
+ f"Map `{mapname}` was not found in the {self.asset_path} directory"
+ )
+ return
+
+ self.current_map = mapname
+ await self.config.current_map.set(self.current_map) # Save to config too
+
+ await self.current_map_load()
+
+ # map_data_path = self.asset_path / mapname / "data.json"
+ # with map_data_path.open() as mapdata:
+ # self.map_data = json.load(mapdata)
+ #
+ # self.ext = self.map_data["extension"]
+
+ current_map_folder = self.data_path / self.current_map
+ current_map = current_map_folder / f"current.{self.ext}"
+
+ if not reset and current_map.exists():
+ await ctx.maybe_send_embed(
+ "This map is already in progress, resuming from last game\n"
+ "Use `[p]conquest set map [mapname] True` to start a new game"
+ )
+ else:
+ if not current_map_folder.exists():
+ os.makedirs(current_map_folder)
+ copyfile(self.asset_path / mapname / f"blank.{self.ext}", current_map)
+
+ await ctx.tick()
+
+ @conquest.command(name="current")
+ async def _conquest_current(self, ctx: commands.Context):
+ """
+ Send the current map.
+ """
+ if self.current_map is None:
+ await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
+ return
+
+ current_img = self.data_path / self.current_map / f"current.{self.ext}"
+
+ await self._send_maybe_zoomed_map(ctx, current_img, f"current_map.{self.ext}")
+
+ async def _send_maybe_zoomed_map(self, ctx, map_path, filename):
+ zoom_data = {"enabled": False}
+
+ zoom_json_path = self.data_path / self.current_map / "settings.json"
+
+ if zoom_json_path.exists():
+ with zoom_json_path.open() as zoom_json:
+ zoom_data = json.load(zoom_json)
+
+ if zoom_data["enabled"]:
+ map_path = await self._create_zoomed_map(map_path, **zoom_data)
+
+ await ctx.send(file=discord.File(fp=map_path, filename=filename))
+
+ @conquest.command("blank")
+ async def _conquest_blank(self, ctx: commands.Context):
+ """
+ Print the blank version of the current map, for reference.
+ """
+ if self.current_map is None:
+ await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
+ return
+
+ current_blank_img = self.asset_path / self.current_map / f"blank.{self.ext}"
+
+ await self._send_maybe_zoomed_map(ctx, current_blank_img, f"blank_map.{self.ext}")
+
+ @conquest.command("numbered")
+ async def _conquest_numbered(self, ctx: commands.Context):
+ """
+ Print the numbered version of the current map, for reference.
+ """
+ if self.current_map is None:
+ await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
+ return
+
+ numbers_path = self.asset_path / self.current_map / f"numbers.{self.ext}"
+ if not numbers_path.exists():
+ await ctx.send(
+ file=discord.File(
+ fp=self.asset_path / self.current_map / f"numbered.{self.ext}",
+ filename=f"numbered.{self.ext}",
+ )
+ )
+ return
+
+ current_map = Image.open(self.data_path / self.current_map / f"current.{self.ext}")
+ numbers = Image.open(numbers_path).convert("L")
+
+ inverted_map = ImageOps.invert(current_map)
+
+ loop = asyncio.get_running_loop()
+ current_numbered_img = await loop.run_in_executor(
+ None, Image.composite, current_map, inverted_map, numbers
+ )
+
+ current_numbered_img.save(
+ self.data_path / self.current_map / f"current_numbered.{self.ext}", self.ext_format
+ )
+
+ await self._send_maybe_zoomed_map(
+ ctx,
+ self.data_path / self.current_map / f"current_numbered.{self.ext}",
+ f"current_numbered.{self.ext}",
+ )
+
+ @conquest.command(name="multitake")
+ async def _conquest_multitake(
+ self, ctx: commands.Context, start_region: int, end_region: int, color: str
+ ):
+ if self.current_map is None:
+ await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
+ return
+
+ try:
+ color = ImageColor.getrgb(color)
+ except ValueError:
+ await ctx.maybe_send_embed(f"Invalid color {color}")
+ return
+
+ if end_region > self.map_data["region_max"] or start_region < 1:
+ await ctx.maybe_send_embed(
+ f"Max region number is {self.map_data['region_max']}, minimum is 1"
+ )
+ return
+ regions = [r for r in range(start_region, end_region + 1)]
+
+ await self._process_take_regions(color, ctx, regions)
+
+ async def _process_take_regions(self, color, ctx, regions):
+ current_img_path = self.data_path / self.current_map / f"current.{self.ext}"
+ im = Image.open(current_img_path)
+ async with ctx.typing():
+ out: Image.Image = await self._composite_regions(im, regions, color)
+ out.save(current_img_path, self.ext_format)
+ await self._send_maybe_zoomed_map(ctx, current_img_path, f"map.{self.ext}")
+
+ @conquest.command(name="take")
+ async def _conquest_take(self, ctx: commands.Context, regions: Greedy[int], *, color: str):
+ """
+ Claim a territory or list of territories for a specified color
+
+ :param regions: List of integer regions
+ :param color: Color to claim regions
+ """
+ if not regions:
+ await ctx.send_help()
+ return
+
+ if self.current_map is None:
+ await ctx.maybe_send_embed("No map is currently set. See `[p]conquest set map`")
+ return
+
+ try:
+ color = ImageColor.getrgb(color)
+ except ValueError:
+ await ctx.maybe_send_embed(f"Invalid color {color}")
+ return
+
+ for region in regions:
+ if region > self.map_data["region_max"] or region < 1:
+ await ctx.maybe_send_embed(
+ f"Max region number is {self.map_data['region_max']}, minimum is 1"
+ )
+ return
+
+ await self._process_take_regions(color, ctx, regions)
+
+ async def _composite_regions(self, im, regions, color) -> Image.Image:
+ im2 = Image.new("RGB", im.size, color)
+
+ loop = asyncio.get_running_loop()
+
+ combined_mask = None
+ for region in regions:
+ mask = Image.open(
+ self.asset_path / self.current_map / "masks" / f"{region}.{self.ext}"
+ ).convert("L")
+ if combined_mask is None:
+ combined_mask = mask
+ else:
+ # combined_mask = ImageChops.logical_or(combined_mask, mask)
+ combined_mask = await loop.run_in_executor(
+ None, ImageChops.multiply, combined_mask, mask
+ )
+
+ out = await loop.run_in_executor(None, Image.composite, im, im2, combined_mask)
+
+ return out
diff --git a/conquest/data/assets/HoI/blank.png b/conquest/data/assets/HoI/blank.png
new file mode 100644
index 0000000..7cf4e82
Binary files /dev/null and b/conquest/data/assets/HoI/blank.png differ
diff --git a/conquest/data/assets/HoI/data.json b/conquest/data/assets/HoI/data.json
new file mode 100644
index 0000000..9baa7fa
--- /dev/null
+++ b/conquest/data/assets/HoI/data.json
@@ -0,0 +1,3 @@
+{
+ "region_max": 70
+}
\ No newline at end of file
diff --git a/conquest/data/assets/HoI/numbered.jpg b/conquest/data/assets/HoI/numbered.jpg
new file mode 100644
index 0000000..228e9bb
Binary files /dev/null and b/conquest/data/assets/HoI/numbered.jpg differ
diff --git a/conquest/data/assets/ck2/blank.png b/conquest/data/assets/ck2/blank.png
new file mode 100644
index 0000000..058abe7
Binary files /dev/null and b/conquest/data/assets/ck2/blank.png differ
diff --git a/conquest/data/assets/ck2/data.json b/conquest/data/assets/ck2/data.json
new file mode 100644
index 0000000..9baa7fa
--- /dev/null
+++ b/conquest/data/assets/ck2/data.json
@@ -0,0 +1,3 @@
+{
+ "region_max": 70
+}
\ No newline at end of file
diff --git a/conquest/data/assets/ck2/numbered.png b/conquest/data/assets/ck2/numbered.png
new file mode 100644
index 0000000..91a4c76
Binary files /dev/null and b/conquest/data/assets/ck2/numbered.png differ
diff --git a/conquest/data/assets/maps.json b/conquest/data/assets/maps.json
new file mode 100644
index 0000000..a7d1c03
--- /dev/null
+++ b/conquest/data/assets/maps.json
@@ -0,0 +1,7 @@
+{
+ "maps": [
+ "simple_blank_map",
+ "test",
+ "test2"
+ ]
+}
\ No newline at end of file
diff --git a/conquest/data/assets/simple/blank.jpg b/conquest/data/assets/simple/blank.jpg
new file mode 100644
index 0000000..f8f4f07
Binary files /dev/null and b/conquest/data/assets/simple/blank.jpg differ
diff --git a/conquest/data/assets/simple/data.json b/conquest/data/assets/simple/data.json
new file mode 100644
index 0000000..56a4c69
--- /dev/null
+++ b/conquest/data/assets/simple/data.json
@@ -0,0 +1,4 @@
+{
+ "region_max": 70,
+ "extension": "jpg"
+}
\ No newline at end of file
diff --git a/conquest/data/assets/simple/masks/1.jpg b/conquest/data/assets/simple/masks/1.jpg
new file mode 100644
index 0000000..084384b
Binary files /dev/null and b/conquest/data/assets/simple/masks/1.jpg differ
diff --git a/conquest/data/assets/simple/masks/10.jpg b/conquest/data/assets/simple/masks/10.jpg
new file mode 100644
index 0000000..078f526
Binary files /dev/null and b/conquest/data/assets/simple/masks/10.jpg differ
diff --git a/conquest/data/assets/simple/masks/11.jpg b/conquest/data/assets/simple/masks/11.jpg
new file mode 100644
index 0000000..7d8092e
Binary files /dev/null and b/conquest/data/assets/simple/masks/11.jpg differ
diff --git a/conquest/data/assets/simple/masks/12.jpg b/conquest/data/assets/simple/masks/12.jpg
new file mode 100644
index 0000000..89ef52a
Binary files /dev/null and b/conquest/data/assets/simple/masks/12.jpg differ
diff --git a/conquest/data/assets/simple/masks/13.jpg b/conquest/data/assets/simple/masks/13.jpg
new file mode 100644
index 0000000..d5bcf44
Binary files /dev/null and b/conquest/data/assets/simple/masks/13.jpg differ
diff --git a/conquest/data/assets/simple/masks/14.jpg b/conquest/data/assets/simple/masks/14.jpg
new file mode 100644
index 0000000..86a7c01
Binary files /dev/null and b/conquest/data/assets/simple/masks/14.jpg differ
diff --git a/conquest/data/assets/simple/masks/15.jpg b/conquest/data/assets/simple/masks/15.jpg
new file mode 100644
index 0000000..b18a720
Binary files /dev/null and b/conquest/data/assets/simple/masks/15.jpg differ
diff --git a/conquest/data/assets/simple/masks/16.jpg b/conquest/data/assets/simple/masks/16.jpg
new file mode 100644
index 0000000..1de9ab3
Binary files /dev/null and b/conquest/data/assets/simple/masks/16.jpg differ
diff --git a/conquest/data/assets/simple/masks/17.jpg b/conquest/data/assets/simple/masks/17.jpg
new file mode 100644
index 0000000..6f428bd
Binary files /dev/null and b/conquest/data/assets/simple/masks/17.jpg differ
diff --git a/conquest/data/assets/simple/masks/18.jpg b/conquest/data/assets/simple/masks/18.jpg
new file mode 100644
index 0000000..c3f13cc
Binary files /dev/null and b/conquest/data/assets/simple/masks/18.jpg differ
diff --git a/conquest/data/assets/simple/masks/19.jpg b/conquest/data/assets/simple/masks/19.jpg
new file mode 100644
index 0000000..bf9ec38
Binary files /dev/null and b/conquest/data/assets/simple/masks/19.jpg differ
diff --git a/conquest/data/assets/simple/masks/2.jpg b/conquest/data/assets/simple/masks/2.jpg
new file mode 100644
index 0000000..6edd658
Binary files /dev/null and b/conquest/data/assets/simple/masks/2.jpg differ
diff --git a/conquest/data/assets/simple/masks/20.jpg b/conquest/data/assets/simple/masks/20.jpg
new file mode 100644
index 0000000..39fac15
Binary files /dev/null and b/conquest/data/assets/simple/masks/20.jpg differ
diff --git a/conquest/data/assets/simple/masks/21.jpg b/conquest/data/assets/simple/masks/21.jpg
new file mode 100644
index 0000000..a6f2d88
Binary files /dev/null and b/conquest/data/assets/simple/masks/21.jpg differ
diff --git a/conquest/data/assets/simple/masks/22.jpg b/conquest/data/assets/simple/masks/22.jpg
new file mode 100644
index 0000000..a4c8057
Binary files /dev/null and b/conquest/data/assets/simple/masks/22.jpg differ
diff --git a/conquest/data/assets/simple/masks/23.jpg b/conquest/data/assets/simple/masks/23.jpg
new file mode 100644
index 0000000..3229d3e
Binary files /dev/null and b/conquest/data/assets/simple/masks/23.jpg differ
diff --git a/conquest/data/assets/simple/masks/24.jpg b/conquest/data/assets/simple/masks/24.jpg
new file mode 100644
index 0000000..413bf4a
Binary files /dev/null and b/conquest/data/assets/simple/masks/24.jpg differ
diff --git a/conquest/data/assets/simple/masks/25.jpg b/conquest/data/assets/simple/masks/25.jpg
new file mode 100644
index 0000000..c040946
Binary files /dev/null and b/conquest/data/assets/simple/masks/25.jpg differ
diff --git a/conquest/data/assets/simple/masks/26.jpg b/conquest/data/assets/simple/masks/26.jpg
new file mode 100644
index 0000000..232b004
Binary files /dev/null and b/conquest/data/assets/simple/masks/26.jpg differ
diff --git a/conquest/data/assets/simple/masks/27.jpg b/conquest/data/assets/simple/masks/27.jpg
new file mode 100644
index 0000000..c8229a3
Binary files /dev/null and b/conquest/data/assets/simple/masks/27.jpg differ
diff --git a/conquest/data/assets/simple/masks/28.jpg b/conquest/data/assets/simple/masks/28.jpg
new file mode 100644
index 0000000..d5484a1
Binary files /dev/null and b/conquest/data/assets/simple/masks/28.jpg differ
diff --git a/conquest/data/assets/simple/masks/29.jpg b/conquest/data/assets/simple/masks/29.jpg
new file mode 100644
index 0000000..0f138f6
Binary files /dev/null and b/conquest/data/assets/simple/masks/29.jpg differ
diff --git a/conquest/data/assets/simple/masks/3.jpg b/conquest/data/assets/simple/masks/3.jpg
new file mode 100644
index 0000000..112d1b0
Binary files /dev/null and b/conquest/data/assets/simple/masks/3.jpg differ
diff --git a/conquest/data/assets/simple/masks/30.jpg b/conquest/data/assets/simple/masks/30.jpg
new file mode 100644
index 0000000..b04de09
Binary files /dev/null and b/conquest/data/assets/simple/masks/30.jpg differ
diff --git a/conquest/data/assets/simple/masks/31.jpg b/conquest/data/assets/simple/masks/31.jpg
new file mode 100644
index 0000000..90812e7
Binary files /dev/null and b/conquest/data/assets/simple/masks/31.jpg differ
diff --git a/conquest/data/assets/simple/masks/32.jpg b/conquest/data/assets/simple/masks/32.jpg
new file mode 100644
index 0000000..fd12574
Binary files /dev/null and b/conquest/data/assets/simple/masks/32.jpg differ
diff --git a/conquest/data/assets/simple/masks/33.jpg b/conquest/data/assets/simple/masks/33.jpg
new file mode 100644
index 0000000..87704c5
Binary files /dev/null and b/conquest/data/assets/simple/masks/33.jpg differ
diff --git a/conquest/data/assets/simple/masks/34.jpg b/conquest/data/assets/simple/masks/34.jpg
new file mode 100644
index 0000000..1c610df
Binary files /dev/null and b/conquest/data/assets/simple/masks/34.jpg differ
diff --git a/conquest/data/assets/simple/masks/35.jpg b/conquest/data/assets/simple/masks/35.jpg
new file mode 100644
index 0000000..e86c726
Binary files /dev/null and b/conquest/data/assets/simple/masks/35.jpg differ
diff --git a/conquest/data/assets/simple/masks/36.jpg b/conquest/data/assets/simple/masks/36.jpg
new file mode 100644
index 0000000..7f4ad3d
Binary files /dev/null and b/conquest/data/assets/simple/masks/36.jpg differ
diff --git a/conquest/data/assets/simple/masks/37.jpg b/conquest/data/assets/simple/masks/37.jpg
new file mode 100644
index 0000000..c8b6b87
Binary files /dev/null and b/conquest/data/assets/simple/masks/37.jpg differ
diff --git a/conquest/data/assets/simple/masks/38.jpg b/conquest/data/assets/simple/masks/38.jpg
new file mode 100644
index 0000000..6fcd5a6
Binary files /dev/null and b/conquest/data/assets/simple/masks/38.jpg differ
diff --git a/conquest/data/assets/simple/masks/39.jpg b/conquest/data/assets/simple/masks/39.jpg
new file mode 100644
index 0000000..455f0e5
Binary files /dev/null and b/conquest/data/assets/simple/masks/39.jpg differ
diff --git a/conquest/data/assets/simple/masks/4.jpg b/conquest/data/assets/simple/masks/4.jpg
new file mode 100644
index 0000000..b77cc24
Binary files /dev/null and b/conquest/data/assets/simple/masks/4.jpg differ
diff --git a/conquest/data/assets/simple/masks/40.jpg b/conquest/data/assets/simple/masks/40.jpg
new file mode 100644
index 0000000..5692661
Binary files /dev/null and b/conquest/data/assets/simple/masks/40.jpg differ
diff --git a/conquest/data/assets/simple/masks/41.jpg b/conquest/data/assets/simple/masks/41.jpg
new file mode 100644
index 0000000..f0e3972
Binary files /dev/null and b/conquest/data/assets/simple/masks/41.jpg differ
diff --git a/conquest/data/assets/simple/masks/42.jpg b/conquest/data/assets/simple/masks/42.jpg
new file mode 100644
index 0000000..b41a652
Binary files /dev/null and b/conquest/data/assets/simple/masks/42.jpg differ
diff --git a/conquest/data/assets/simple/masks/43.jpg b/conquest/data/assets/simple/masks/43.jpg
new file mode 100644
index 0000000..463c17f
Binary files /dev/null and b/conquest/data/assets/simple/masks/43.jpg differ
diff --git a/conquest/data/assets/simple/masks/44.jpg b/conquest/data/assets/simple/masks/44.jpg
new file mode 100644
index 0000000..b2da82b
Binary files /dev/null and b/conquest/data/assets/simple/masks/44.jpg differ
diff --git a/conquest/data/assets/simple/masks/45.jpg b/conquest/data/assets/simple/masks/45.jpg
new file mode 100644
index 0000000..fdeca1c
Binary files /dev/null and b/conquest/data/assets/simple/masks/45.jpg differ
diff --git a/conquest/data/assets/simple/masks/46.jpg b/conquest/data/assets/simple/masks/46.jpg
new file mode 100644
index 0000000..6da7ec6
Binary files /dev/null and b/conquest/data/assets/simple/masks/46.jpg differ
diff --git a/conquest/data/assets/simple/masks/47.jpg b/conquest/data/assets/simple/masks/47.jpg
new file mode 100644
index 0000000..4e7a2a4
Binary files /dev/null and b/conquest/data/assets/simple/masks/47.jpg differ
diff --git a/conquest/data/assets/simple/masks/48.jpg b/conquest/data/assets/simple/masks/48.jpg
new file mode 100644
index 0000000..87223f7
Binary files /dev/null and b/conquest/data/assets/simple/masks/48.jpg differ
diff --git a/conquest/data/assets/simple/masks/49.jpg b/conquest/data/assets/simple/masks/49.jpg
new file mode 100644
index 0000000..d749cae
Binary files /dev/null and b/conquest/data/assets/simple/masks/49.jpg differ
diff --git a/conquest/data/assets/simple/masks/5.jpg b/conquest/data/assets/simple/masks/5.jpg
new file mode 100644
index 0000000..c392916
Binary files /dev/null and b/conquest/data/assets/simple/masks/5.jpg differ
diff --git a/conquest/data/assets/simple/masks/50.jpg b/conquest/data/assets/simple/masks/50.jpg
new file mode 100644
index 0000000..514a67f
Binary files /dev/null and b/conquest/data/assets/simple/masks/50.jpg differ
diff --git a/conquest/data/assets/simple/masks/51.jpg b/conquest/data/assets/simple/masks/51.jpg
new file mode 100644
index 0000000..c928321
Binary files /dev/null and b/conquest/data/assets/simple/masks/51.jpg differ
diff --git a/conquest/data/assets/simple/masks/52.jpg b/conquest/data/assets/simple/masks/52.jpg
new file mode 100644
index 0000000..9321238
Binary files /dev/null and b/conquest/data/assets/simple/masks/52.jpg differ
diff --git a/conquest/data/assets/simple/masks/53.jpg b/conquest/data/assets/simple/masks/53.jpg
new file mode 100644
index 0000000..4b36e75
Binary files /dev/null and b/conquest/data/assets/simple/masks/53.jpg differ
diff --git a/conquest/data/assets/simple/masks/54.jpg b/conquest/data/assets/simple/masks/54.jpg
new file mode 100644
index 0000000..b592522
Binary files /dev/null and b/conquest/data/assets/simple/masks/54.jpg differ
diff --git a/conquest/data/assets/simple/masks/55.jpg b/conquest/data/assets/simple/masks/55.jpg
new file mode 100644
index 0000000..5bed3a8
Binary files /dev/null and b/conquest/data/assets/simple/masks/55.jpg differ
diff --git a/conquest/data/assets/simple/masks/56.jpg b/conquest/data/assets/simple/masks/56.jpg
new file mode 100644
index 0000000..627473c
Binary files /dev/null and b/conquest/data/assets/simple/masks/56.jpg differ
diff --git a/conquest/data/assets/simple/masks/57.jpg b/conquest/data/assets/simple/masks/57.jpg
new file mode 100644
index 0000000..6e0e1e4
Binary files /dev/null and b/conquest/data/assets/simple/masks/57.jpg differ
diff --git a/conquest/data/assets/simple/masks/58.jpg b/conquest/data/assets/simple/masks/58.jpg
new file mode 100644
index 0000000..47e98fe
Binary files /dev/null and b/conquest/data/assets/simple/masks/58.jpg differ
diff --git a/conquest/data/assets/simple/masks/59.jpg b/conquest/data/assets/simple/masks/59.jpg
new file mode 100644
index 0000000..f23e469
Binary files /dev/null and b/conquest/data/assets/simple/masks/59.jpg differ
diff --git a/conquest/data/assets/simple/masks/6.jpg b/conquest/data/assets/simple/masks/6.jpg
new file mode 100644
index 0000000..95eb45f
Binary files /dev/null and b/conquest/data/assets/simple/masks/6.jpg differ
diff --git a/conquest/data/assets/simple/masks/60.jpg b/conquest/data/assets/simple/masks/60.jpg
new file mode 100644
index 0000000..c1b149e
Binary files /dev/null and b/conquest/data/assets/simple/masks/60.jpg differ
diff --git a/conquest/data/assets/simple/masks/61.jpg b/conquest/data/assets/simple/masks/61.jpg
new file mode 100644
index 0000000..e343d5d
Binary files /dev/null and b/conquest/data/assets/simple/masks/61.jpg differ
diff --git a/conquest/data/assets/simple/masks/62.jpg b/conquest/data/assets/simple/masks/62.jpg
new file mode 100644
index 0000000..413a5ad
Binary files /dev/null and b/conquest/data/assets/simple/masks/62.jpg differ
diff --git a/conquest/data/assets/simple/masks/63.jpg b/conquest/data/assets/simple/masks/63.jpg
new file mode 100644
index 0000000..f03435f
Binary files /dev/null and b/conquest/data/assets/simple/masks/63.jpg differ
diff --git a/conquest/data/assets/simple/masks/64.jpg b/conquest/data/assets/simple/masks/64.jpg
new file mode 100644
index 0000000..59e80fe
Binary files /dev/null and b/conquest/data/assets/simple/masks/64.jpg differ
diff --git a/conquest/data/assets/simple/masks/65.jpg b/conquest/data/assets/simple/masks/65.jpg
new file mode 100644
index 0000000..fd2cb0d
Binary files /dev/null and b/conquest/data/assets/simple/masks/65.jpg differ
diff --git a/conquest/data/assets/simple/masks/66.jpg b/conquest/data/assets/simple/masks/66.jpg
new file mode 100644
index 0000000..2ac9dda
Binary files /dev/null and b/conquest/data/assets/simple/masks/66.jpg differ
diff --git a/conquest/data/assets/simple/masks/67.jpg b/conquest/data/assets/simple/masks/67.jpg
new file mode 100644
index 0000000..e2ebdc9
Binary files /dev/null and b/conquest/data/assets/simple/masks/67.jpg differ
diff --git a/conquest/data/assets/simple/masks/68.jpg b/conquest/data/assets/simple/masks/68.jpg
new file mode 100644
index 0000000..535e506
Binary files /dev/null and b/conquest/data/assets/simple/masks/68.jpg differ
diff --git a/conquest/data/assets/simple/masks/69.jpg b/conquest/data/assets/simple/masks/69.jpg
new file mode 100644
index 0000000..6b1d010
Binary files /dev/null and b/conquest/data/assets/simple/masks/69.jpg differ
diff --git a/conquest/data/assets/simple/masks/7.jpg b/conquest/data/assets/simple/masks/7.jpg
new file mode 100644
index 0000000..a0202b0
Binary files /dev/null and b/conquest/data/assets/simple/masks/7.jpg differ
diff --git a/conquest/data/assets/simple/masks/70.jpg b/conquest/data/assets/simple/masks/70.jpg
new file mode 100644
index 0000000..a1716af
Binary files /dev/null and b/conquest/data/assets/simple/masks/70.jpg differ
diff --git a/conquest/data/assets/simple/masks/8.jpg b/conquest/data/assets/simple/masks/8.jpg
new file mode 100644
index 0000000..647c649
Binary files /dev/null and b/conquest/data/assets/simple/masks/8.jpg differ
diff --git a/conquest/data/assets/simple/masks/9.jpg b/conquest/data/assets/simple/masks/9.jpg
new file mode 100644
index 0000000..3bafeda
Binary files /dev/null and b/conquest/data/assets/simple/masks/9.jpg differ
diff --git a/conquest/data/assets/simple/numbered.jpg b/conquest/data/assets/simple/numbered.jpg
new file mode 100644
index 0000000..228e9bb
Binary files /dev/null and b/conquest/data/assets/simple/numbered.jpg differ
diff --git a/conquest/data/assets/simple/numbers.jpg b/conquest/data/assets/simple/numbers.jpg
new file mode 100644
index 0000000..de87fd7
Binary files /dev/null and b/conquest/data/assets/simple/numbers.jpg differ
diff --git a/conquest/data/assets/simple/numbers.png b/conquest/data/assets/simple/numbers.png
new file mode 100644
index 0000000..2f72371
Binary files /dev/null and b/conquest/data/assets/simple/numbers.png differ
diff --git a/conquest/info.json b/conquest/info.json
new file mode 100644
index 0000000..d92c394
--- /dev/null
+++ b/conquest/info.json
@@ -0,0 +1,24 @@
+{
+ "author": [
+ "Bobloy"
+ ],
+ "min_bot_version": "3.4.0",
+ "description": "Handle war games by filling in specified territories with colors",
+ "hidden": false,
+ "install_msg": "Thank you for installing Conquest. Get started with `[p]load conquest`, then `[p]help Conquest`",
+ "short": "War Game Map",
+ "requirements": [
+ "Pillow"
+ ],
+ "tags": [
+ "bobloy",
+ "games",
+ "game",
+ "war",
+ "map",
+ "axisandallies",
+ "heartsofiron",
+ "conquest",
+ "rpg"
+ ]
+}
diff --git a/conquest/mapmaker.py b/conquest/mapmaker.py
new file mode 100644
index 0000000..0cde96a
--- /dev/null
+++ b/conquest/mapmaker.py
@@ -0,0 +1,50 @@
+import discord
+from redbot.core import Config, commands
+from redbot.core.bot import Red
+
+
+class MapMaker(commands.Cog):
+ """
+ Create Maps to be used with Conquest
+ """
+
+ def __init__(self, bot: Red):
+ super().__init__()
+ self.bot = bot
+
+ self.config = Config.get_conf(
+ self, identifier=77971127797107101114, force_registration=True
+ )
+
+ default_guild = {}
+ default_global = {}
+ self.config.register_guild(**default_guild)
+ self.config.register_global(**default_global)
+
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
+ @commands.group()
+ async def mapmaker(self, ctx: commands.context):
+ """
+ Base command for managing current maps or creating new ones
+ """
+ if ctx.invoked_subcommand is None:
+ pass
+
+ @mapmaker.command(name="upload")
+ async def _mapmaker_upload(self, ctx: commands.Context, map_path=""):
+ """Load a map image to be modified. Upload one with this command or provide a path"""
+ message: discord.Message = ctx.message
+ if not message.attachments and not map_path:
+ await ctx.maybe_send_embed(
+ "Either upload an image with this command or provide a path to the image"
+ )
+ return
+ await ctx.maybe_send_embed("WIP")
+
+ @mapmaker.command(name="load")
+ async def _mapmaker_load(self, ctx: commands.Context, map_name=""):
+ """Load an existing map to be modified."""
+ await ctx.maybe_send_embed("WIP")
diff --git a/conquest/regioner.py b/conquest/regioner.py
new file mode 100644
index 0000000..dc77373
--- /dev/null
+++ b/conquest/regioner.py
@@ -0,0 +1,132 @@
+import os
+import pathlib
+from PIL import Image, ImageColor, ImageFont, ImageOps, ImageDraw
+from PIL.ImageDraw import _color_diff
+
+
+def get_center(points):
+ """
+ Taken from https://stackoverflow.com/questions/4355894/how-to-get-center-of-set-of-points-using-python
+ """
+ x = [p[0] for p in points]
+ y = [p[1] for p in points]
+ return sum(x) / len(points), sum(y) / len(points)
+
+
+def floodfill(image, xy, value, border=None, thresh=0) -> set:
+ """
+ Taken and modified from PIL.ImageDraw.floodfill
+
+ (experimental) Fills a bounded region with a given color.
+
+ :param image: Target image.
+ :param xy: Seed position (a 2-item coordinate tuple). See
+ :ref:`coordinate-system`.
+ :param value: Fill color.
+ :param border: Optional border value. If given, the region consists of
+ pixels with a color different from the border color. If not given,
+ the region consists of pixels having the same color as the seed
+ pixel.
+ :param thresh: Optional threshold value which specifies a maximum
+ tolerable difference of a pixel value from the 'background' in
+ order for it to be replaced. Useful for filling regions of
+ non-homogeneous, but similar, colors.
+ """
+ # based on an implementation by Eric S. Raymond
+ # amended by yo1995 @20180806
+ pixel = image.load()
+ x, y = xy
+ try:
+ background = pixel[x, y]
+ if _color_diff(value, background) <= thresh:
+ return set() # seed point already has fill color
+ pixel[x, y] = value
+ except (ValueError, IndexError):
+ return set() # seed point outside image
+ edge = {(x, y)}
+ # use a set to keep record of current and previous edge pixels
+ # to reduce memory consumption
+ filled_pixels = set()
+ full_edge = set()
+ while edge:
+ filled_pixels.update(edge)
+ new_edge = set()
+ for (x, y) in edge: # 4 adjacent method
+ for (s, t) in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
+ # If already processed, or if a coordinate is negative, skip
+ if (s, t) in full_edge or s < 0 or t < 0:
+ continue
+ try:
+ p = pixel[s, t]
+ except (ValueError, IndexError):
+ pass
+ else:
+ full_edge.add((s, t))
+ if border is None:
+ fill = _color_diff(p, background) <= thresh
+ else:
+ fill = p != value and p != border
+ if fill:
+ pixel[s, t] = value
+ new_edge.add((s, t))
+ full_edge = edge # discard pixels processed
+ edge = new_edge
+ return filled_pixels
+
+
+class Regioner:
+ def __init__(
+ self, filepath: pathlib.Path, filename: str, wall_color="black", region_color="white"
+ ):
+ self.filepath = filepath
+ self.filename = filename
+ self.wall_color = ImageColor.getcolor(wall_color, "L")
+ self.region_color = ImageColor.getcolor(region_color, "L")
+
+ def execute(self):
+ base_img_path = self.filepath / self.filename
+ if not base_img_path.exists():
+ return None
+
+ masks_path = self.filepath / "masks"
+
+ if not masks_path.exists():
+ os.makedirs(masks_path)
+
+ black = ImageColor.getcolor("black", "L")
+ white = ImageColor.getcolor("white", "L")
+
+ base_img: Image.Image = Image.open(base_img_path).convert("L")
+ already_processed = set()
+
+ mask_count = 0
+ mask_centers = {}
+
+ for y1 in range(base_img.height):
+ for x1 in range(base_img.width):
+ if (x1, y1) in already_processed:
+ continue
+ if base_img.getpixel((x1, y1)) == self.region_color:
+ filled = floodfill(base_img, (x1, y1), black, self.wall_color)
+ if filled: # Pixels were updated, make them into a mask
+ mask = Image.new("L", base_img.size, 255)
+ for x2, y2 in filled:
+ mask.putpixel((x2, y2), 0)
+
+ mask_count += 1
+ mask = mask.convert("L")
+ mask.save(masks_path / f"{mask_count}.png", "PNG")
+
+ mask_centers[mask_count] = get_center(filled)
+
+ already_processed.update(filled)
+
+ number_img = Image.new("L", base_img.size, 255)
+ fnt = ImageFont.load_default()
+ d = ImageDraw.Draw(number_img)
+ for mask_num, center in mask_centers.items():
+ d.text(center, str(mask_num), font=fnt, fill=0)
+
+ number_img.save(self.filepath / f"numbers.png", "PNG")
+
+ return mask_centers
diff --git a/dad/dad.py b/dad/dad.py
index 3cc9a05..de4361e 100644
--- a/dad/dad.py
+++ b/dad/dad.py
@@ -32,6 +32,10 @@ class Dad(Cog):
self.cooldown = defaultdict(datetime.now)
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.command()
async def dadjoke(self, ctx: commands.Context):
headers = {
@@ -42,10 +46,14 @@ class Dad(Cog):
async with aiohttp.ClientSession(headers=headers) as session:
joke = await fetch_url(session, "https://icanhazdadjoke.com/")
- em = discord.Embed()
- em.set_image(url="https://icanhazdadjoke.com/j/{}.png".format(joke["id"]))
+ await ctx.maybe_send_embed(joke["joke"])
- await ctx.send(embed=em)
+ # print(joke)
+ #
+ # em = discord.Embed()
+ # em.set_image(url="https://icanhazdadjoke.com/j/{}.png".format(joke["id"]))
+ #
+ # await ctx.send(embed=em)
@commands.group()
@checks.admin()
@@ -69,14 +77,15 @@ class Dad(Cog):
@dad.command(name="cooldown")
async def dad_cooldown(self, ctx: commands.Context, cooldown: int):
- """Set the auto-joke cooldown"""
+ """Set the auto-joke cooldown in seconds"""
await self.config.guild(ctx.guild).cooldown.set(cooldown)
- await ctx.send("Dad joke cooldown is now set to {}".format(cooldown))
+ self.cooldown[ctx.guild.id] = datetime.now()
+ await ctx.send("Dad joke cooldown is now set to {} seconds".format(cooldown))
@commands.Cog.listener()
- async def on_message(self, message: discord.Message):
- guild: discord.Guild = message.guild
+ async def on_message_without_command(self, message: discord.Message):
+ guild: discord.Guild = getattr(message, "guild", None)
if guild is None:
return
@@ -88,23 +97,23 @@ class Dad(Cog):
if self.cooldown[guild.id] > datetime.now():
return
- lower = message.clean_content.lower()
- lower_split = lower.split()
- if len(lower_split) == 0:
+ cleaned_content = message.clean_content
+ content_split = cleaned_content.split()
+ if len(content_split) == 0:
return
- if lower_split[0] == "i'm" and len(lower_split) >= 2:
+ if content_split[0].lower() == "i'm" and len(content_split) >= 2:
if await guild_config.nickname():
try:
- await message.author.edit(nick=lower[4:])
+ await message.author.edit(nick=cleaned_content[4:])
except discord.Forbidden:
- out = lower[4:]
+ out = cleaned_content[4:]
else:
out = message.author.mention
else:
- out = lower[4:]
+ out = cleaned_content[4:]
try:
- await message.channel.send("Hi {}, I'm {}!".format(out, guild.me.display_name))
+ await message.channel.send(f"Hi {out}, I'm {guild.me.display_name}!")
except discord.HTTPException:
return
diff --git a/dad/info.json b/dad/info.json
index 81af745..c4b2dd8 100644
--- a/dad/info.json
+++ b/dad/info.json
@@ -2,15 +2,12 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Tell dad jokes and give out bad nicknames",
- "hidden": true,
+ "hidden": false,
"install_msg": "Thank you for installing Dad. Get started with `[p]load dad`, then `[p]help Dad`",
"short": "Dad joke bot",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"utils",
diff --git a/exclusiverole/exclusiverole.py b/exclusiverole/exclusiverole.py
index ded1677..6dc4b84 100644
--- a/exclusiverole/exclusiverole.py
+++ b/exclusiverole/exclusiverole.py
@@ -18,6 +18,10 @@ class ExclusiveRole(Cog):
self.config.register_guild(**default_guild)
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.guild_only()
@commands.group(aliases=["exclusiverole"])
async def exclusive(self, ctx):
diff --git a/exclusiverole/info.json b/exclusiverole/info.json
index ade6bd0..426eabd 100644
--- a/exclusiverole/info.json
+++ b/exclusiverole/info.json
@@ -2,15 +2,12 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Assign roles to be exclusive, preventing other roles from being added",
"hidden": false,
"install_msg": "Thank you for installing ExclusiveRole. Get started with `[p]load exclusiverole` and `[p]help ExclusiveRole`",
"short": "Set roles to be exclusive",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"fox",
"bobloy",
diff --git a/flag/flag.py b/flag/flag.py
index 4419e29..f93de2f 100644
--- a/flag/flag.py
+++ b/flag/flag.py
@@ -1,9 +1,11 @@
from datetime import date, timedelta
+from typing import Literal
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 import AsyncIter
from redbot.core.utils.chat_formatting import pagify
@@ -22,6 +24,21 @@ class Flag(Cog):
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
+ async def red_delete_data_for_user(
+ self,
+ *,
+ requester: Literal["discord_deleted_user", "owner", "user", "user_strict"],
+ user_id: int,
+ ):
+ if requester not in ["discord_deleted_user", "owner"]:
+ return
+
+ all_guilds = await self.config.all_guilds()
+
+ async for guild_id, guild_data in AsyncIter(all_guilds.items(), steps=100):
+ if user_id in guild_data["flags"]:
+ await self.config.guild_from_id(guild_id).flags.clear_raw(user_id)
+
@checks.is_owner()
@commands.guild_only()
@commands.command()
diff --git a/flag/info.json b/flag/info.json
index 7ee8ad5..0d687a6 100644
--- a/flag/info.json
+++ b/flag/info.json
@@ -2,15 +2,12 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Add expiring flags on members to track warnings or incidents",
"hidden": false,
"install_msg": "Thank you for installing Flag! Get started with `[p]load flag` and `[p]help Flag`",
"short": "Add expiring flags to members",
+ "end_user_data_statement": "This cog stores user IDs listed along with a provided explanation for being flagged",
"tags": [
"bobloy",
"warning",
diff --git a/forcemention/forcemention.py b/forcemention/forcemention.py
index 8fb1380..2aeaac5 100644
--- a/forcemention/forcemention.py
+++ b/forcemention/forcemention.py
@@ -21,6 +21,10 @@ class ForceMention(Cog):
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@checks.admin_or_permissions(manage_roles=True)
@commands.command()
async def forcemention(self, ctx: commands.Context, role: str, *, message=""):
diff --git a/forcemention/info.json b/forcemention/info.json
index f7326b9..e99f611 100644
--- a/forcemention/info.json
+++ b/forcemention/info.json
@@ -2,15 +2,12 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Mentions roles that are unmentionable",
"hidden": false,
"install_msg": "Thank you for installing ForceMention! Get started with `[p]load forcemention`, then `[p]forcemention`",
"short": "Mention unmentionables",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"utils"
diff --git a/hangman/hangman.py b/hangman/hangman.py
index da6509f..338e8b7 100644
--- a/hangman/hangman.py
+++ b/hangman/hangman.py
@@ -14,6 +14,7 @@ class Hangman(Cog):
letters = "🇦🇧🇨🇩🇪🇫🇬🇭🇮🇯🇰🇱🇲🇳🇴🇵🇶🇷🇸🇹🇺🇻🇼🇽🇾🇿"
def __init__(self, bot):
+ super().__init__()
self.bot = bot
self.config = Config.get_conf(self, identifier=1049711010310997110)
default_guild = {"theface": ":thinking:", "emojis": True}
@@ -39,6 +40,10 @@ class Hangman(Cog):
self.hanglist = {}
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
async def _update_hanglist(self):
for guild in self.bot.guilds:
theface = await self.config.guild(guild).theface()
diff --git a/hangman/info.json b/hangman/info.json
index c9dadf0..b4db258 100644
--- a/hangman/info.json
+++ b/hangman/info.json
@@ -2,16 +2,13 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Play Hangman with your friends",
"hidden": false,
"install_msg": "Thank you for installing Hangman! Get started with `[p]load hangman`, then `[p]help Hangman`",
"requirements": [],
"short": "Play Hangman",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"game",
"fun",
diff --git a/info.json b/info.json
index 4d8e5e1..23cc49a 100644
--- a/info.json
+++ b/info.json
@@ -1,7 +1,7 @@
{
- "AUTHOR": "Bobloy",
- "INSTALL_MSG": "Thank you for installing Fox-V3 by Bobloy",
- "NAME": "Fox-V3",
- "SHORT": "Cogs by Bobloy",
- "DESCRIPTION": "Cogs for RED Discord Bot by Bobloy"
-}
\ No newline at end of file
+ "author": ["Bobloy"],
+ "install_msg": "Thank you for installing Fox-V3 by Bobloy",
+ "name": "Fox-V3",
+ "short": "Cogs by Bobloy",
+ "description": "Cogs for RED Discord Bot by Bobloy"
+}
diff --git a/infochannel/info.json b/infochannel/info.json
index 2a2efc5..b8fad4e 100644
--- a/infochannel/info.json
+++ b/infochannel/info.json
@@ -2,15 +2,12 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Create a channel with updating server info",
"hidden": false,
"install_msg": "Thank you for installing InfoChannel. Get started with `[p]load infochannel`, then `[p]help InfoChannel`",
"short": "Updating server info channel",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"utils"
diff --git a/infochannel/infochannel.py b/infochannel/infochannel.py
index b612830..eb393d0 100644
--- a/infochannel/infochannel.py
+++ b/infochannel/infochannel.py
@@ -41,6 +41,10 @@ class InfoChannel(Cog):
self._critical_section_wooah_ = 0
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.command()
@checks.admin()
async def infochannel(self, ctx: commands.Context):
diff --git a/leaver/info.json b/leaver/info.json
index f5b0a65..0c8d3d1 100644
--- a/leaver/info.json
+++ b/leaver/info.json
@@ -2,15 +2,12 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Keeps track of when people leave the server, and posts a message notifying",
"hidden": false,
"install_msg": "Thank you for installing Leaver. Get started with `[p]load leaver`, then `[p]help Leaver`",
"short": "Send message on leave",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"utils",
diff --git a/leaver/leaver.py b/leaver/leaver.py
index 1147684..9475384 100644
--- a/leaver/leaver.py
+++ b/leaver/leaver.py
@@ -10,12 +10,17 @@ class Leaver(Cog):
"""
def __init__(self, bot: Red):
+ super().__init__()
self.bot = bot
self.config = Config.get_conf(self, identifier=9811198108111121, force_registration=True)
default_guild = {"channel": ""}
self.config.register_guild(**default_guild)
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.group(aliases=["setleaver"])
@checks.mod_or_permissions(administrator=True)
async def leaverset(self, ctx):
diff --git a/lovecalculator/info.json b/lovecalculator/info.json
index 20601b6..543ff92 100644
--- a/lovecalculator/info.json
+++ b/lovecalculator/info.json
@@ -3,11 +3,7 @@
"Bobloy",
"SnappyDragon"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Calculate the love percentage for two users",
"hidden": false,
"install_msg": "Thank you for installing LoveCalculator. Love is in the air.\n Get started with `[p]load lovecalculator`, then `[p]help LoveCalculator`",
@@ -15,6 +11,7 @@
"beautifulsoup4"
],
"short": "Calculate love percentage",
+ "end_user_data_statement": "This cog uses the core Bank cog. It store no End User Data otherwise.",
"tags": [
"bobloy",
"fun",
diff --git a/lovecalculator/lovecalculator.py b/lovecalculator/lovecalculator.py
index 0bf85f3..ad57c5d 100644
--- a/lovecalculator/lovecalculator.py
+++ b/lovecalculator/lovecalculator.py
@@ -9,8 +9,13 @@ class LoveCalculator(Cog):
"""Calculate the love percentage for two users!"""
def __init__(self, bot):
+ super().__init__()
self.bot = bot
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.command(aliases=["lovecalc"])
async def lovecalculator(
self, ctx: commands.Context, lover: discord.Member, loved: discord.Member
diff --git a/lseen/info.json b/lseen/info.json
index c5e5eec..33ae133 100644
--- a/lseen/info.json
+++ b/lseen/info.json
@@ -2,16 +2,15 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Keep track of when users were last seen online",
"hidden": false,
"install_msg": "Thank you for installing LastSeen. Get started with `[p]load lseen`, then `[p]help LastSeen`",
- "requirements": ["python-dateutil"],
+ "requirements": [
+ "python-dateutil"
+ ],
"short": "Last seen tracker",
+ "end_user_data_statement": "This cog stores user IDs along with a time they were last online per guild",
"tags": [
"bobloy",
"utils",
diff --git a/lseen/lseen.py b/lseen/lseen.py
index e4461b5..abfef2d 100644
--- a/lseen/lseen.py
+++ b/lseen/lseen.py
@@ -1,10 +1,12 @@
from datetime import datetime
+from typing import Literal
import dateutil.parser
import discord
from redbot.core import Config, commands
from redbot.core.bot import Red
from redbot.core.commands import Cog
+from redbot.core.utils import AsyncIter
class LastSeen(Cog):
@@ -28,6 +30,19 @@ class LastSeen(Cog):
self.config.register_guild(**default_guild)
self.config.register_member(**default_member)
+ async def red_delete_data_for_user(
+ self,
+ *,
+ requester: Literal["discord_deleted_user", "owner", "user", "user_strict"],
+ user_id: int,
+ ):
+
+ all_members = await self.config.all_members()
+
+ async for guild_id, guild_data in AsyncIter(all_members.items(), steps=100):
+ if user_id in guild_data:
+ await self.config.member_from_ids(guild_id, user_id).clear()
+
@staticmethod
def get_date_time(s):
d = dateutil.parser.parse(s)
diff --git a/nudity/__init__.py b/nudity/__init__.py
new file mode 100644
index 0000000..09d9dbf
--- /dev/null
+++ b/nudity/__init__.py
@@ -0,0 +1,6 @@
+from .nudity import Nudity
+
+
+def setup(bot):
+ n = Nudity(bot)
+ bot.add_cog(n)
diff --git a/nudity/info..json b/nudity/info..json
new file mode 100644
index 0000000..4a2c1fb
--- /dev/null
+++ b/nudity/info..json
@@ -0,0 +1,22 @@
+{
+ "author": [
+ "Bobloy"
+ ],
+ "min_bot_version": "3.3.11",
+ "description": "Monitor images for NSFW content and moves them to a nsfw channel if possible",
+ "hidden": false,
+ "install_msg": "Thank you for installing Nudity. Get started with `[p]load nudity`, then `[p]help Nudity`",
+ "requirements": [
+ "nudenet",
+ "tensorflow>=1.14,<2.0",
+ "keras>=2.4"
+ ],
+ "short": "NSFW image tracker and mover",
+ "tags": [
+ "bobloy",
+ "utils",
+ "tools",
+ "nude",
+ "nsfw"
+ ]
+}
diff --git a/nudity/nudity.py b/nudity/nudity.py
new file mode 100644
index 0000000..6eb4221
--- /dev/null
+++ b/nudity/nudity.py
@@ -0,0 +1,147 @@
+import pathlib
+
+import discord
+from nudenet import NudeClassifier
+from redbot.core import Config, commands
+from redbot.core.bot import Red
+from redbot.core.data_manager import cog_data_path
+
+
+class Nudity(commands.Cog):
+ """
+ V3 Cog Template
+ """
+
+ def __init__(self, bot: Red):
+ super().__init__()
+ self.bot = bot
+ self.config = Config.get_conf(self, identifier=9811198108111121, force_registration=True)
+
+ default_guild = {"enabled": False, "channel_id": None}
+
+ self.config.register_guild(**default_guild)
+
+ # self.detector = NudeDetector()
+ self.classifier = NudeClassifier()
+
+ self.data_path: pathlib.Path = cog_data_path(self)
+
+ self.current_processes = 0
+
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
+ @commands.command(aliases=["togglenudity"], name="nudity")
+ async def nudity(self, ctx: commands.Context):
+ """Toggle nude-checking on or off"""
+ is_on = await self.config.guild(ctx.guild).enabled()
+ await self.config.guild(ctx.guild).enabled.set(not is_on)
+ await ctx.send("Nude checking is now set to {}".format(not is_on))
+
+ @commands.command()
+ async def nsfwchannel(self, ctx: commands.Context, channel: discord.TextChannel = None):
+ if channel is None:
+ await self.config.guild(ctx.guild).channel_id.set(None)
+ await ctx.send("NSFW Channel cleared")
+ else:
+ if not channel.is_nsfw():
+ await ctx.send("This channel isn't NSFW!")
+ return
+ else:
+ await self.config.guild(ctx.guild).channel_id.set(channel.id)
+ await ctx.send("NSFW channel has been set to {}".format(channel.mention))
+
+ async def get_nsfw_channel(self, guild: discord.Guild):
+ channel_id = await self.config.guild(guild).channel_id()
+
+ if channel_id is None:
+ return None
+ else:
+ return guild.get_channel(channel_id=channel_id)
+
+ async def nsfw(self, message: discord.Message, images: dict):
+ content = message.content
+ guild: discord.Guild = message.guild
+ if not content:
+ content = "*`None`*"
+ try:
+ await message.delete()
+ except discord.Forbidden:
+ await message.channel.send("NSFW Image detected!")
+ return
+
+ embed = discord.Embed(title="NSFW Image Detected")
+ embed.add_field(name="Original Message", value=content)
+ embed.set_author(name=message.author.name, icon_url=message.author.avatar_url)
+ await message.channel.send(embed=embed)
+
+ nsfw_channel = await self.get_nsfw_channel(guild)
+
+ if nsfw_channel is None:
+ return
+ else:
+ for image, r in images.items():
+ if r["unsafe"] > 0.7:
+ await nsfw_channel.send(
+ "NSFW Image from {}".format(message.channel.mention),
+ file=discord.File(image,),
+ )
+
+ @commands.Cog.listener()
+ async def on_message(self, message: discord.Message):
+ is_private = isinstance(message.channel, discord.abc.PrivateChannel)
+
+ if not message.attachments or is_private or message.author.bot:
+ # print("did not qualify")
+ return
+
+ try:
+ is_on = await self.config.guild(message.guild).enabled()
+ except AttributeError:
+ return
+
+ if not is_on:
+ print("Not on")
+ return
+
+ channel: discord.TextChannel = message.channel
+
+ if channel.is_nsfw():
+ print("nsfw channel is okay")
+ return
+
+ check_list = []
+ for attachment in message.attachments:
+ # async with aiohttp.ClientSession() as session:
+ # img = await fetch_img(session, attachment.url)
+
+ ext = attachment.filename
+
+ temp_name = self.data_path / f"nudecheck{self.current_processes}_{ext}"
+
+ self.current_processes += 1
+
+ print("Pre attachment save")
+ await attachment.save(temp_name)
+ check_list.append(temp_name)
+
+ print("Pre nude check")
+ # nude_results = self.detector.detect(temp_name)
+ nude_results = self.classifier.classify([str(n) for n in check_list])
+ # print(nude_results)
+
+ if True in [r["unsafe"] > 0.7 for r in nude_results.values()]:
+ # print("Is nude")
+ await message.add_reaction("❌")
+ await self.nsfw(message, nude_results)
+ else:
+ # print("Is not nude")
+ await message.add_reaction("✅")
+
+
+# async def fetch_img(session, url):
+# with aiohttp.Timeout(10):
+# async with session.get(url) as response:
+# assert response.status == 200
+# return await response.read()
diff --git a/planttycoon/info.json b/planttycoon/info.json
index 32fe8e2..d64d70f 100644
--- a/planttycoon/info.json
+++ b/planttycoon/info.json
@@ -4,16 +4,13 @@
"SnappyDragon",
"PaddoInWonderland"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Grow your own plants! Be sure to take care of it. Do `[p]gardening` to get started",
"hidden": false,
"install_msg": "Thank you for installing PlantTycoon. Check out all the commands with `[p]help PlantTycoon`",
"requirements": [],
"short": "Grow your own plants! Do `[p]gardening` to get started.",
+ "end_user_data_statement": "This cog stores user IDs along with their progress in the PlantTycoon game",
"tags": [
"bobloy",
"games",
diff --git a/planttycoon/planttycoon.py b/planttycoon/planttycoon.py
index a37a42f..61e5e06 100644
--- a/planttycoon/planttycoon.py
+++ b/planttycoon/planttycoon.py
@@ -5,18 +5,19 @@ import datetime
import json
import time
from random import choice
+from typing import Literal
import discord
from redbot.core import Config, bank, commands
from redbot.core.bot import Red
from redbot.core.data_manager import bundled_data_path
+from redbot.core.utils import AsyncIter
-class Gardener(commands.Cog):
+class Gardener:
"""Gardener class"""
def __init__(self, user: discord.User, config: Config):
- super().__init__()
self.user = user
self.config = config
self.badges = []
@@ -180,7 +181,17 @@ class PlantTycoon(commands.Cog):
# self.bank = bot.get_cog('Economy').bank
+ async def red_delete_data_for_user(
+ self,
+ *,
+ requester: Literal["discord_deleted_user", "owner", "user", "user_strict"],
+ user_id: int,
+ ):
+
+ await self.config.user_from_id(user_id).clear()
+
async def _load_plants_products(self):
+ """Runs in __init__.py before cog is added to the bot"""
plant_path = bundled_data_path(self) / "plants.json"
product_path = bundled_data_path(self) / "products.json"
with plant_path.open() as json_data:
diff --git a/qrinvite/info.json b/qrinvite/info.json
index 0db8d11..5817774 100644
--- a/qrinvite/info.json
+++ b/qrinvite/info.json
@@ -2,11 +2,7 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Create a QR code invite for the server",
"hidden": false,
"install_msg": "Thank you for installing QRInvite! Get started with `[p]load qrinvite`, then `[p]help QRInvite`",
@@ -14,6 +10,7 @@
"MyQR"
],
"short": "Create a QR code invite",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"tools",
diff --git a/qrinvite/qrinvite.py b/qrinvite/qrinvite.py
index 4adcbe2..c35f617 100644
--- a/qrinvite/qrinvite.py
+++ b/qrinvite/qrinvite.py
@@ -25,6 +25,10 @@ class QRInvite(Cog):
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.command()
async def qrinvite(
self,
diff --git a/reactrestrict/info.json b/reactrestrict/info.json
index c232ac8..1eaa291 100644
--- a/reactrestrict/info.json
+++ b/reactrestrict/info.json
@@ -2,15 +2,12 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Cog to prevent reactions on specific messages from certain users",
- "hidden": true,
+ "hidden": false,
"install_msg": "Thank you for installing ReactRestrict.",
- "short": "[Incomplete] Prevent reactions",
+ "short": "Prevent reactions to messages",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"react",
"reaction",
diff --git a/reactrestrict/reactrestrict.py b/reactrestrict/reactrestrict.py
index 396a7ae..585d7dc 100644
--- a/reactrestrict/reactrestrict.py
+++ b/reactrestrict/reactrestrict.py
@@ -35,6 +35,10 @@ class ReactRestrict(Cog):
)
self.config.register_global(registered_combos=[])
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
async def combo_list(self) -> List[ReactRestrictCombo]:
"""
Returns a list of reactrestrict combos.
diff --git a/recyclingplant/info.json b/recyclingplant/info.json
index b713f5c..f7c0cdc 100644
--- a/recyclingplant/info.json
+++ b/recyclingplant/info.json
@@ -3,15 +3,12 @@
"Bobloy",
"SnappyDragon"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Apply for a job at the recycling plant! Sort out the garbage!",
"hidden": false,
"install_msg": "Thank you for installing RecyclingPlant. Start recycling today with `[p]load recyclingplant`, then `[p]recyclingplant`",
"short": "Apply for a job at the recycling plant!",
+ "end_user_data_statement": "This cog used the core",
"tags": [
"bobloy",
"environment",
diff --git a/recyclingplant/recyclingplant.py b/recyclingplant/recyclingplant.py
index 2460c4f..cc7bf57 100644
--- a/recyclingplant/recyclingplant.py
+++ b/recyclingplant/recyclingplant.py
@@ -15,6 +15,10 @@ class RecyclingPlant(Cog):
self.bot = bot
self.junk = None
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
def load_junk(self):
junk_path = bundled_data_path(self) / "junk.json"
with junk_path.open() as json_data:
diff --git a/rpsls/info.json b/rpsls/info.json
index c7c9ebe..dc5fa5e 100644
--- a/rpsls/info.json
+++ b/rpsls/info.json
@@ -3,16 +3,13 @@
"Bobloy",
"SnappyDragon"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Play Rock Papers Scissor Lizard Spock by Sam Kass in Discord!",
"hidden": false,
"install_msg": "Thank you for installing RPSLS. Get started with `[p]load rpsls`, then `[p]rpsls`",
"requirements": [],
"short": "Play Rock Papers Scissor Lizard Spock in Discord!",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"star trek",
diff --git a/rpsls/rpsls.py b/rpsls/rpsls.py
index ac80c8e..b831d1c 100644
--- a/rpsls/rpsls.py
+++ b/rpsls/rpsls.py
@@ -21,6 +21,10 @@ class RPSLS(Cog):
super().__init__()
self.bot = bot
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.command()
async def rpsls(self, ctx: commands.Context, choice: str):
"""
diff --git a/sayurl/info.json b/sayurl/info.json
index 1beaf60..c978eb4 100644
--- a/sayurl/info.json
+++ b/sayurl/info.json
@@ -2,16 +2,15 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Convert any website into text and post it in chat",
"hidden": true,
"install_msg": "Thank you for installing SayUrl! Get started with `[p]load sayurl`, then `[p]help SayUrl",
- "requirements": ["html2text"],
+ "requirements": [
+ "html2text"
+ ],
"short": "Convert URL to text",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"tools"
diff --git a/sayurl/sayurl.py b/sayurl/sayurl.py
index ed6ca94..0057b86 100644
--- a/sayurl/sayurl.py
+++ b/sayurl/sayurl.py
@@ -27,6 +27,10 @@ class SayUrl(Cog):
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.command()
async def sayurl(self, ctx: commands.Context, url):
"""
diff --git a/scp/info.json b/scp/info.json
index 4ac9ea9..5ca808c 100644
--- a/scp/info.json
+++ b/scp/info.json
@@ -3,16 +3,13 @@
"Bobloy",
"SnappyDragon"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Look up SCP articles. Warning: Some of them may be too creepy or gruesome.",
"hidden": false,
"install_msg": "You are now connected to the SCP database. You may now proceed to access the data using `[p]load scp`, then `[p]help SCP`",
"requirements": [],
"short": "Look up SCP articles.",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"gruesom"
diff --git a/scp/scp.py b/scp/scp.py
index 0df8c87..3b4176c 100644
--- a/scp/scp.py
+++ b/scp/scp.py
@@ -11,6 +11,10 @@ class SCP(Cog):
super().__init__()
self.bot = bot
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.command()
async def scp(self, ctx: commands.Context, num: int):
"""Look up SCP articles.
diff --git a/stealemoji/info.json b/stealemoji/info.json
index 67d2ad9..f91ce8e 100644
--- a/stealemoji/info.json
+++ b/stealemoji/info.json
@@ -2,16 +2,13 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Steals custom emojis the bot sees and moves them to an emoji server",
- "hidden": true,
+ "hidden": false,
"install_msg": "Thank you for installing StealEmoji",
"requirements": [],
"short": "Steals custom emojis",
+ "end_user_data_statement": "This cog gathers custom emojis from all sources, but stores no End User Data",
"tags": [
"bobloy",
"utils",
diff --git a/stealemoji/stealemoji.py b/stealemoji/stealemoji.py
index 8d05fd7..d3391e6 100644
--- a/stealemoji/stealemoji.py
+++ b/stealemoji/stealemoji.py
@@ -36,6 +36,7 @@ class StealEmoji(Cog):
"managed": None,
"guild_id": None,
"animated": None,
+ "saveid": None,
}
def __init__(self, red: Red):
@@ -48,6 +49,10 @@ class StealEmoji(Cog):
self.is_on = None
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.group()
async def stealemoji(self, ctx: commands.Context):
"""
@@ -56,6 +61,36 @@ class StealEmoji(Cog):
if ctx.invoked_subcommand is None:
pass
+ @checks.is_owner()
+ @stealemoji.command(name="clearemojis")
+ async def se_clearemojis(self, ctx: commands.Context, confirm: bool = False):
+ """Removes the history of all stolen emojis. Will not delete emojis from server banks"""
+ if not confirm:
+ await ctx.maybe_send_embed(
+ "This will reset all stolen emoji data.\n"
+ "If you want to continue, run this command again as:\n"
+ "`[p]stealemoji clearemojis True`"
+ )
+ return
+
+ await self.config.stolemoji.clear()
+ await ctx.tick()
+
+ @checks.is_owner()
+ @stealemoji.command(name="print")
+ async def se_print(self, ctx: commands.Context):
+ """Prints all the emojis that have been stolen so far"""
+ stolen = await self.config.stolemoji()
+ id_list = [v.get("saveid") for k, v in stolen.items()]
+
+ emoj = " ".join(str(e) for e in self.bot.emojis if e.id in id_list)
+
+ if emoj == " ":
+ await ctx.maybe_send_embed("No stolen emojis yet")
+ return
+
+ await ctx.maybe_send_embed(emoj)
+
@checks.is_owner()
@stealemoji.command(name="notify")
async def se_notify(self, ctx: commands.Context):
@@ -174,31 +209,10 @@ class StealEmoji(Cog):
# print("Emoji has already been stolen")
return
- # stolemojis = await self.config.stolemoji()
- #
- # print(stolemojis.keys())
- #
- # if emoji.id in stolemojis:
- # print("Emoji has already been stolen")
- # return
-
- # Alright, time to steal it for real
- # path = urlparse(emoji.url).path
- # ext = os.path.splitext(path)[1]
-
- # async with aiohttp.ClientSession() as session:
- # img = await fetch_img(session, emoji.url)
-
img = await emoji.url.read()
- # path = data_manager.cog_data_path(cog_instance=self) / (emoji.name+ext)
-
- # with path.open("wb") as f:
- # f.write(img)
- # urllib.urlretrieve(emoji.url, emoji.name+ext)
-
try:
- await guildbank.create_custom_emoji(
+ uploaded_emoji = await guildbank.create_custom_emoji(
name=emoji.name, image=img, reason="Stole from " + str(user)
)
except discord.Forbidden as e:
@@ -221,6 +235,7 @@ class StealEmoji(Cog):
# save_dict[k] = getattr(emoji, k, None)
save_dict["guildbank"] = guildbank.id
+ save_dict["saveid"] = uploaded_emoji.id
async with self.config.stolemoji() as stolemoji:
stolemoji[emoji.id] = save_dict
@@ -234,4 +249,4 @@ class StealEmoji(Cog):
else:
target = self.bot.get_channel(notify_settings)
- await target.send(f"Just added emoji {emoji} to server {guildbank}")
+ await target.send(f"Just added emoji {uploaded_emoji} to server {guildbank}")
diff --git a/timerole/info.json b/timerole/info.json
index 34b173b..ec74efb 100644
--- a/timerole/info.json
+++ b/timerole/info.json
@@ -2,15 +2,12 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Apply roles based on the # of days on server",
"hidden": false,
"install_msg": "Thank you for installing timerole.\nGet started with `[p]load timerole`. Configure with `[p]timerole`",
"short": "Apply roles after # of days",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"utilities",
diff --git a/timerole/timerole.py b/timerole/timerole.py
index 273216d..ff93e39 100644
--- a/timerole/timerole.py
+++ b/timerole/timerole.py
@@ -22,6 +22,10 @@ class Timerole(Cog):
self.config.register_guild(**default_guild)
self.updating = self.bot.loop.create_task(self.check_day())
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
def cog_unload(self):
self.updating.cancel()
diff --git a/tts/info.json b/tts/info.json
index 6810a42..707928f 100644
--- a/tts/info.json
+++ b/tts/info.json
@@ -2,18 +2,15 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Send Text2Speech messages as an uploaded mp3",
- "hidden": true,
+ "hidden": false,
"install_msg": "Thank you for installing TTS. Get started with `[p]load tts`, then `[p]help TTS`",
"requirements": [
"gTTS"
],
"short": "Send TTS messages as uploaded mp3",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"utils",
diff --git a/tts/tts.py b/tts/tts.py
index e00bfa7..1291777 100644
--- a/tts/tts.py
+++ b/tts/tts.py
@@ -23,6 +23,10 @@ class TTS(Cog):
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.command(aliases=["t2s", "text2"])
async def tts(self, ctx: commands.Context, *, text: str):
"""
diff --git a/unicode/info.json b/unicode/info.json
index 0d8d24b..edd160f 100644
--- a/unicode/info.json
+++ b/unicode/info.json
@@ -3,16 +3,13 @@
"Bobloy",
"SnappyDragon"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Encode/Decode Unicode characters!",
"hidden": false,
"install_msg": "\u0048\u0065\u006c\u006c\u006f\u0021 \u0054\u0068\u0069\u0073 \u006d\u0065\u0073\u0073\u0061\u0067\u0065 \u0077\u0061\u0073 \u0077\u0072\u0069\u0074\u0074\u0065\u006e \u0069\u006e \u0055\u004e\u0049\u0043\u004f\u0044\u0045\u002e",
"requirements": [],
"short": "Encode/Decode Unicode characters!",
+ "end_user_data_statement": "This cog does not store any End User Data",
"tags": [
"bobloy",
"utility",
diff --git a/unicode/unicode.py b/unicode/unicode.py
index 78eb4f0..4705f5d 100644
--- a/unicode/unicode.py
+++ b/unicode/unicode.py
@@ -12,6 +12,10 @@ class Unicode(Cog):
super().__init__()
self.bot = bot
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
@commands.group(name="unicode", pass_context=True)
async def unicode(self, ctx):
"""Encode/Decode a Unicode character."""
diff --git a/werewolf/info.json b/werewolf/info.json
index 5fbc50b..af67794 100644
--- a/werewolf/info.json
+++ b/werewolf/info.json
@@ -2,16 +2,13 @@
"author": [
"Bobloy"
],
- "bot_version": [
- 3,
- 0,
- 0
- ],
+ "min_bot_version": "3.3.0",
"description": "Customizable Werewolf Game",
- "hidden": false,
+ "hidden": true,
"install_msg": "Thank you for installing Werewolf! Get started with `[p]load werewolf`\n Use `[p]wwset` to run inital setup",
"requirements": [],
"short": "Werewolf Game",
+ "end_user_data_statement": "This store user IDs in memory while they're actively using the cog, and store no persistent End User Data.",
"tags": [
"mafia",
"werewolf",
diff --git a/werewolf/werewolf.py b/werewolf/werewolf.py
index cd4d95b..1f8fc3f 100644
--- a/werewolf/werewolf.py
+++ b/werewolf/werewolf.py
@@ -20,6 +20,7 @@ class Werewolf(Cog):
"""
def __init__(self, bot: Red):
+ super().__init__()
self.bot = bot
self.config = Config.get_conf(
self, identifier=87101114101119111108102, force_registration=True
@@ -37,6 +38,10 @@ class Werewolf(Cog):
self.games = {} # Active games stored here, id is per guild
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
def __unload(self):
print("Unload called")
for game in self.games.values():