71 Commits

Author SHA1 Message Date
aikaterna
6bf7ac77fe Update away.py 2020-10-17 22:08:44 -07:00
aikaterna
d6c42a0aac [Chatchart] Cleanup & deny addition 2020-10-15 16:58:27 -07:00
James
f1d1380d9c [Away] per user filtering in servers (#166)
* per member toggleaway

* format and black

* remove black format as requested
2020-10-14 08:36:28 -07:00
jack1142
edb5be3a25 Avoid dispatching when there's no feed updates (#173)
* Avoid dispatching when there's no feed updates

* version bump
2020-10-14 07:06:53 -07:00
jack1142
763539f352 [RSS] Add feed update dispatch (#168)
* Add feed update dispatch

* version

Co-authored-by: aikaterna <20862007+aikaterna@users.noreply.github.com>
2020-10-13 09:00:30 -07:00
aikaterna
ddd8c14d03 [RSS] Try for an image instead of assume 2020-10-13 08:57:48 -07:00
Jyu Viole Grace
89f56d69eb typo fix + sort perms (#171) 2020-10-13 08:38:25 -07:00
Fixator10
7d19d8128f [timezone] [p]time set check for owner (#167)
instead of mod
2020-10-10 17:24:43 -07:00
Flame442
971b4a3df2 Don't try to remove managed roles (#170) 2020-10-10 17:24:20 -07:00
PhenoM4n4n
586020bc6c chatchart blocking (#169) 2020-10-09 16:11:22 -07:00
aikaterna
65bd1386cc [RSS] Account for aiohttp fetch failure 2020-10-06 16:29:58 -07:00
Neuro Assassin
3b38c5293e [Fix] Prevent error if a TOT channel is deleted (#165)
* Add fix for if a TOT channel is deleted

* version

Co-authored-by: aikaterna <20862007+aikaterna@users.noreply.github.com>
2020-10-06 07:56:50 -07:00
TrustyJAID
e7647bda90 [RndStatus] remove loop to get user count (#164)
The loop to get user count is unnecessary since `self.bot.users` already contains a unique list of user objects and is updated internally via listeners.
2020-10-06 07:56:21 -07:00
aikaterna
5097271ab4 [TrickOrTreat] Dial back rewards a little 2020-10-05 11:42:56 -07:00
aikaterna
5c99bc4ee0 [TrickOrTreat] Chocolate is now visible 2020-10-05 09:57:01 -07:00
jack1142
6e8c22d1e5 Add gitignore (#163) 2020-10-05 09:09:07 -07:00
aikaterna
b431b599ae [RSS] Catch feeds that have no content 2020-10-05 08:43:06 -07:00
jack1142
5bfbc3b83b [RSS] Make RSS work with cog disabling API (#160)
* [RSS] Make RSS work with cog disabling API

* Update feed qualifier to work with disabling

Instead of the catch-all dealing with feeds having no time, just let the catch-all handle everything. If the cog has been disabled for a long period of time and there are no matches via the saved feed information, the feed will only post 1 feed post instead of all of them (10, 20, 25 posts depending on the feed usually). If the cog is reinstated and there is only a partial match (say, 18 out of 20 feeds before it finds a qualifying match)... it will still post all 18 posts.

Co-authored-by: aikaterna <20862007+aikaterna@users.noreply.github.com>
2020-10-05 08:42:07 -07:00
aikaterna
f32a146b6e [TrickOrTreat] Channel bucket -> user 2020-10-05 07:55:17 -07:00
aikaterna
7fa2947a85 [TrickOrTreat] Add chocolate 2020-10-05 07:51:10 -07:00
Fixator10
e0e53cf87e [trickortreat] make number in eatcandy optional (#161) 2020-10-04 13:18:57 -07:00
aikaterna
a2245d9542 [TrickOrTreat] Who knew, this cog has a version 2020-10-02 09:53:57 -07:00
aikaterna
85d22213b1 [TrickOrTreat] steal -> stealcandy 2020-10-02 09:47:51 -07:00
aikaterna
3329b2f48c [TrickOrTreat] eat -> eatcandy 2020-10-02 09:26:21 -07:00
aikaterna
3a97066458 [Chatchart] Another guess at negatives
plus check attach file perms while we're here
2020-10-02 08:55:26 -07:00
aikaterna
d6c052dcfd [RSS] Different list unpacking 2020-10-01 15:10:34 -07:00
aikaterna
eb0ff5bdeb [RSS] Update info.json 2020-10-01 12:06:20 -07:00
aikaterna
6fdb24a7c4 [RSS] Provisional list unpacking 2020-10-01 11:41:57 -07:00
aikaterna
ac9405a2b5 [Chatchart] No negative values
How the user got this no one knows, but here you go
2020-10-01 08:34:32 -07:00
aikaterna
8c81e8a42b [RSS] Cleaner twitch content 2020-09-30 08:34:35 -07:00
aikaterna
185af4a19e [RSS] Time tags & special case pinterest feeds 2020-09-29 21:58:17 -07:00
aikaterna
891a124834 [RSS] Add datetime to time tag validation 2020-09-29 20:39:44 -07:00
aikaterna
dc6419deed [RSS] More feed fun, pt 6 2020-09-29 20:03:46 -07:00
aikaterna
98781b9268 [RSS] More feed fun, pt 5 2020-09-29 17:59:32 -07:00
Jyu Viole Grace
2bfd00236a [RSS] added support for targeted channels (#158)
* rss support for targeted channels

* process review

* remove style and revert black

* Check for permissions

Co-authored-by: aikaterna <20862007+aikaterna@users.noreply.github.com>
2020-09-27 13:54:02 -07:00
aikaterna
c4492abbe8 [WarcraftLogs] Remove wclgear until fixed 2020-09-25 16:23:33 -07:00
aikaterna
e30f7b05c5 [RSS] More feed fun pt.3 2020-09-25 12:42:32 -07:00
aikaterna
4e2d3738ce [RSS] More feed fun pt 2 2020-09-24 20:47:59 -04:00
aikaterna
c27d53c636 [RndStatus] Update presences and types on 1 status 2020-09-24 11:33:01 -07:00
PhenoM4n4n
bf264ab18b [rndstatus] add selectable status types (#153)
* status

* oops

* default status should be online and not idle

* lol

* QoL improvements while testing this PR

Presence will now be updated after using commands that change presence status or type. 
More clarity for users on the option they have picked with the rndstatus type and rndstatus status commands.
Black @ 120.

Co-authored-by: aikaterna <20862007+aikaterna@users.noreply.github.com>
2020-09-24 11:01:54 -07:00
bobloy
fe09f1e306 Add get_usertime for outside calls (#156) 2020-09-24 10:25:36 -07:00
aikaterna
04c167c79e [RSS] More feed fun 2020-09-23 10:47:20 -07:00
aikaterna
8b043d81a8 [RSS] Handle no title entries a little better pt.3 2020-09-22 15:45:02 -07:00
aikaterna
d4be924066 [Latex] Encode equation 2020-09-22 14:21:34 -07:00
aikaterna
f3cebef0e2 [RSS] Handle no title entries a little better pt.2 2020-09-22 13:00:33 -07:00
aikaterna
ccfeb4a1b1 [RSS] Handle no title entries a little better
This still has the issue that if a feed continually posts entries without a title, nothing will be posted from the feed, but a no-title post should be a rarity to begin with (only seen very sporadically in Pinterest posts so far)
2020-09-22 13:44:33 -04:00
aikaterna
02fccf2edd [RSS] Better hex code matching 2020-09-22 01:50:08 -04:00
aikaterna
3d59573ca8 [RSS] Update queue size properly 2020-09-21 19:32:28 -07:00
aikaterna
263a4e3c1a [RSS] An overengineered color related addition 2020-09-21 21:47:52 -04:00
jack1142
b1b5b75336 Add aikaternacogs_rss_message dispatch (#154)
* Add recursive mapping proxy type

* Move `force` logic to `get_current_feed()` method

* Add `on_aikaternacogs_rss_message` dispatch

* Welp, looks like MappingProxyType isn't subclassable, that's a bummer

* God, I'm an idiot...

* Fuck the extra protections then, I'm sure all cog devs are responsible

;)

* Update version number
2020-09-21 11:43:18 -07:00
Jyu Viole Grace
e33d3b4ff9 Fix invalid JSON information error (#151) 2020-09-18 14:26:05 -07:00
aikaterna
312ef80ab6 [RSS] Change info.json encoding 2020-09-18 16:07:46 -04:00
aikaterna
8c877fe9fe [RSS] Initial commit 2020-09-18 15:33:01 -04:00
aikaterna
d9587b9c75 [NoFlippedTables] Guild check first 2020-09-09 12:59:39 -07:00
aikaterna
2a27d9012e [NoFlippedTables] No unflippin' in silent channels 2020-09-09 10:26:12 -07:00
aikaterna
e4fe5c0264 [IcyParser] No audio in DMs 2020-09-09 10:19:00 -07:00
aikaterna
c94ead0429 [Dictionary] Pagify results 2020-09-09 10:17:01 -07:00
aikaterna
559f7049db [TrickOrTreat] Add cog level docstring 2020-09-06 23:57:03 -07:00
aikaterna
0f5c5eac9d [Quiz] Move data deletion statement 2020-09-06 23:55:13 -07:00
aikaterna
e8d4be26eb [NoLinks] Add cog level docstring 2020-09-06 23:52:28 -07:00
aikaterna
a8db7698a1 [LuigiPoker] Add cog level docstring 2020-09-06 23:51:08 -07:00
aikaterna
e18406319a [IcyParser] Add cog level docstring 2020-09-06 23:48:49 -07:00
aikaterna
68874cb3c6 [CardsAgainstHumanity] Add cog level docstring 2020-09-06 23:47:04 -07:00
aikaterna
92c1567b87 [Blurplefly] Give it a cog level docstring 2020-09-06 23:44:40 -07:00
aikaterna
edf21b3902 [WarcraftLogs] Phase 5
AQ dropped a month ago? Sorry, too busy killing 42,000 silithid...
2020-09-02 10:23:19 -07:00
aikaterna
c387bca651 [Latex] Typo time 2020-08-31 13:32:11 -07:00
aikaterna
01a1b9e3cb [Latex] Fix the fix 2020-08-31 13:29:48 -07:00
jack1142
7aef03c957 Support code blocks (#146)
LET USERS BE THE TESTERS! CHAOS! BLOOD FOR THE BLOOD GOD!
2020-08-31 13:27:14 -07:00
aikaterna
6a2c99143e [Latex] Initial commit 2020-08-31 14:25:34 -04:00
Draper
940b5ea84c fucking fuck fuckaroo (#144) 2020-08-26 10:10:34 -07:00
Draper
ae9dbf569c Data API Complicance (Red 3.4) (#136)
* Simple ones first

* Less simple but still simple.

* Slightly more complicated

* use correct name

* move to module

* Black -l 120

* review

* give users the proper feedback

Co-authored-by: aikaterna <20862007+aikaterna@users.noreply.github.com>
2020-08-26 09:57:43 -07:00
107 changed files with 2558 additions and 904 deletions

129
.gitignore vendored Normal file
View File

@@ -0,0 +1,129 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Sublime project files
*.sublime-project
*.sublime-workspace
# PyCharm project files
.idea/
# VS Code project files
.vscode/

View File

@@ -29,6 +29,8 @@ inspirobot - Fetch "inspirational" messages from inspirobot.me with [p]inspireme
invites - Display invites that are available on the server and the information those invites contain. The bot must have the administrator permission granted on the guild to be able to use this cog.
latex - A simple cog originally by Stevy for v2 that displayes LaTeX expressions in an image.
luigipoker - Play the Luigi Poker minigame from New Super Mario Brothers. Ported from the v2 version written by themario30.
noflippedtables - A v3 port of irdumb's v2 cog with a little extra surprise included. Unflip all the tables.
@@ -53,6 +55,8 @@ retrosign - A v3 port of Anismash's retrosign cog.
rndstatus - A v3 port of Twentysix's rndstatus cog with a couple extra settings.
rss - Will's RSS cog ported for v3 with a lot of extra bells and whistles.
snacktime - A v3 port of irdumb's snacktime cog. Now with friends!
timezone - A v3 port of Fishyfing's timezone cog with a few improvements.

View File

@@ -1,5 +1,7 @@
from .antiphoneclapper import AntiPhoneClapper
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
async def setup(bot):
bot.add_cog(AntiPhoneClapper(bot))

View File

@@ -13,9 +13,13 @@ log = logging.getLogger("red.aikaterna.antiphoneclapper")
class AntiPhoneClapper(commands.Cog):
"""This cog deletes bad GIFs that will crash phone clients."""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.session = aiohttp.ClientSession()
self.session = aiohttp.ClientSession()
self.config = Config.get_conf(self, 2719371001, force_registration=True)
default_guild = {"watching": []}
@@ -60,9 +64,7 @@ class AntiPhoneClapper(commands.Cog):
else:
return await ctx.send("Channel is not being watched.")
await self.config.guild(ctx.guild).watching.set(channel_list)
await ctx.send(
f"{self.bot.get_channel(channel.id).mention} will not have bad gifs removed."
)
await ctx.send(f"{self.bot.get_channel(channel.id).mention} will not have bad gifs removed.")
def is_phone_clapper(self, im):
limit = im.size
@@ -101,9 +103,7 @@ class AntiPhoneClapper(commands.Cog):
if phone_clapper:
try:
await m.delete()
await m.channel.send(
f"{m.author.mention} just tried to send a phone-killing GIF and I removed it."
)
await m.channel.send(f"{m.author.mention} just tried to send a phone-killing GIF and I removed it.")
return
except discord.errors.Forbidden:
await m.channel.send(f"Don't send GIFs that do that, {m.author.mention}")

View File

@@ -11,5 +11,6 @@
"pillow"
],
"short": "Deletes messages with malformed GIFs.",
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -1,5 +1,16 @@
from .away import Away
__red_end_user_data_statement__ = (
"This cog stores data provided by users "
"for the express purpose of redisplaying. "
"It does not store user data which was not "
"provided through a command. "
"Users may remove their own content "
"without making a data removal request. "
"This cog does not support data requests, "
"but will respect deletion requests."
)
def setup(bot):
bot.add_cog(Away(bot))

View File

@@ -1,6 +1,6 @@
import discord
from redbot.core import Config, commands, checks
from typing import Optional
from typing import Optional, Literal
import datetime
import re
@@ -11,7 +11,7 @@ class Away(commands.Cog):
"""Le away cog"""
default_global_settings = {"ign_servers": []}
default_guild_settings = {"TEXT_ONLY": False}
default_guild_settings = {"TEXT_ONLY": False, "BLACKLISTED_MEMBERS": []}
default_user_settings = {
"MESSAGE": False,
"IDLE_MESSAGE": False,
@@ -19,9 +19,14 @@ class Away(commands.Cog):
"OFFLINE_MESSAGE": False,
"GAME_MESSAGE": {},
"STREAMING_MESSAGE": False,
"LISTENING_MESSAGE": False,
"LISTENING_MESSAGE": False
}
async def red_delete_data_for_user(
self, *, requester: Literal["discord", "owner", "user", "user_strict"], user_id: int,
):
await self._away.user_from_id(user_id).clear()
def __init__(self, bot):
self.bot = bot
self._away = Config.get_conf(self, 8423491260, force_registration=True)
@@ -57,6 +62,7 @@ class Away(commands.Cog):
"""
avatar = author.avatar_url_as() # This will return default avatar if no avatar is present
color = author.color
link = None
if message:
link = IMAGE_LINKS.search(message)
if link:
@@ -69,17 +75,14 @@ class Away(commands.Cog):
em.set_author(name=f"{author.display_name} is currently idle", icon_url=avatar)
elif state == "dnd":
em = discord.Embed(description=message, color=color)
em.set_author(
name=f"{author.display_name} is currently do not disturb", icon_url=avatar
)
em.set_author(name=f"{author.display_name} is currently do not disturb", icon_url=avatar)
elif state == "offline":
em = discord.Embed(description=message, color=color)
em.set_author(name=f"{author.display_name} is currently offline", icon_url=avatar)
elif state == "gaming":
em = discord.Embed(description=message, color=color)
em.set_author(
name=f"{author.display_name} is currently playing {author.activity.name}",
icon_url=avatar,
name=f"{author.display_name} is currently playing {author.activity.name}", icon_url=avatar,
)
em.title = getattr(author.activity, "details", None)
thumbnail = getattr(author.activity, "large_image_url", None)
@@ -89,8 +92,7 @@ class Away(commands.Cog):
status = [c for c in author.activities if c.type == discord.ActivityType.playing]
em = discord.Embed(description=message, color=color)
em.set_author(
name=f"{author.display_name} is currently playing {status[0].name}",
icon_url=avatar,
name=f"{author.display_name} is currently playing {status[0].name}", icon_url=avatar,
)
em.title = getattr(status[0], "details", None)
thumbnail = getattr(status[0], "large_image_url", None)
@@ -140,8 +142,7 @@ class Away(commands.Cog):
em.description = message + "\n" + author.activity.url
em.title = getattr(author.activity, "details", None)
em.set_author(
name=f"{author.display_name} is currently streaming {author.activity.name}",
icon_url=avatar,
name=f"{author.display_name} is currently streaming {author.activity.name}", icon_url=avatar,
)
elif state == "streamingcustom":
activity = [c for c in author.activities if c.type == discord.ActivityType.streaming]
@@ -150,8 +151,7 @@ class Away(commands.Cog):
em.description = message + "\n" + activity[0].url
em.title = getattr(author.activity, "details", None)
em.set_author(
name=f"{author.display_name} is currently streaming {activity[0].name}",
icon_url=avatar,
name=f"{author.display_name} is currently streaming {activity[0].name}", icon_url=avatar,
)
else:
em = discord.Embed(color=color)
@@ -175,7 +175,14 @@ class Away(commands.Cog):
"""
Makes the message to display if embeds aren't available
"""
message = await self.find_user_mention(message)
url = None
if message:
message = await self.find_user_mention(message)
link = IMAGE_LINKS.search(message)
if link:
url_loc = message.index(link.group(0))
url = message[url_loc:]
message = message.replace(link.group(0), " ")
if state == "away":
msg = f"{author.display_name} is currently away"
@@ -191,9 +198,7 @@ class Away(commands.Cog):
status = [c for c in author.activities if c.type == discord.ActivityType.playing]
msg = f"{author.display_name} is currently playing {status[0].name}"
elif state == "listening":
artist_title = f"{author.activity.title} by " + ", ".join(
a for a in author.activity.artists
)
artist_title = f"{author.activity.title} by " + ", ".join(a for a in author.activity.artists)
currently_playing = self._draw_play(author.activity)
msg = f"{author.display_name} is currently listening to {artist_title}\n{currently_playing}"
elif state == "listeningcustom":
@@ -209,11 +214,13 @@ class Away(commands.Cog):
else:
msg = f"{author.display_name} is currently away"
if message != " " and state != "listeningcustom":
msg += f" and has set the following message: `{message}`"
elif message != " " and state == "listeningcustom":
msg += f"\n\nCustom message: `{message}`"
if message and state != "listeningcustom":
msg += f" and has set the following message: `{message.rstrip()}`"
elif message and state == "listeningcustom":
msg += f"\n\nCustom message: `{message.rstrip()}`"
if url:
msg += f"\n{url}"
return msg
async def is_mod_or_admin(self, member: discord.Member):
@@ -242,8 +249,9 @@ class Away(commands.Cog):
if message.author.bot:
return
bl_members = await self._away.guild(guild).BLACKLISTED_MEMBERS()
for author in message.mentions:
if guild.id in list_of_guilds_ign and not await self.is_mod_or_admin(author):
if (guild.id in list_of_guilds_ign and not await self.is_mod_or_admin(author)) or author.id in bl_members:
continue
user_data = await self._away.user(author).all()
text_only = await self._away.guild(guild).TEXT_ONLY()
@@ -313,9 +321,7 @@ class Away(commands.Cog):
await message.channel.send(msg, delete_after=delete_after)
continue
if streaming_msg and type(author.activity) is discord.CustomActivity:
stream_status = [
c for c in author.activities if c.type == discord.ActivityType.streaming
]
stream_status = [c for c in author.activities if c.type == discord.ActivityType.streaming]
if not stream_status:
continue
streaming_msg, delete_after = streaming_msg
@@ -337,9 +343,7 @@ class Away(commands.Cog):
await message.channel.send(msg, delete_after=delete_after)
continue
if listening_msg and type(author.activity) is discord.CustomActivity:
listening_status = [
c for c in author.activities if c.type == discord.ActivityType.listening
]
listening_status = [c for c in author.activities if c.type == discord.ActivityType.listening]
if not listening_status:
continue
listening_msg, delete_after = listening_msg
@@ -364,9 +368,7 @@ class Away(commands.Cog):
await message.channel.send(msg, delete_after=delete_after)
break
if gaming_msgs and type(author.activity) is discord.CustomActivity:
game_status = [
c for c in author.activities if c.type == discord.ActivityType.playing
]
game_status = [c for c in author.activities if c.type == discord.ActivityType.playing]
if not game_status:
continue
for game in gaming_msgs:
@@ -395,9 +397,17 @@ class Away(commands.Cog):
await self._away.user(author).MESSAGE.set(False)
msg = "You're now back."
else:
if message is None:
if message is None and len(ctx.message.attachments) == 0:
await self._away.user(author).MESSAGE.set((" ", delete_after))
else:
if len(ctx.message.attachments) > 0:
link = IMAGE_LINKS.search(ctx.message.attachments[0].url)
url = link.group(0)
if link:
if message:
message = f"{message} {url}"
else:
message = url
await self._away.user(author).MESSAGE.set((message, delete_after))
msg = "You're now set as away."
await ctx.send(msg)
@@ -501,15 +511,11 @@ class Away(commands.Cog):
msg = "The bot will no longer reply for you when you're mentioned while listening to Spotify."
else:
await self._away.user(author).LISTENING_MESSAGE.set((message, delete_after))
msg = (
"The bot will now reply for you when you're mentioned while listening to Spotify."
)
msg = "The bot will now reply for you when you're mentioned while listening to Spotify."
await ctx.send(msg)
@commands.command(name="gaming")
async def gaming_(
self, ctx, game: str, delete_after: Optional[int] = None, *, message: str = None
):
async def gaming_(self, ctx, game: str, delete_after: Optional[int] = None, *, message: str = None):
"""
Set an automatic reply when you're playing a specified game.
@@ -536,13 +542,26 @@ class Away(commands.Cog):
@commands.command(name="toggleaway")
@checks.admin_or_permissions(administrator=True)
async def _ignore(self, ctx):
async def _ignore(self, ctx, member: discord.Member=None):
"""
Toggle away messages on the whole server.
Toggle away messages on the whole server or a specific guild member.
Mods, Admins and Bot Owner are immune to this.
"""
guild = ctx.message.guild
if member:
bl_mems = await self._away.guild(guild).BLACKLISTED_MEMBERS()
if member.id not in bl_mems:
bl_mems.append(member.id)
await self._away.guild(guild).BLACKLISTED_MEMBERS.set(bl_mems)
msg = f"Away messages will not appear when {member.display_name} is mentioned in this guild."
await ctx.send(msg)
elif member.id in bl_mems:
bl_mems.remove(member.id)
await self._away.guild(guild).BLACKLISTED_MEMBERS.set(bl_mems)
msg = f"Away messages will appear when {member.display_name} is mentioned in this guild."
await ctx.send(msg)
return
if guild.id in (await self._away.ign_servers()):
guilds = await self._away.ign_servers()
guilds.remove(guild.id)
@@ -567,7 +586,9 @@ class Away(commands.Cog):
if text_only:
message = "Away messages will now be embedded or text only based on the bot's permissions for embed links."
else:
message = "Away messages are now forced to be text only, regardless of the bot's permissions for embed links."
message = (
"Away messages are now forced to be text only, regardless of the bot's permissions for embed links."
)
await self._away.guild(ctx.guild).TEXT_ONLY.set(not text_only)
await ctx.send(message)
@@ -619,9 +640,7 @@ class Away(commands.Cog):
if ctx.channel.permissions_for(ctx.me).embed_links:
em = discord.Embed(description=msg[:2048], color=author.color)
em.set_author(
name=f"{author.display_name}'s away settings", icon_url=author.avatar_url
)
em.set_author(name=f"{author.display_name}'s away settings", icon_url=author.avatar_url)
await ctx.send(embed=em)
else:
await ctx.send(f"{author.display_name} away settings\n" + msg)

View File

@@ -8,5 +8,6 @@
"tags": [
"away"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog stores data provided by users for the express purpose of redisplaying. It does not store user data which was not provided through a command. Users may remove their own content without making a data removal request. This cog does not support data requests, but will respect deletion requests."
}

View File

@@ -1,5 +1,7 @@
from .blurplefy import Blurplefy
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(Blurplefy(bot))

View File

@@ -7,7 +7,6 @@ from PIL import Image, ImageEnhance, ImageSequence
from io import BytesIO
import aiohttp
import asyncio
import copy
import datetime
import io
import math
@@ -15,14 +14,20 @@ from resizeimage import resizeimage
from redbot.core import Config, commands, checks
blurple = (114, 137, 218)
blurplehex = 0x7289da
blurplehex = 0x7289DA
darkblurple = (78, 93, 148)
white = (255, 255, 255)
class Blurplefy(commands.Cog):
"""Blurplefy images and check blurple content of images."""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
"""Blurplefy images and check content of images."""
"""Blurplefy images and check blurple content of images."""
self.bot = bot
self.config = Config.get_conf(self, 2778931480, force_registration=True)
@@ -38,9 +43,7 @@ class Blurplefy(commands.Cog):
"""Toggle a role award for having a blurple profile picture."""
blurple_role_id = await self.config.guild(ctx.guild).blurple_role()
if blurple_role_id is None:
await ctx.send(
"Enter the role name to award: it needs to be a valid, already existing role."
)
await ctx.send("Enter the role name to award: it needs to be a valid, already existing role.")
def check(m):
return m.author == ctx.author
@@ -140,11 +143,7 @@ class Blurplefy(commands.Cog):
im = resizeimage.resize_width(im, (imsize[0] * downsizefraction))
imsize = list(im.size)
impixels = imsize[0] * imsize[1]
await ctx.send(
"{}, image resized smaller for easier processing.".format(
ctx.message.author.display_name
)
)
await ctx.send("{}, image resized smaller for easier processing.".format(ctx.message.author.display_name))
image = self.blurple_imager(im, imsize)
image = discord.File(fp=image, filename="image.png")
@@ -155,19 +154,11 @@ class Blurplefy(commands.Cog):
percentwhite = round(((noofwhitepixels / noofpixels) * 100), 2)
embed = discord.Embed(title="", colour=0x7289DA)
embed.add_field(name="Total amount of Blurple", value="{}%".format(blurplenesspercentage), inline=False)
embed.add_field(name="Blurple (rgb(114, 137, 218))", value="{}%".format(percentblurple), inline=True)
embed.add_field(name="White (rgb(255, 255, 255))", value="{}\%".format(percentwhite), inline=True)
embed.add_field(
name="Total amount of Blurple", value="{}%".format(blurplenesspercentage), inline=False
)
embed.add_field(
name="Blurple (rgb(114, 137, 218))", value="{}%".format(percentblurple), inline=True
)
embed.add_field(
name="White (rgb(255, 255, 255))", value="{}\%".format(percentwhite), inline=True
)
embed.add_field(
name="Dark Blurple (rgb(78, 93, 148))",
value="{}\%".format(percentdblurple),
inline=True,
name="Dark Blurple (rgb(78, 93, 148))", value="{}\%".format(percentdblurple), inline=True,
)
embed.add_field(
name="Guide",
@@ -247,20 +238,14 @@ class Blurplefy(commands.Cog):
except Exception:
isgif = False
await ctx.send(
"{}, image fetched, analyzing image...".format(ctx.message.author.display_name)
)
await ctx.send("{}, image fetched, analyzing image...".format(ctx.message.author.display_name))
if impixels > maxpixelcount:
downsizefraction = math.sqrt(maxpixelcount / impixels)
im = resizeimage.resize_width(im, (imsize[0] * downsizefraction))
imsize = list(im.size)
impixels = imsize[0] * imsize[1]
await ctx.send(
"{}, image resized smaller for easier processing".format(
ctx.message.author.display_name
)
)
await ctx.send("{}, image resized smaller for easier processing".format(ctx.message.author.display_name))
if isgif is False:
image = self.imager(im, imsize)
@@ -323,9 +308,7 @@ class Blurplefy(commands.Cog):
for i in range(3):
if not (blurple[i] + colourbuffer > pixel[i] > blurple[i] - colourbuffer):
checkblurple = 0
if not (
darkblurple[i] + colourbuffer > pixel[i] > darkblurple[i] - colourbuffer
):
if not (darkblurple[i] + colourbuffer > pixel[i] > darkblurple[i] - colourbuffer):
checkdarkblurple = 0
if not (white[i] + colourbuffer > pixel[i] > white[i] - colourbuffer):
checkwhite = 0
@@ -397,19 +380,11 @@ class Blurplefy(commands.Cog):
@commands.command()
async def countdown(self, ctx):
"""Countdown to Discord's 6th Anniversary."""
embed = discord.Embed(name="", colour=0x7289da)
timeleft = (
datetime.datetime(2020, 5, 13)
+ datetime.timedelta(hours=7)
- datetime.datetime.utcnow()
)
embed = discord.Embed(name="", colour=0x7289DA)
timeleft = datetime.datetime(2020, 5, 13) + datetime.timedelta(hours=7) - datetime.datetime.utcnow()
embed.set_author(name="Time left until Discord's 6th Anniversary")
if int(timeleft.total_seconds()) < 0:
timeleft = (
datetime.datetime(2021, 5, 13)
+ datetime.timedelta(hours=7)
- datetime.datetime.utcnow()
)
timeleft = datetime.datetime(2021, 5, 13) + datetime.timedelta(hours=7) - datetime.datetime.utcnow()
embed.set_author(name="Time left until Discord's 6th Anniversary")
embed.add_field(
name="Countdown to midnight, May 13, California time (UTC-7):",

View File

@@ -18,5 +18,6 @@
"image",
"profile"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -1,5 +1,7 @@
from .cah import CardsAgainstHumanity
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(CardsAgainstHumanity(bot))

View File

@@ -10,19 +10,21 @@ from redbot.core.data_manager import bundled_data_path
class CardsAgainstHumanity(commands.Cog):
"""Play Cards Against Humanity in DMs."""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.games = []
self.maxBots = (
5 # Max number of bots that can be added to a game - don't count toward max players
)
self.maxBots = 5 # Max number of bots that can be added to a game - don't count toward max players
self.maxPlayers = 10 # Max players for ranjom joins
self.maxDeadTime = 3600 # Allow an hour of dead time before killing a game
self.checkTime = 300 # 5 minutes between dead time checks
self.winAfter = 10 # 10 wins for the game
self.botWaitMin = (
5 # Minimum number of seconds before the bot makes a decision (default 5)
)
self.botWaitMin = 5 # Minimum number of seconds before the bot makes a decision (default 5)
self.botWaitMax = 30 # Max number of seconds before a bot makes a decision (default 30)
self.userTimeout = 500 # 5 minutes to timeout
self.utCheck = 30 # Check timeout every 30 seconds
@@ -196,8 +198,7 @@ class CardsAgainstHumanity(commands.Cog):
member["Task"] = None
continue
await self.sendToUser(
member["User"],
f"Game id: *{game['ID']}* has been closed due to inactivity.",
member["User"], f"Game id: *{game['ID']}* has been closed due to inactivity.",
)
# Set running to false
@@ -211,9 +212,7 @@ class CardsAgainstHumanity(commands.Cog):
return True
else:
# Not in PM
await message.channel.send(
"Cards Against Humanity commands must be run in Direct Messages with the bot."
)
await message.channel.send("Cards Against Humanity commands must be run in Direct Messages with the bot.")
return False
def randomID(self, length=8):
@@ -310,8 +309,7 @@ class CardsAgainstHumanity(commands.Cog):
if not newCreator["IsBot"]:
newCreator["Creator"] = True
await self.sendToUser(
newCreator["User"],
"The creator of this game left. **YOU** are now the creator.",
newCreator["User"], "The creator of this game left. **YOU** are now the creator.",
)
break
@@ -330,8 +328,7 @@ class CardsAgainstHumanity(commands.Cog):
member["Task"] = None
else:
await self.sendToUser(
member["User"],
f"**You were removed from game id:** ***{game['ID']}.***",
member["User"], f"**You were removed from game id:** ***{game['ID']}.***",
)
# Removed, no need to finish the loop
break
@@ -456,9 +453,7 @@ class CardsAgainstHumanity(commands.Cog):
name=f"Not enough players to continue! ({len(game['Members'])}/{self.minMembers})"
)
prefix = await self.bot.get_valid_prefixes()
stat_embed.set_footer(
text=f"Have other users join with: {prefix[0]}joincah {game['ID']}"
)
stat_embed.set_footer(text=f"Have other users join with: {prefix[0]}joincah {game['ID']}")
await self.sendToUser(member["User"], stat_embed, True)
continue
if member["IsBot"] == True:
@@ -541,13 +536,9 @@ class CardsAgainstHumanity(commands.Cog):
else:
stat_embed.set_author(name=f"{winnerName} won!")
if len(winner["Cards"]) == 1:
msg = "The **Winning** card was:\n\n{}".format(
"{}".format(" - ".join(winner["Cards"]))
)
msg = "The **Winning** card was:\n\n{}".format("{}".format(" - ".join(winner["Cards"])))
else:
msg = "The **Winning** cards were:\n\n{}".format(
"{}".format(" - ".join(winner["Cards"]))
)
msg = "The **Winning** cards were:\n\n{}".format("{}".format(" - ".join(winner["Cards"])))
await self.sendToUser(member["User"], stat_embed, True)
await self.sendToUser(member["User"], msg)
await asyncio.sleep(0.1)
@@ -788,13 +779,9 @@ class CardsAgainstHumanity(commands.Cog):
# Advances the game
if len(game["Members"]) < self.minMembers:
stat_embed = discord.Embed(color=discord.Color.red())
stat_embed.set_author(
name=f"Not enough players to continue! ({len(game['Members'])}/{self.minMembers})"
)
stat_embed.set_author(name=f"Not enough players to continue! ({len(game['Members'])}/{self.minMembers})")
prefix = await self.bot.get_valid_prefixes()
stat_embed.set_footer(
text=f"Have other users join with: {prefix[0]}joincah {game['ID']}"
)
stat_embed.set_footer(text=f"Have other users join with: {prefix[0]}joincah {game['ID']}")
for member in game["Members"]:
if member["IsBot"]:
continue
@@ -819,9 +806,7 @@ class CardsAgainstHumanity(commands.Cog):
if member["IsBot"]:
stat_embed.set_author(name=f"{self.botName} ({member['ID']}) is the WINNER!!")
else:
stat_embed.set_author(
name=f"{self.displayname(member['User'])} is the WINNER!!"
)
stat_embed.set_author(name=f"{self.displayname(member['User'])} is the WINNER!!")
stat_embed.set_footer(text="Congratulations!")
break
if winner:
@@ -935,9 +920,7 @@ class CardsAgainstHumanity(commands.Cog):
user = member
index = userGame["Members"].index(member)
if index == userGame["Judge"]:
await self.sendToUser(
ctx.author, "You're the judge. You don't get to lay cards this round."
)
await self.sendToUser(ctx.author, "You're the judge. You don't get to lay cards this round.")
return
for submit in userGame["Submitted"]:
if submit["By"]["User"] == ctx.author:
@@ -954,9 +937,7 @@ class CardsAgainstHumanity(commands.Cog):
stat_embed.set_author(
name=f"Not enough players to continue! ({len(userGame['Members'])}/{self.minMembers})"
)
stat_embed.set_footer(
text=f"Have other users join with: {prefix[0]}joincah {userGame['ID']}"
)
stat_embed.set_footer(text=f"Have other users join with: {prefix[0]}joincah {userGame['ID']}")
return await self.sendToUser(ctx.author, stat_embed, True)
numberCards = userGame["BlackCard"]["Pick"]
@@ -992,9 +973,7 @@ class CardsAgainstHumanity(commands.Cog):
)
return await self.showHand(ctx, ctx.author)
if c < 1 or c > len(user["Hand"]):
await self.sendToUser(
ctx.author, f"Card numbers must be between 1 and {len(user['Hand'])}."
)
await self.sendToUser(ctx.author, f"Card numbers must be between 1 and {len(user['Hand'])}.")
return await self.showHand(ctx, ctx.author)
cards.append(user["Hand"][c - 1]["Text"])
# Remove from user's hand
@@ -1009,14 +988,10 @@ class CardsAgainstHumanity(commands.Cog):
try:
card = int(card)
except Exception:
await self.sendToUser(
ctx.author, f"You need to lay a valid card with `{prefix[0]}lay [card number]`"
)
await self.sendToUser(ctx.author, f"You need to lay a valid card with `{prefix[0]}lay [card number]`")
return await self.showHand(ctx, ctx.author)
if card < 1 or card > len(user["Hand"]):
await self.sendToUser(
ctx.author, f"Card numbers must be between 1 and {len(user['Hand'])}."
)
await self.sendToUser(ctx.author, f"Card numbers must be between 1 and {len(user['Hand'])}.")
return await self.showHand(ctx, ctx.author)
# Valid card
newSubmission = {"By": user, "Cards": [user["Hand"].pop(card - 1)["Text"]]}
@@ -1068,9 +1043,7 @@ class CardsAgainstHumanity(commands.Cog):
except Exception:
card = -1
if card < 0 or card >= totalUsers:
return await self.sendToUser(
ctx.author, f"Your pick must be between 1 and {totalUsers}."
)
return await self.sendToUser(ctx.author, f"Your pick must be between 1 and {totalUsers}.")
# Pick is good!
await self.winningCard(ctx, userGame, card)
@@ -1099,9 +1072,7 @@ class CardsAgainstHumanity(commands.Cog):
embed.set_author(name="**Setting up the game...**")
await ctx.author.send(embed=embed)
except discord.errors.Forbidden:
return await ctx.send(
"You must allow Direct Messages from the bot for this game to work."
)
return await ctx.send("You must allow Direct Messages from the bot for this game to work.")
# Check if the user is already in game
userGame = await self.userGame(ctx.author)
@@ -1178,9 +1149,7 @@ class CardsAgainstHumanity(commands.Cog):
embed.set_author(name="**Setting up the game...**")
await ctx.author.send(embed=embed)
except discord.errors.Forbidden:
return await ctx.send(
"You must allow Direct Messages from the bot for this game to work."
)
return await ctx.send("You must allow Direct Messages from the bot for this game to work.")
# Check if the user is already in game
userGame = await self.userGame(ctx.author)
@@ -1253,9 +1222,7 @@ class CardsAgainstHumanity(commands.Cog):
for member in game["Members"]:
if member["IsBot"]:
continue
await self.sendToUser(
member["User"], f"***{self.displayname(ctx.author)}*** **joined the game!**"
)
await self.sendToUser(member["User"], f"***{self.displayname(ctx.author)}*** **joined the game!**")
# We got a user!
currentTime = int(time.time())
@@ -1328,9 +1295,7 @@ class CardsAgainstHumanity(commands.Cog):
# We are the creator - let's check the number of bots
if botCount >= self.maxBots:
# Too many bots!
return await self.sendToUser(
ctx.author, f"You already have enough bots (max is {self.maxBots})."
)
return await self.sendToUser(ctx.author, f"You already have enough bots (max is {self.maxBots}).")
# We can get another bot!
botID = self.randomBotID(userGame)
lobot = {
@@ -1350,9 +1315,7 @@ class CardsAgainstHumanity(commands.Cog):
for member in userGame["Members"]:
if member["IsBot"]:
continue
await self.sendToUser(
member["User"], f"***{self.botName} ({botID})*** **joined the game!**"
)
await self.sendToUser(member["User"], f"***{self.botName} ({botID})*** **joined the game!**")
# await self.nextPlay(ctx, userGame)
# Check if adding put us at minimum members
@@ -1391,9 +1354,7 @@ class CardsAgainstHumanity(commands.Cog):
if member["User"] == ctx.author:
if not member["Creator"]:
# You didn't make this game
return await self.sendToUser(
ctx.author, "Only the player that created the game can add bots."
)
return await self.sendToUser(ctx.author, "Only the player that created the game can add bots.")
member["Time"] = int(time.time())
if number == None:
# No number specified - let's add the max number of bots
@@ -1407,9 +1368,7 @@ class CardsAgainstHumanity(commands.Cog):
# We are the creator - let's check the number of bots
if botCount >= self.maxBots:
# Too many bots!
return await self.sendToUser(
ctx.author, f"You already have enough bots (max is {self.maxBots})."
)
return await self.sendToUser(ctx.author, f"You already have enough bots (max is {self.maxBots}).")
if number > (self.maxBots - botCount):
number = self.maxBots - botCount
@@ -1483,9 +1442,7 @@ class CardsAgainstHumanity(commands.Cog):
if member["User"] == ctx.author:
if not member["Creator"]:
# You didn't make this game
return await self.sendToUser(
ctx.author, "Only the player that created the game can remove bots."
)
return await self.sendToUser(ctx.author, "Only the player that created the game can remove bots.")
member["Time"] = int(time.time())
# We are the creator - let's check the number of bots
if id == None:
@@ -1761,9 +1718,7 @@ class CardsAgainstHumanity(commands.Cog):
if member["User"] == ctx.author:
if not member["Creator"]:
# You didn't make this game
return await self.sendToUser(
ctx.author, "Only the player that created the game can remove bots."
)
return await self.sendToUser(ctx.author, "Only the player that created the game can remove bots.")
# We are the creator - let's check the number of bots
if setting == None:
# Output idle kick status

View File

@@ -14,5 +14,6 @@
"cards",
"games"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -1,5 +1,7 @@
from .chatchart import Chatchart
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(Chatchart(bot))

View File

@@ -1,29 +1,42 @@
# Lines 72 through 90 are influenced heavily by cacobot's stats module:
# This cog is influenced heavily by cacobot's stats module:
# https://github.com/Orangestar12/cacobot/blob/master/cacobot/stats.py
# Big thanks to Redjumpman for changing the beta version from
# Imagemagick/cairosvg to matplotlib.
# Thanks to violetnyte for suggesting this cog.
import asyncio
import discord
import heapq
import os
from io import BytesIO
from typing import Optional
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
plt.switch_backend("agg")
from redbot.core import commands
from redbot.core import commands, Config
class Chatchart(commands.Cog):
"""Show activity."""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2766691001, force_registration=True)
def create_chart(self, top, others, channel):
default_guild = {"channel_deny": []}
self.config.register_guild(**default_guild)
@staticmethod
async def create_chart(top, others, channel):
plt.clf()
sizes = [x[1] for x in top]
labels = ["{} {:g}%".format(x[0], x[1]) for x in top]
@@ -81,28 +94,49 @@ class Chatchart(commands.Cog):
@commands.command()
@commands.cooldown(1, 10, commands.BucketType.channel)
@commands.max_concurrency(1, commands.BucketType.channel)
@commands.bot_has_permissions(attach_files=True)
async def chatchart(self, ctx, channel: Optional[discord.TextChannel] = None, messages=5000):
"""
Generates a pie chart, representing the last 5000 messages in the specified channel.
"""
e = discord.Embed(description="Loading...", colour=0x00ccff)
e.set_thumbnail(url="https://i.imgur.com/vSp4xRk.gif")
if channel is None:
channel = ctx.channel
deny = await self.config.guild(ctx.guild).channel_deny()
if channel.id in deny:
return await ctx.send(f"I am not allowed to create a chatchart of {channel.mention}.")
e = discord.Embed(
description="This might take a while...", colour=await self.bot.get_embed_colour(location=channel)
)
em = await ctx.send(embed=e)
if channel is None:
channel = ctx.message.channel
history = []
history_counter = 0
if not channel.permissions_for(ctx.message.author).read_messages == True:
await em.delete()
try:
await em.delete()
except discord.NotFound:
pass
return await ctx.send("You're not allowed to access that channel.")
try:
async for msg in channel.history(limit=messages):
history.append(msg)
history_counter += 1
await asyncio.sleep(0.005)
if history_counter % 250 == 0:
new_embed = discord.Embed(
description=f"This might take a while...\n{history_counter} messages gathered",
colour=await self.bot.get_embed_colour(location=channel),
)
await em.edit(embed=new_embed)
except discord.errors.Forbidden:
await em.delete()
try:
await em.delete()
except discord.NotFound:
pass
return await ctx.send("No permissions to read that channel.")
msg_data = {"total count": 0, "users": {}}
msg_data = {"total count": 0, "users": {}}
for msg in history:
if len(msg.author.display_name) >= 20:
short_name = "{}...".format(msg.author.display_name[:20]).replace("$", "\\$")
@@ -119,9 +153,12 @@ class Chatchart(commands.Cog):
msg_data["users"][whole_name]["msgcount"] = 1
msg_data["total count"] += 1
if msg_data['users'] == {}:
await em.delete()
return await ctx.message.channel.send(f'Only bots have sent messages in {channel.mention}')
if msg_data["users"] == {}:
try:
await em.delete()
except discord.NotFound:
pass
return await ctx.send(f"Only bots have sent messages in {channel.mention} or I can't read message history.")
for usr in msg_data["users"]:
pd = float(msg_data["users"][usr]["msgcount"]) / float(msg_data["total count"])
@@ -133,11 +170,58 @@ class Chatchart(commands.Cog):
(x, msg_data["users"][x][y])
for x in msg_data["users"]
for y in msg_data["users"][x]
if y == "percent"
if (y == "percent" and msg_data["users"][x][y] > 0)
],
key=lambda x: x[1],
)
others = 100 - sum(x[1] for x in top_ten)
img = self.create_chart(top_ten, others, channel)
await em.delete()
await ctx.message.channel.send(file=discord.File(img, "chart.png"))
chart = await self.create_chart(top_ten, others, channel)
try:
await em.delete()
except discord.NotFound:
pass
await ctx.send(file=discord.File(chart, "chart.png"))
@commands.command()
async def ccdeny(self, ctx, channel: discord.TextChannel):
"""Add a channel to deny chatchart use."""
channel_list = await self.config.guild(ctx.guild).channel_deny()
if channel.id not in channel_list:
channel_list.append(channel.id)
await self.config.guild(ctx.guild).channel_deny.set(channel_list)
await ctx.send(f"{channel.mention} was added to the deny list for chatchart.")
@commands.command()
async def ccdenylist(self, ctx):
"""List the channels that are denied."""
no_channels_msg = "Chatchart is currently allowed everywhere in this server."
channel_list = await self.config.guild(ctx.guild).channel_deny()
if not channel_list:
msg = no_channels_msg
else:
msg = "Chatchart is not allowed in:\n"
remove_list = []
for channel in channel_list:
channel_obj = self.bot.get_channel(channel)
if not channel_obj:
remove_list.append(channel)
else:
msg += f"{channel_obj.mention}\n"
if remove_list:
new_list = [x for x in channel_list if x not in remove_list]
await self.config.guild(ctx.guild).channel_deny.set(new_list)
if len(remove_list) == len(channel_list):
msg = no_channels_msg
await ctx.send(msg)
@commands.command()
async def ccallow(self, ctx, channel: discord.TextChannel):
"""Remove a channel from the deny list to allow chatchart use."""
channel_list = await self.config.guild(ctx.guild).channel_deny()
if channel.id in channel_list:
channel_list.remove(channel.id)
else:
return await ctx.send("Channel is not on the deny list.")
await self.config.guild(ctx.guild).channel_deny.set(channel_list)
await ctx.send(f"{channel.mention} will be allowed for chatchart use.")

View File

@@ -14,5 +14,6 @@
"requirements": [
"matplotlib"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -1,5 +1,7 @@
from .dadjokes import DadJokes
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(DadJokes(bot))

View File

@@ -1,16 +1,21 @@
from redbot.core import commands
import aiohttp
class DadJokes(commands.Cog):
"""Random dad jokes from icanhazdadjoke.com"""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
@commands.command()
async def dadjoke(self, ctx):
"""Gets a random dad joke."""
api = 'https://icanhazdadjoke.com/'
async with aiohttp.request('GET', api, headers={'Accept': 'text/plain'}) as r:
api = "https://icanhazdadjoke.com/"
async with aiohttp.request("GET", api, headers={"Accept": "text/plain"}) as r:
result = await r.text()
await ctx.send(f"`{result}`")

View File

@@ -6,5 +6,6 @@
"install_msg": "Gets a random dad joke from icanhazdadjoke.com. Thanks for installing.",
"short": "Random dad jokes",
"tags": ["jokes", "dad", "dadjokes"],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -1,5 +1,7 @@
from .dictionary import Dictionary
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(Dictionary(bot))

View File

@@ -3,6 +3,7 @@ from bs4 import BeautifulSoup
import logging
import re
from redbot.core import commands
from redbot.core.utils.chat_formatting import pagify
log = logging.getLogger("red.aikaterna.dictionary")
@@ -12,6 +13,10 @@ class Dictionary(commands.Cog):
"""Word, yo
Parts of this cog are adapted from the PyDictionary library."""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.session = aiohttp.ClientSession()
@@ -30,14 +35,16 @@ class Dictionary(commands.Cog):
@commands.command()
async def antonym(self, ctx, *, word: str):
"""Displays antonyms for a given word."""
search_msg = await ctx.send("Searching...")
search_term = word.split(" ", 1)[0]
result = await self._antonym(ctx, search_term)
if not result:
return await search_msg.edit(content="This word is not in the dictionary.")
await ctx.send("This word is not in the dictionary.")
return
result_text = "*, *".join(result)
await search_msg.edit(content=f"Antonyms for **{search_term}**: *{result_text}*")
msg = f"Antonyms for **{search_term}**: *{result_text}*"
for page in pagify(msg, delims=["\n"]):
await ctx.send(page)
async def _antonym(self, ctx, word):
data = await self._get_soup_object(f"http://www.thesaurus.com/browse/{word}")
@@ -60,7 +67,9 @@ class Dictionary(commands.Cog):
result = await self._definition(ctx, search_term)
str_buffer = ""
if not result:
return await search_msg.edit(content="This word is not in the dictionary.")
await search_msg.delete()
await ctx.send("This word is not in the dictionary.")
return
for key in result:
str_buffer += f"\n**{key}**: \n"
counter = 1
@@ -77,7 +86,9 @@ class Dictionary(commands.Cog):
else:
str_buffer += f"{str(counter)}. {val}\n"
counter += 1
await search_msg.edit(content=str_buffer)
await search_msg.delete()
for page in pagify(str_buffer, delims=["\n"]):
await ctx.send(page)
async def _definition(self, ctx, word):
data = await self._get_soup_object(f"http://wordnetweb.princeton.edu/perl/webwn?s={word}")
@@ -117,11 +128,13 @@ class Dictionary(commands.Cog):
@commands.command()
async def synonym(self, ctx, *, word: str):
"""Displays synonyms for a given word."""
search_msg = await ctx.send("Searching...")
search_term = word.split(" ", 1)[0]
result = await self._synonym(ctx, search_term)
if not result:
return await search_msg.edit(content="This word is not in the dictionary.")
await ctx.send("This word is not in the dictionary.")
return
result_text = "*, *".join(result)
await search_msg.edit(content=f"Synonyms for **{search_term}**: *{result_text}*")
msg = f"Synonyms for **{search_term}**: *{result_text}*"
for page in pagify(msg, delims=["\n"]):
await ctx.send(page)

View File

@@ -11,5 +11,6 @@
"requirements": [
"beautifulsoup4"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -1,5 +1,9 @@
from .dungeon import Dungeon
__red_end_user_data_statement__ = (
"This cog does not persistently store end user data. " "This cog does store discord IDs as needed for operation. "
)
def setup(bot):
bot.add_cog(Dungeon(bot))

View File

@@ -1,5 +1,6 @@
import asyncio
import datetime
from typing import Literal
import discord
import logging
from redbot.core import Config, commands, checks, modlog
@@ -12,6 +13,20 @@ log = logging.getLogger("red.aikaterna.dungeon")
class Dungeon(commands.Cog):
"""Auto-quarantine suspicious users."""
async def red_delete_data_for_user(
self, *, requester: Literal["discord", "owner", "user", "user_strict"], user_id: int,
):
if requester == "discord":
# user is deleted, just comply
data = await self.config.all_guilds()
for guild_id, guild_data in data.items():
if user_id in guild_data.get("bypass", []):
bypass = guild_data.get("bypass", [])
bypass = set(bypass)
bypass.discard(user_id)
await self.config.guild_from_id(guild_id).bypass.set(list(bypass))
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2700081001, force_registration=True)
@@ -57,18 +72,16 @@ class Dungeon(commands.Cog):
if not dungeon_role_obj:
return await ctx.send("No dungeon role set.")
#Managed roles CANNOT be removed by the bot.
remaining_roles = [r for r in user.roles if r.managed]
try:
await user.edit(
roles=[], reason=f"Removing all roles, {ctx.message.author} is banishing user"
)
await user.edit(roles=remaining_roles, reason=f"Removing all roles, {ctx.message.author} is banishing user")
except discord.Forbidden:
return await ctx.send(
"I need permission to manage roles or the role hierarchy might not allow me to do this. I need a role higher than the person you're trying to banish."
)
await user.add_roles(
dungeon_role_obj, reason=f"Adding dungeon role, {ctx.message.author} is banishing user"
)
await user.add_roles(dungeon_role_obj, reason=f"Adding dungeon role, {ctx.message.author} is banishing user")
if blacklist:
blacklist_msg = ", blacklisted from the bot,"
@@ -89,9 +102,7 @@ class Dungeon(commands.Cog):
"""Sets the announcement channel for users moved to the dungeon."""
await self.config.guild(ctx.guild).announce_channel.set(channel.id)
announce_channel_id = await self.config.guild(ctx.guild).announce_channel()
await ctx.send(
f"User announcement channel set to: {self.bot.get_channel(announce_channel_id).mention}."
)
await ctx.send(f"User announcement channel set to: {self.bot.get_channel(announce_channel_id).mention}.")
@dungeon.command()
async def autoban(self, ctx):
@@ -106,9 +117,7 @@ class Dungeon(commands.Cog):
auto_ban = await self.config.guild(ctx.guild).auto_ban()
if not ban_message:
await self.config.guild(ctx.guild).auto_ban_message.set(None)
return await ctx.send(
"Auto-ban message removed. No message will be sent on an auto-ban."
)
return await ctx.send("Auto-ban message removed. No message will be sent on an auto-ban.")
await self.config.guild(ctx.guild).auto_ban_message.set(str(ban_message))
await self.config.guild(ctx.guild).auto_ban.set(True)
await ctx.send(f"Auto-ban has been turned on.\nMessage to send on ban:\n{ban_message}")
@@ -254,13 +263,10 @@ class Dungeon(commands.Cog):
role_check = True
try:
await user.remove_roles(
dungeon_role_obj,
reason=f"Removing dungeon role, verified by {ctx.message.author}.",
dungeon_role_obj, reason=f"Removing dungeon role, verified by {ctx.message.author}.",
)
if not user_role_obj:
return await ctx.send(
"Dungeon role removed, but no member role is set so I can't award one."
)
return await ctx.send("Dungeon role removed, but no member role is set so I can't award one.")
await user.add_roles(user_role_obj, reason="Adding member role.")
except discord.Forbidden:
return await ctx.send(
@@ -281,9 +287,7 @@ class Dungeon(commands.Cog):
try:
await user.send(dm_message)
except discord.Forbidden:
await ctx.send(
f"I couldn't DM {user} to let them know they've been verified, they've blocked me."
)
await ctx.send(f"I couldn't DM {user} to let them know they've been verified, they've blocked me.")
@dungeon.command()
async def autosetup(self, ctx):
@@ -291,11 +295,7 @@ class Dungeon(commands.Cog):
You must deny the default role (@ everyone) from viewing or typing in other channels in your server manually.
"""
try:
overwrites = {
ctx.guild.default_role: discord.PermissionOverwrite(
send_messages=False, read_messages=False
)
}
overwrites = {ctx.guild.default_role: discord.PermissionOverwrite(send_messages=False, read_messages=False)}
dungeon_role = await ctx.guild.create_role(name="Dungeon")
@@ -304,9 +304,7 @@ class Dungeon(commands.Cog):
dungeon_role, read_messages=True, send_messages=False, read_message_history=True
)
dungeon_channel = await ctx.guild.create_text_channel(
"Silenced", category=dungeon_category
)
dungeon_channel = await ctx.guild.create_text_channel("Silenced", category=dungeon_category)
await dungeon_channel.set_permissions(
dungeon_role, read_messages=True, send_messages=False, read_message_history=True
)
@@ -409,8 +407,7 @@ class Dungeon(commands.Cog):
user_role_obj = discord.utils.get(member.guild.roles, id=user_role_id)
try:
await member.add_roles(
user_role_obj,
reason="User has bypassed Dungeon checks. Assigning member role.",
user_role_obj, reason="User has bypassed Dungeon checks. Assigning member role.",
)
except discord.Forbidden:
pass
@@ -444,9 +441,7 @@ class Dungeon(commands.Cog):
log.debug(perm_msg)
return
try:
await member.guild.ban(
member, reason="Dungeon auto-ban", delete_message_days=0
)
await member.guild.ban(member, reason="Dungeon auto-ban", delete_message_days=0)
except discord.Forbidden:
if announce_channel:
return await channel_object.send(
@@ -466,14 +461,7 @@ class Dungeon(commands.Cog):
else:
try:
await modlog.create_case(
self.bot,
member.guild,
now,
"ban",
member,
member.guild.me,
until=None,
channel=None,
self.bot, member.guild, now, "ban", member, member.guild.me, until=None, channel=None,
)
except RuntimeError:
log.error(

View File

@@ -15,5 +15,6 @@
"dungeon",
"autoban"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store end user data. This cog does store discord IDs as needed for operation. "
}

View File

@@ -1,5 +1,14 @@
from .hunting import Hunting
__red_end_user_data_statement__ = (
"This cog does not persistently store end user data. "
"This cog does store discord IDs as needed for operation. "
"This cog does store user stats for the cog such as their score. "
"Users may remove their own content without making a data removal request."
"This cog does not support data requests, "
"but will respect deletion requests."
)
async def setup(bot):
bot.add_cog(Hunting(bot))

View File

@@ -1,7 +1,8 @@
from typing import Literal
import asyncio
import discord
import datetime
import itertools
import math
import random
import time
@@ -11,12 +12,17 @@ from redbot.core.utils.chat_formatting import bold, box, humanize_list, humanize
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
__version__ = "3.1.3"
__version__ = "3.1.4"
class Hunting(commands.Cog):
"""Hunting, it hunts birds and things that fly."""
async def red_delete_data_for_user(
self, *, requester: Literal["discord", "owner", "user", "user_strict"], user_id: int,
):
await self.config.user_from_id(user_id).clear()
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2784481002, force_registration=True)
@@ -92,9 +98,7 @@ class Hunting(commands.Cog):
header = "{score:{score_len}}{name:2}\n".format(
score="# Birds Shot",
score_len=score_len + 5,
name="Name"
if not str(ctx.author.mobile_status) in ["online", "idle", "dnd"]
else "Name",
name="Name" if not str(ctx.author.mobile_status) in ["online", "idle", "dnd"] else "Name",
)
temp_msg = header
for account in sorted_acc:
@@ -124,9 +128,7 @@ class Hunting(commands.Cog):
colour=await ctx.bot.get_embed_color(location=ctx.channel),
description=box(title, lang="prolog") + (box(page, lang="md")),
)
embed.set_footer(
text=f"Page {humanize_number(pages)}/{humanize_number(math.ceil(len(temp_msg) / 800))}"
)
embed.set_footer(text=f"Page {humanize_number(pages)}/{humanize_number(math.ceil(len(temp_msg) / 800))}")
pages += 1
page_list.append(embed)
if len(page_list) == 1:
@@ -252,7 +254,9 @@ class Hunting(commands.Cog):
await self.config.guild(ctx.guild).hunt_interval_minimum.set(interval_min)
await self.config.guild(ctx.guild).hunt_interval_maximum.set(interval_max)
await self.config.guild(ctx.guild).wait_for_bang_timeout.set(bang_timeout)
message += f"Timing has been set:\nMin time {interval_min}s\nMax time {interval_max}s\nBang timeout {bang_timeout}s"
message += (
f"Timing has been set:\nMin time {interval_min}s\nMax time {interval_max}s\nBang timeout {bang_timeout}s"
)
await ctx.send(bold(message))
@hunting.command()
@@ -280,9 +284,7 @@ class Hunting(commands.Cog):
if channel.id not in self.paused_games:
self.paused_games.append(channel.id)
await channel.send(
bold(
"It seems there are no hunters here. The hunt will be resumed when someone treads here again."
)
bold("It seems there are no hunters here. The hunt will be resumed when someone treads here again.")
)
return False
@@ -306,9 +308,7 @@ class Hunting(commands.Cog):
return False
if channel != message.channel:
return False
return (
message.content.lower().split(" ")[0] == "bang" if message.content else False
)
return message.content.lower().split(" ")[0] == "bang" if message.content else False
try:
bang_msg = await self.bot.wait_for("message", check=check, timeout=timeout)
@@ -346,9 +346,7 @@ class Hunting(commands.Cog):
bang_now = time.time()
time_for_bang = "{:.3f}".format(bang_now - now)
bangtime = (
"" if not await self.config.guild(guild).bang_time() else f" in {time_for_bang}s"
)
bangtime = "" if not await self.config.guild(guild).bang_time() else f" in {time_for_bang}s"
if random.randrange(0, 17) > 1:
await self._add_score(guild, author, animal)
@@ -379,9 +377,7 @@ class Hunting(commands.Cog):
self.in_game.append(message.channel.id)
guild_data = await self.config.guild(message.guild).all()
wait_time = random.randrange(
guild_data["hunt_interval_minimum"], guild_data["hunt_interval_maximum"]
)
wait_time = random.randrange(guild_data["hunt_interval_minimum"], guild_data["hunt_interval_maximum"])
self.next_bang[message.guild.id] = datetime.datetime.fromtimestamp(
int(time.mktime(datetime.datetime.utcnow().timetuple())) + wait_time
)

View File

@@ -4,5 +4,6 @@
"install_msg": "Check out [p]hunting to get started.",
"short": "A bird hunting game.",
"tags": ["hunting", "hunt", "game"],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store end user data. This cog does store discord IDs as needed for operation. This cog does store user stats for the cog such as their score. Users may remove their own content without making a data removal request. This cog does not support data requests, but will respect deletion requests."
}

View File

@@ -1,5 +1,7 @@
from .icyparser import IcyParser
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(IcyParser(bot))

View File

@@ -7,6 +7,12 @@ from redbot.core import commands
class IcyParser(commands.Cog):
"""Icyparser/Shoutcast stream reader."""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.session = aiohttp.ClientSession()
@@ -46,6 +52,7 @@ class IcyParser(commands.Cog):
def cog_unload(self):
self.bot.loop.create_task(self.session.close())
@commands.guild_only()
@commands.command(aliases=["icynp"])
async def icyparser(self, ctx, url=None):
"""Show Icecast or Shoutcast stream information, if any."""
@@ -69,9 +76,7 @@ class IcyParser(commands.Cog):
f"Can't read the stream information for <{player.current.uri if not url else url}>, it may not be an Icecast or Shoutcast radio station or there may be no stream information available."
)
song = f"**[{icy[0]}]({player.current.uri if not url else url})**\n"
embed = discord.Embed(
colour=await ctx.embed_colour(), title="Now Playing", description=song
)
embed = discord.Embed(colour=await ctx.embed_colour(), title="Now Playing", description=song)
if icy[2]:
embed.set_thumbnail(url=icy[1])
await ctx.send(embed=embed)

View File

@@ -10,5 +10,6 @@
"icecast",
"shoutcast"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -1,5 +1,7 @@
from .inspirobot import Inspirobot
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(Inspirobot(bot))

View File

@@ -12,5 +12,6 @@
"inspire",
"inspirobot"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -5,6 +5,11 @@ from redbot.core import commands
class Inspirobot(commands.Cog):
"""Posts images generated by https://inspirobot.me"""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.session = aiohttp.ClientSession()
@@ -13,9 +18,7 @@ class Inspirobot(commands.Cog):
async def inspireme(self, ctx):
"""Fetch a random "inspirational message" from the bot."""
try:
async with self.session.request(
"GET", "http://inspirobot.me/api?generate=true"
) as page:
async with self.session.request("GET", "http://inspirobot.me/api?generate=true") as page:
pic = await page.text(encoding="utf-8")
em = discord.Embed()
em.set_image(url=pic)

4
latex/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
from .latex import Latex
def setup(bot):
bot.add_cog(Latex(bot))

10
latex/info.json Normal file
View File

@@ -0,0 +1,10 @@
{
"author": ["aikaterna", "Stevy"],
"description": "Generates an image for a LaTeX expression.",
"install_msg": "Thanks for installing, have fun.",
"short": "Generates an image for a LaTeX expression.",
"tags": ["latex"],
"requirements": ["pillow"],
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

64
latex/latex.py Normal file
View File

@@ -0,0 +1,64 @@
import aiohttp
import discord
import io
import logging
import re
from PIL import Image, ImageOps
import urllib.parse as parse
from redbot.core import commands
log = logging.getLogger("red.aikaterna.latex")
START_CODE_BLOCK_RE = re.compile(r"^((```(la)?tex)(?=\s)|(```))")
class Latex(commands.Cog):
"""LaTeX expressions via an image."""
async def red_delete_data_for_user(self, **kwargs):
"""Nothing to delete."""
return
def __init__(self, bot):
self.bot = bot
self.session = aiohttp.ClientSession()
@staticmethod
def cleanup_code_block(content):
# remove ```latex\n```/```tex\n```/``````
if content.startswith("```") and content.endswith("```"):
return START_CODE_BLOCK_RE.sub("", content)[:-3]
# remove `foo`
return content.strip("` \n")
@commands.guild_only()
@commands.command()
async def latex(self, ctx, *, equation):
"""Takes a LaTeX expression and makes it pretty."""
base_url = "https://latex.codecogs.com/gif.latex?%5Cbg_white%20%5CLARGE%20"
equation = self.cleanup_code_block(equation)
equation = parse.quote(equation)
url = f"{base_url}{equation}"
try:
async with self.session.get(url) as r:
image = await r.read()
image = Image.open(io.BytesIO(image)).convert("RGBA")
except Exception as exc:
log.exception("Something went wrong while trying to read the image:\n ", exc_info=exc)
image = None
if not image:
return await ctx.send("I can't get the image from the website, check your console for more information.")
image = ImageOps.expand(image, border=10, fill="white")
image_file_object = io.BytesIO()
image.save(image_file_object, format="png")
image_file_object.seek(0)
image_final = discord.File(fp=image_file_object, filename="latex.png")
await ctx.send(file=image_final)
def cog_unload(self):
self.bot.loop.create_task(self.session.close())

View File

@@ -1,5 +1,7 @@
from .luigipoker import LuigiPoker
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(LuigiPoker(bot))

View File

@@ -4,5 +4,6 @@
"install_msg": "Thanks for installing.",
"short": "A Luigi poker minigame.",
"tags": ["poker", "game"],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -1,7 +1,7 @@
import asyncio
import logging
from random import randint
from redbot.core import commands, checks
from redbot.core import commands
from redbot.core.utils.chat_formatting import box
from redbot.core.utils.predicates import MessagePredicate
@@ -83,8 +83,13 @@ class Deck:
class LuigiPoker(commands.Cog):
"""The Luigi Poker minigame from New Super Mario Brothers."""
__version__ = "0.1.1"
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
__version__ = "0.1.2"
def __init__(self, bot):
self.bot = bot
@@ -165,8 +170,7 @@ class LuigiPoker(commands.Cog):
return await self.fold(ctx)
else:
log.error(
"LuigiPoker: Something broke unexpectedly in _play_response. Please report it.",
exc_info=True,
"LuigiPoker: Something broke unexpectedly in _play_response. Please report it.", exc_info=True,
)
async def hit(self, ctx):
@@ -176,9 +180,7 @@ class LuigiPoker(commands.Cog):
"Examples: `1,3,5` or `4, 5`"
)
try:
user_resp = await ctx.bot.wait_for(
"message", timeout=60, check=MessagePredicate.same_context(ctx)
)
user_resp = await ctx.bot.wait_for("message", timeout=60, check=MessagePredicate.same_context(ctx))
except asyncio.TimeoutError:
await ctx.send("No response.")
return await self.fold(ctx)

View File

@@ -1,5 +1,7 @@
from .noflippedtables import NoFlippedTables
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(NoFlippedTables(bot))
bot.add_cog(NoFlippedTables(bot))

View File

@@ -5,5 +5,6 @@
"description" : "Unflip all the flipped tables.",
"install_msg" : "Usage: [p]help tableset",
"tags" : ["noflippedtables", "no flip", "tables"],
"disabled" : false
"disabled" : false,
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -8,6 +8,10 @@ from redbot.core.utils.chat_formatting import box
class NoFlippedTables(commands.Cog):
"""For the table sympathizers"""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2712290002, force_registration=True)
@@ -81,6 +85,8 @@ class NoFlippedTables(commands.Cog):
user = message.author
if not message.guild:
return
if not channel.permissions_for(message.guild.me).send_messages:
return
if hasattr(user, "bot") and user.bot is True:
return
toggle = await self.config.guild(message.guild).TOGGLE()

View File

@@ -1,5 +1,7 @@
from .nolinks import NoLinks
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(NoLinks(bot))
bot.add_cog(NoLinks(bot))

View File

@@ -13,5 +13,6 @@
"links",
"automod"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -3,10 +3,17 @@ import re
from redbot.core import Config, commands, checks
LINKS = re.compile(
"(([\w]+:)?//)?(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+)?@)?([\d\w][-\d\w]{0,253}[\d\w]\.)+[\w]{2,63}(:[\d]+)?(/([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)*(\?(&?([-+_~.\d\w]|%[a-fA-f\d]{2,2})=?)*)?(#([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)?"
)
"(([\w]+:)?//)?(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+)?@)?([\d\w][-\d\w]{0,253}[\d\w]\.)+[\w]{2,63}(:[\d]+)?(/([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)*(\?(&?([-+_~.\d\w]|%[a-fA-f\d]{2,2})=?)*)?(#([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)?"
)
class NoLinks(commands.Cog):
"""A heavy-handed hammer for anything that looks like a link."""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2740131001, force_registration=True)
@@ -128,12 +135,8 @@ class NoLinks(commands.Cog):
sentence = message.content.split()
for word in sentence:
if self._match_url(word):
msg = "**Message Removed in** {} ({})\n".format(
message.channel.mention, message.channel.id
)
msg += "**Message sent by**: {} ({})\n".format(
message.author.name, message.author.id
)
msg = "**Message Removed in** {} ({})\n".format(message.channel.mention, message.channel.id)
msg += "**Message sent by**: {} ({})\n".format(message.author.name, message.author.id)
msg += "**Message content**:\n{}".format(message.content)
if message_channel:
await message_channel.send(msg)

View File

@@ -1,5 +1,9 @@
from .otherbot import Otherbot
__red_end_user_data_statement__ = (
"This cog does not persistently store end user data. " "This cog does store discord IDs as needed for operation. "
)
async def setup(bot):
cog = Otherbot(bot)

View File

@@ -1,9 +1,16 @@
{
"author": ["aikaterna", "Predä 。#1001"],
"description": "Alerts a role when bot(s) go offline.",
"install_msg": "Thanks for installing, have fun.",
"permissions": ["manage_roles"],
"short": "Alerts a role when bot(s) go offline.",
"tags": ["bots"],
"type": "COG"
"author": [
"aikaterna", "Predä 。#1001"
],
"description": "Alerts a role when bot(s) go offline.",
"install_msg": "Thanks for installing, have fun.",
"permissions" : [
"manage_roles"
],
"short": "Alerts a role when bot(s) go offline.",
"tags": [
"bots"
],
"type": "COG",
"end_user_data_statement": "This cog does not persistently store end user data. This cog does store discord IDs as needed for operation. "
}

View File

@@ -1,3 +1,5 @@
from typing import Literal
import discord
from redbot.core.bot import Red
from redbot.core import commands, checks, Config
@@ -10,7 +12,21 @@ DEFAULT_ONLINE_EMOJI = "\N{WHITE HEAVY CHECK MARK}"
class Otherbot(commands.Cog):
__author__ = ["aikaterna", "Predä 。#1001"]
__version__ = "0.9"
__version__ = "0.10"
async def red_delete_data_for_user(
self, *, requester: Literal["discord", "owner", "user", "user_strict"], user_id: int,
):
if requester == "discord":
# user is deleted, just comply
data = await self.config.all_guilds()
for guild_id, guild_data in data.items():
if user_id in guild_data.get("watching", []):
bypass = guild_data.get("watching", [])
bypass = set(bypass)
bypass.discard(user_id)
await self.config.guild_from_id(guild_id).bypass.set(list(bypass))
def __init__(self, bot: Red):
self.bot = bot

View File

@@ -1,5 +1,7 @@
from .partycrash import PartyCrash
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(PartyCrash(bot))

View File

@@ -11,5 +11,6 @@
"tags": [
"invite"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -8,6 +8,10 @@ class PartyCrash(commands.Cog):
"""Partycrash inspired by v2 Admin by Will
Does not generate invites, only lists existing invites."""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
@@ -75,8 +79,6 @@ class PartyCrash(commands.Cog):
try:
await self._get_invites(guild, ctx)
except discord.errors.Forbidden:
return await ctx.send(
f"I don't have permission to get invites for {guild.name}."
)
return await ctx.send(f"I don't have permission to get invites for {guild.name}.")
except asyncio.TimeoutError:
return await ctx.send("No server number entered, try again later.")

View File

@@ -1,5 +1,7 @@
from .pingtime import Pingtime
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(Pingtime(bot))

View File

@@ -9,5 +9,6 @@
"pingtime",
"latency"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -3,8 +3,14 @@ from redbot.core import commands
BaseCog = getattr(commands, "Cog", object)
class Pingtime(BaseCog):
"""🏓"""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot

View File

@@ -1,4 +1,7 @@
from .pressf import PressF
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(PressF(bot))

View File

@@ -9,5 +9,6 @@
"pressf",
"respects"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -7,6 +7,10 @@ from redbot.core.utils.common_filters import filter_mass_mentions
class PressF(commands.Cog):
"""Pay some respects."""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.channels = {}
@@ -41,13 +45,13 @@ class PressF(commands.Cog):
f"Everyone, let's pay respects to **{filter_mass_mentions(answer)}**! Press the f reaction on the this message to pay respects."
)
await message.add_reaction("\U0001f1eb")
self.channels[str(ctx.channel.id)] = {'msg_id': message.id, 'reacted': []}
self.channels[str(ctx.channel.id)] = {"msg_id": message.id, "reacted": []}
await asyncio.sleep(120)
try:
await message.delete()
except (discord.errors.NotFound, discord.errors.Forbidden):
pass
amount = len(self.channels[str(ctx.channel.id)]['reacted'])
amount = len(self.channels[str(ctx.channel.id)]["reacted"])
word = "person has" if amount == 1 else "people have"
await ctx.send(f"**{amount}** {word} paid respects to **{filter_mass_mentions(answer)}**.")
del self.channels[str(ctx.channel.id)]
@@ -56,11 +60,11 @@ class PressF(commands.Cog):
async def on_reaction_add(self, reaction, user):
if str(reaction.message.channel.id) not in self.channels:
return
if self.channels[str(reaction.message.channel.id)]['msg_id'] != reaction.message.id:
if self.channels[str(reaction.message.channel.id)]["msg_id"] != reaction.message.id:
return
if user.id == self.bot.user.id:
return
if user.id not in self.channels[str(reaction.message.channel.id)]['reacted']:
if user.id not in self.channels[str(reaction.message.channel.id)]["reacted"]:
if str(reaction.emoji) == "\U0001f1eb":
await reaction.message.channel.send(f"**{user.name}** has paid their respects.")
self.channels[str(reaction.message.channel.id)]['reacted'].append(user.id)
self.channels[str(reaction.message.channel.id)]["reacted"].append(user.id)

View File

@@ -1,5 +1,7 @@
from .pupper import Pupper
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
async def setup(bot):
bot.add_cog(Pupper(bot))

View File

@@ -8,5 +8,6 @@
"tags": [
"pets"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -12,6 +12,10 @@ log = logging.getLogger("red.aikaterna.pupper")
class Pupper(commands.Cog):
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2767241393, force_registration=True)
@@ -47,9 +51,7 @@ class Pupper(commands.Cog):
space = "\N{EN SPACE}"
toggle = "Active" if guild_data["toggle"] else "Inactive"
delete_after = (
"No deletion" if not guild_data["delete_after"] else guild_data["delete_after"]
)
delete_after = "No deletion" if not guild_data["delete_after"] else guild_data["delete_after"]
msg = f"[Channels]: {humanize_list(channel_names)}\n"
msg += f"[Cooldown]: {guild_data['cooldown']} seconds\n"
@@ -169,9 +171,7 @@ class Pupper(commands.Cog):
async def addall(self, ctx):
"""Add all valid channels for the guild that the bot can speak in."""
bot_text_channels = [
c
for c in ctx.guild.text_channels
if c.permissions_for(ctx.guild.me).send_messages is True
c for c in ctx.guild.text_channels if c.permissions_for(ctx.guild.me).send_messages is True
]
channel_list = await self.config.guild(ctx.guild).channel()
channels_appended = []
@@ -189,13 +189,9 @@ class Pupper(commands.Cog):
second_msg = ""
await self.config.guild(ctx.guild).channel.set(channel_list)
if len(channels_appended) > 0:
first_msg = (
f"{humanize_list(channels_appended)} added to the valid petting channels.\n"
)
first_msg = f"{humanize_list(channels_appended)} added to the valid petting channels.\n"
if len(channels_in_list) > 0:
second_msg = (
f"{humanize_list(channels_in_list)}: already in the list of petting channels."
)
second_msg = f"{humanize_list(channels_in_list)}: already in the list of petting channels."
await ctx.send(f"{first_msg}{second_msg}")
@channel.command()
@@ -237,15 +233,10 @@ class Pupper(commands.Cog):
if self.pets[message.guild.id]:
return
last_time = datetime.datetime.strptime(
str(guild_data["last_pet"]), "%Y-%m-%d %H:%M:%S.%f"
)
last_time = datetime.datetime.strptime(str(guild_data["last_pet"]), "%Y-%m-%d %H:%M:%S.%f")
now = datetime.datetime.now(datetime.timezone.utc)
now = now.replace(tzinfo=None)
if (
int((now - last_time).total_seconds())
> await self.config.guild(message.guild).cooldown()
):
if int((now - last_time).total_seconds()) > await self.config.guild(message.guild).cooldown():
self._pet_lock(message.guild.id, True)
rando_channel = random.choice(guild_data["channel"])
await asyncio.sleep(random.randint(60, 480))
@@ -279,9 +270,7 @@ class Pupper(commands.Cog):
if not large_bank
else guild_data["borf_msg"]
)
await rando_channel_obj.send(
content=msg, delete_after=guild_data["delete_after"]
)
await rando_channel_obj.send(content=msg, delete_after=guild_data["delete_after"])
else:
pass
self._pet_lock(message.guild.id, False)

View File

@@ -1,4 +1,7 @@
from .quiz import Quiz
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(Quiz(bot))

View File

@@ -4,5 +4,6 @@
"install_msg": "Thanks for installing.",
"short": "Play a kahoot-like trivia game.",
"tags": ["trivia", "quiz"],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -49,6 +49,10 @@ class Quiz(commands.Cog):
Originally by Keane for Red v2
"""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
@@ -102,9 +106,7 @@ class Quiz(commands.Cog):
elif category_name_or_id.isdigit():
# cat id specified
if 9 <= int(category_name_or_id) >= 32:
return await ctx.send(
f"Invalid category number. Use `{ctx.prefix}quiz categories` to see a list."
)
return await ctx.send(f"Invalid category number. Use `{ctx.prefix}quiz categories` to see a list.")
category_id = category_name_or_id
category_name = await self.category_name_from_id(int(category_name_or_id))
else:
@@ -112,9 +114,7 @@ class Quiz(commands.Cog):
try:
category_name = await self.category_name_match(category_name_or_id)
except RuntimeError:
return await ctx.send(
f"Invalid category name. Use `{ctx.prefix}quiz categories` to see a list."
)
return await ctx.send(f"Invalid category name. Use `{ctx.prefix}quiz categories` to see a list.")
category_id = await self.category_id_from_name(category_name)
if channel.id not in self.playing_channels:
@@ -221,9 +221,7 @@ class Quiz(commands.Cog):
while True:
for channelid in list(self.playing_channels):
channelinfo = self.playing_channels[channelid]
since_start = (
datetime.datetime.utcnow() - channelinfo["Start"]
).total_seconds()
since_start = (datetime.datetime.utcnow() - channelinfo["Start"]).total_seconds()
if since_start > 30 and not channelinfo["Started"]:
channel = self.bot.get_channel(channelid)
@@ -318,9 +316,7 @@ class Quiz(commands.Cog):
assert answerdict[correct_letter] == html.unescape(dictionary["correct_answer"])
if await self.config.guild(channel.guild).show_answer():
message = (
f"Correct answer:```{correct_letter.upper()}. {answerdict[correct_letter]}```"
)
message = f"Correct answer:```{correct_letter.upper()}. {answerdict[correct_letter]}```"
await channel.send(message)
# Assign scores
@@ -350,11 +346,7 @@ class Quiz(commands.Cog):
"""Ends a quiz game."""
# non-linear credit earning .0002x^{2.9} where x is score/100
channelinfo = self.playing_channels[channel.id]
idlist = sorted(
list(channelinfo["Players"]),
key=(lambda idnum: channelinfo["Players"][idnum]),
reverse=True,
)
idlist = sorted(list(channelinfo["Players"]), key=(lambda idnum: channelinfo["Players"][idnum]), reverse=True,)
winner = channel.guild.get_member(idlist[0])
await channel.send(f"Game over! {winner.mention} won!")
@@ -400,11 +392,7 @@ class Quiz(commands.Cog):
"""Returns a scoreboard string to be sent to the text channel."""
channelinfo = self.playing_channels[channel.id]
scoreboard = "\n"
idlist = sorted(
list(channelinfo["Players"]),
key=(lambda idnum: channelinfo["Players"][idnum]),
reverse=True,
)
idlist = sorted(list(channelinfo["Players"]), key=(lambda idnum: channelinfo["Players"][idnum]), reverse=True,)
max_score = channelinfo["Players"][idlist[0]]
end_len = len(str(max_score)) + 1
rank = 1
@@ -457,21 +445,15 @@ class Quiz(commands.Cog):
parameters["difficulty"] = difficulty
for _ in range(3):
parameters["token"] = await self.get_token(server)
async with self.session.get(
"https://opentdb.com/api.php", params=parameters
) as response:
async with self.session.get("https://opentdb.com/api.php", params=parameters) as response:
response_json = await response.json()
response_code = response_json["response_code"]
if response_code == 0:
return response_json
elif response_code == 1:
raise RuntimeError(
"Question retrieval unsuccessful. Response code from OTDB: 1"
)
raise RuntimeError("Question retrieval unsuccessful. Response code from OTDB: 1")
elif response_code == 2:
raise RuntimeError(
"Question retrieval unsuccessful. Response code from OTDB: 2"
)
raise RuntimeError("Question retrieval unsuccessful. Response code from OTDB: 2")
elif response_code == 3:
# Token expired. Obtain new one.
log.debug("Quiz: Response code from OTDB: 3")
@@ -487,9 +469,7 @@ class Quiz(commands.Cog):
and saves one if one doesn't exist."""
token = await self.config.guild(server).token()
if not token:
async with self.session.get(
"https://opentdb.com/api_token.php", params={"command": "request"}
) as response:
async with self.session.get("https://opentdb.com/api_token.php", params={"command": "request"}) as response:
response_json = await response.json()
token = response_json["token"]
await self.config.guild(server).token.set(token)
@@ -503,17 +483,13 @@ class Quiz(commands.Cog):
) as response:
response_code = (await response.json())["response_code"]
if response_code != 0:
raise RuntimeError(
f"Token reset was unsuccessful. Response code from OTDB: {response_code}"
)
raise RuntimeError(f"Token reset was unsuccessful. Response code from OTDB: {response_code}")
async def category_selector(self):
"""Chooses a random category that has enough questions."""
for _ in range(10):
category = random.randint(9, 32)
async with self.session.get(
"https://opentdb.com/api_count.php", params={"category": category}
) as response:
async with self.session.get("https://opentdb.com/api_count.php", params={"category": category}) as response:
response_json = await response.json()
assert response_json["category_id"] == category
if response_json["category_question_count"]["total_question_count"] > 39:

View File

@@ -1,5 +1,7 @@
from .region import Region
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(Region())

View File

@@ -5,5 +5,6 @@
"description" : "Change the Discord server's region with a command.",
"install_msg" : "Thanks for installing.",
"tags" : ["voice region", "region"],
"disabled" : false
"disabled" : false,
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -6,6 +6,10 @@ from redbot.core.utils.chat_formatting import humanize_list
class Region(commands.Cog):
"""Change the guild voice region."""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
@checks.mod_or_permissions(administrator=True)
@commands.cooldown(1, 60, discord.ext.commands.BucketType.guild)
@commands.command()
@@ -44,6 +48,4 @@ class Region(commands.Cog):
return await ctx.send("I don't have permissions to edit this guild's settings.")
except discord.errors.HTTPException:
return await ctx.send(f"Error: An invalid server region was passed: `{region}`")
await ctx.send(
f"The voice server region for `{ctx.guild.name}` has been changed to `{region}`."
)
await ctx.send(f"The voice server region for `{ctx.guild.name}` has been changed to `{region}`.")

View File

@@ -1,5 +1,7 @@
from .retrosign import Retrosign
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(Retrosign(bot))

View File

@@ -15,5 +15,6 @@
"retro",
"80s"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -6,7 +6,6 @@ from bs4 import BeautifulSoup as bs
import discord
from redbot.core import commands
from io import BytesIO
import os
from random import choice
import re
import unicodedata
@@ -14,6 +13,11 @@ import unicodedata
class Retrosign(commands.Cog):
"""Make an 80s retro sign. Originally by Anismash"""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.session = aiohttp.ClientSession()
@@ -26,47 +30,27 @@ class Retrosign(commands.Cog):
if len(texts) == 1:
lenstr = len(texts[0])
if lenstr <= 15:
data = dict(
bcg=choice([1, 2, 3, 4, 5]),
txt=choice([1, 2, 3, 4]),
text1="",
text2=texts[0],
text3="",
)
data = dict(bcg=choice([1, 2, 3, 4, 5]), txt=choice([1, 2, 3, 4]), text1="", text2=texts[0], text3="",)
else:
return await ctx.send("\N{CROSS MARK} Your line is too long (14 character limit)")
elif len(texts) == 3:
texts[0] = unicodedata.normalize('NFD', texts[0]).encode('ascii', 'ignore')
texts[0] = texts[0].decode('UTF-8')
texts[0] = re.sub(r'[^A-Za-z0-9 ]', '', texts[0])
texts[0] = unicodedata.normalize("NFD", texts[0]).encode("ascii", "ignore")
texts[0] = texts[0].decode("UTF-8")
texts[0] = re.sub(r"[^A-Za-z0-9 ]", "", texts[0])
if len(texts[0]) >= 15:
return await ctx.send(
"\N{CROSS MARK} Your first line is too long (14 character limit)"
)
return await ctx.send("\N{CROSS MARK} Your first line is too long (14 character limit)")
if len(texts[1]) >= 13:
return await ctx.send(
"\N{CROSS MARK} Your second line is too long (12 character limit)"
)
return await ctx.send("\N{CROSS MARK} Your second line is too long (12 character limit)")
if len(texts[2]) >= 26:
return await ctx.send(
"\N{CROSS MARK} Your third line is too long (25 character limit)"
)
return await ctx.send("\N{CROSS MARK} Your third line is too long (25 character limit)")
data = dict(
bcg=choice([1, 2, 3, 4, 5]),
txt=choice([1, 2, 3, 4]),
text1=texts[0],
text2=texts[1],
text3=texts[2],
bcg=choice([1, 2, 3, 4, 5]), txt=choice([1, 2, 3, 4]), text1=texts[0], text2=texts[1], text3=texts[2],
)
else:
return await ctx.send(
"\N{CROSS MARK} please provide three words seperated by ';' or one word"
)
return await ctx.send("\N{CROSS MARK} please provide three words seperated by ';' or one word")
async with ctx.channel.typing():
async with self.session.post(
"https://photofunia.com/effects/retro-wave", data=data
) as response:
async with self.session.post("https://photofunia.com/effects/retro-wave", data=data) as response:
if response.status == 200:
soup = bs(await response.text(), "html.parser")
download_url = soup.find("div", class_="downloads-container").ul.li.a["href"]

View File

@@ -1,5 +1,7 @@
from .rndstatus import RndStatus
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(RndStatus(bot))

View File

@@ -9,5 +9,6 @@
"tags": [
"status"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -3,14 +3,13 @@ import discord
from redbot.core import Config, commands, checks
from redbot.core.utils import AsyncIter
from random import choice as rndchoice
from collections import defaultdict, Counter, Sequence
import time
from collections import defaultdict
import contextlib
import asyncio
import logging
log = logging.getLogger("red.aikaterna-cogs.rndstatus")
log = logging.getLogger("red.aikaterna.rndstatus")
class RndStatus(commands.Cog):
@@ -18,49 +17,30 @@ class RndStatus(commands.Cog):
If a custom status is already set, it won't change it until
it's back to none. [p]set game"""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.last_change = None
self.config = Config.get_conf(self, 2752521001, force_registration=True)
self._user_count = 0
self.user_task = asyncio.create_task(self._get_user_count())
self.presence_task = asyncio.create_task(self.maybe_update_presence())
default_global = {
"botstats": False,
"delay": 300,
"statuses": [
"her Turn()",
"Tomb Raider II",
"Transistor",
"NEO Scavenger",
"Python",
"with your heart.",
],
"statuses": ["her Turn()", "Tomb Raider II", "Transistor", "NEO Scavenger", "Python", "with your heart.",],
"streamer": "rndstatusstreamer",
"type": 1,
"type": 0,
"status": 0,
}
self.config.register_global(**default_global)
def cog_unload(self):
self.user_task.cancel()
self.presence_task.cancel()
async def _get_user_count(self):
await self.bot.wait_until_ready()
with contextlib.suppress(asyncio.CancelledError):
self._user_count = len(self.bot.users)
while True:
temp_data = defaultdict(set)
async for s in AsyncIter(self.bot.guilds):
if s.unavailable:
continue
async for m in AsyncIter(s.members):
temp_data["Unique Users"].add(m.id)
self._user_count = len(temp_data["Unique Users"])
await asyncio.sleep(30)
@commands.group(autohelp=True)
@commands.guild_only()
@checks.is_owner()
@@ -78,24 +58,23 @@ class RndStatus(commands.Cog):
Shows current list if empty."""
saved_status = await self.config.statuses()
if statuses == () or "" in statuses:
return await ctx.send("Current statuses: " + " | ".join(saved_status))
msg = (
f"Current statuses: {(' | ').join(saved_status)}\n"
f"To set new statuses, use the instructions in `{ctx.prefix}help rndstatus set`."
)
return await ctx.send(msg)
await self.config.statuses.set(list(statuses))
await ctx.send(
"Done. Redo this command with no parameters to see the current list of statuses."
)
await self.presence_updater()
await ctx.send("Done. Redo this command with no parameters to see the current list of statuses.")
@rndstatus.command(name="streamer")
async def _streamer(self, ctx: commands.Context, *, streamer=None):
"""Set the streamer needed for streaming statuses.
"""
"""Set the streamer name needed for streaming statuses."""
saved_streamer = await self.config.streamer()
if streamer is None:
return await ctx.send(f"Current Streamer: {saved_streamer}" )
return await ctx.send(f"Current Streamer: {saved_streamer}")
await self.config.streamer.set(streamer)
await ctx.send(
"Done. Redo this command with no parameters to see the current streamer."
)
await ctx.send("Done. Redo this command with no parameters to see the current streamer.")
@rndstatus.command()
async def botstats(self, ctx, *statuses: str):
@@ -103,8 +82,7 @@ class RndStatus(commands.Cog):
botstats = await self.config.botstats()
await self.config.botstats.set(not botstats)
await ctx.send(f"Botstats toggle: {not botstats}.")
if botstats is not False:
await self.bot.change_presence(activity=None)
await self.presence_updater()
@rndstatus.command()
async def delay(self, ctx, seconds: int):
@@ -115,77 +93,106 @@ class RndStatus(commands.Cog):
await self.config.delay.set(seconds)
await ctx.send(f"Interval set to {seconds} seconds.")
@rndstatus.command()
async def type(self, ctx, type: int):
"""Define the rndstatus type.
@rndstatus.command(name="type")
async def _rndstatus_type(self, ctx, status_type: int):
"""Define the rndstatus game type.
Type list:
0 = Playing
1 = Streaming
2 = Listening
3 = Watching"""
if 0 <= type <= 3:
await self.config.type.set(type)
await ctx.send("Rndstatus type set.")
if 0 <= status_type <= 3:
rnd_type = {0: "playing", 1: "streaming", 2: "listening", 3: "watching"}
await self.config.type.set(status_type)
await self.presence_updater()
await ctx.send(f"Rndstatus activity type set to {rnd_type[status_type]}.")
else:
await ctx.send("Type must be between 0 and 3.")
await ctx.send(
f"Status activity type must be between 0 and 3. "
f"See `{ctx.prefix}help rndstatus type` for more information."
)
@rndstatus.command()
async def status(self, ctx, status: int):
"""Define the rndstatus presence status.
Status list:
0 = Online
1 = Idle
2 = DND
3 = Invisible"""
if 0 <= status <= 3:
rnd_status = {0: "online", 1: "idle", 2: "DND", 3: "invisible"}
await self.config.status.set(status)
await self.presence_updater()
await ctx.send(f"Rndstatus presence status set to {rnd_status[status]}.")
else:
await ctx.send(
f"Status presence type must be between 0 and 3. "
f"See `{ctx.prefix}help rndstatus status` for more information."
)
async def maybe_update_presence(self):
await self.bot.wait_until_ready()
pattern = re.compile(rf"<@!?{self.bot.user.id}>")
delay = 0
await self.bot.wait_until_red_ready()
delay = await self.config.delay()
while True:
try:
cog_settings = await self.config.all()
guilds = self.bot.guilds
guild = next(g for g in guilds if not g.unavailable)
try:
current_game = str(guild.me.activity.name)
except AttributeError:
current_game = None
statuses = cog_settings["statuses"]
botstats = cog_settings["botstats"]
streamer = cog_settings["streamer"]
_type = cog_settings["type"]
delay = cog_settings["delay"]
url = f"https://www.twitch.tv/{streamer}"
prefix = await self.bot.get_valid_prefixes()
if botstats:
me = self.bot.user
clean_prefix = pattern.sub(f"@{me.name}", prefix[0])
total_users = self._user_count
servers = str(len(self.bot.guilds))
botstatus = f"{clean_prefix}help | {total_users} users | {servers} servers"
if (current_game != str(botstatus)) or current_game is None:
if _type == 1:
await self.bot.change_presence(
activity=discord.Streaming(name=botstatus, url=url)
)
else:
await self.bot.change_presence(
activity=discord.Activity(name=botstatus, type=_type)
)
else:
if len(statuses) > 0:
new_status = self.random_status(guild, statuses)
if current_game != new_status:
if (current_game != new_status) or current_game is None:
if _type == 1:
await self.bot.change_presence(
activity=discord.Streaming(name=new_status, url=url)
)
else:
await self.bot.change_presence(
activity=discord.Activity(name=new_status, type=_type)
)
await self.presence_updater()
await asyncio.sleep(int(delay))
except asyncio.CancelledError:
break
except Exception as e:
log.exception(e, exc_info=e)
await asyncio.sleep(int(delay))
async def presence_updater(self):
pattern = re.compile(rf"<@!?{self.bot.user.id}>")
cog_settings = await self.config.all()
guilds = self.bot.guilds
guild = next(g for g in guilds if not g.unavailable)
try:
current_game = str(guild.me.activity.name)
except AttributeError:
current_game = None
statuses = cog_settings["statuses"]
botstats = cog_settings["botstats"]
streamer = cog_settings["streamer"]
_type = cog_settings["type"]
_status = cog_settings["status"]
url = f"https://www.twitch.tv/{streamer}"
prefix = await self.bot.get_valid_prefixes()
if _status == 0:
status = discord.Status.online
elif _status == 1:
status = discord.Status.idle
elif _status == 2:
status = discord.Status.dnd
elif _status == 3:
status = discord.Status.offline
if botstats:
me = self.bot.user
clean_prefix = pattern.sub(f"@{me.name}", prefix[0])
total_users = len(self.bot.users)
servers = str(len(self.bot.guilds))
botstatus = f"{clean_prefix}help | {total_users} users | {servers} servers"
if (current_game != str(botstatus)) or current_game is None:
if _type == 1:
await self.bot.change_presence(activity=discord.Streaming(name=botstatus, url=url))
else:
await self.bot.change_presence(activity=discord.Activity(name=botstatus, type=_type), status=status)
else:
if len(statuses) > 0:
new_status = self.random_status(guild, statuses)
if (current_game != new_status) or (current_game is None) or (len(statuses) == 1):
if _type == 1:
await self.bot.change_presence(activity=discord.Streaming(name=new_status, url=url))
else:
await self.bot.change_presence(
activity=discord.Activity(name=new_status, type=_type), status=status
)
def random_status(self, guild, statuses):
try:

9
rss/__init__.py Normal file
View File

@@ -0,0 +1,9 @@
from redbot.core import commands
from .rss import RSS
async def setup(bot: commands.Bot):
n = RSS(bot)
bot.add_cog(n)
n.initialize()

77
rss/color.py Normal file
View File

@@ -0,0 +1,77 @@
import discord
import re
from scipy.spatial import KDTree
import webcolors
class Color:
"""Helper for color handling."""
async def _color_converter(self, hex_code_or_color_word: str):
"""
Used for user input on rss embed color
Input: discord.Color name, CSS3 color name, 0xFFFFFF, #FFFFFF, FFFFFF
Output: 0xFFFFFF
"""
# #FFFFFF and FFFFFF to 0xFFFFFF
hex_match = re.match(r"#?[a-f0-9]{6}", hex_code_or_color_word.lower())
if hex_match:
hex_code = f"0x{hex_code_or_color_word.lstrip('#')}"
return hex_code
# discord.Color checking
if hasattr(discord.Color, hex_code_or_color_word):
hex_code = str(getattr(discord.Color, hex_code_or_color_word)())
hex_code = hex_code.replace("#", "0x")
return hex_code
# CSS3 color name checking
try:
hex_code = webcolors.name_to_hex(hex_code_or_color_word, spec="css3")
hex_code = hex_code.replace("#", "0x")
return hex_code
except ValueError:
pass
return None
async def _hex_to_css3_name(self, hex_code: str):
"""
Input: 0xFFFFFF
Output: CSS3 color name string closest match
"""
hex_code = await self._hex_validator(hex_code)
rgb_tuple = await self._hex_to_rgb(hex_code)
names = []
positions = []
for hex, name in webcolors.css3_hex_to_names.items():
names.append(name)
positions.append(webcolors.hex_to_rgb(hex))
spacedb = KDTree(positions)
dist, index = spacedb.query(rgb_tuple)
return names[index]
async def _hex_to_rgb(self, hex_code: str):
"""
Input: 0xFFFFFF
Output: (255, 255, 255)
"""
return webcolors.hex_to_rgb(hex_code)
async def _hex_validator(self, hex_code: str):
"""
Input: 0xFFFFFF
Output: #FFFFFF or None
"""
if hex_code[:2] == "0x":
hex_code = hex_code.replace("0x", "#")
try:
# just a check to make sure it's a real color hex code
hex_code = webcolors.normalize_hex(hex_code)
except ValueError:
hex_code = None
return hex_code

10
rss/info.json Normal file
View File

@@ -0,0 +1,10 @@
{
"author": ["aikaterna"],
"install_msg": "Thanks for installing.",
"short": "Read RSS feeds",
"description": "Read RSS feeds",
"tags": ["rss"],
"permissions": ["embed_links"],
"requirements": ["bs4", "feedparser>=6.0.0", "scipy", "webcolors==1.3"],
"min_bot_version" : "3.4.0"
}

31
rss/quiet_template.py Normal file
View File

@@ -0,0 +1,31 @@
from collections import ChainMap
from string import Template
class QuietTemplate(Template):
"""
A subclass of string.Template that is less verbose on a missing key
https://github.com/python/cpython/blob/919f0bc8c904d3aa13eedb2dd1fe9c6b0555a591/Lib/string.py#L123
"""
def quiet_safe_substitute(self, mapping={}, /, **kws):
if mapping is {}:
mapping = kws
elif kws:
mapping = ChainMap(kws, mapping)
# Helper function for .sub()
def convert(mo):
named = mo.group('named') or mo.group('braced')
if named is not None:
try:
return str(mapping[named])
except KeyError:
# return None instead of the tag name so that
# invalid tags are not present in the feed output
return None
if mo.group('escaped') is not None:
return self.delimiter
if mo.group('invalid') is not None:
return mo.group()
raise ValueError('Unrecognized named group in pattern', self.pattern)
return self.pattern.sub(convert, self.template)

1107
rss/rss.py Normal file

File diff suppressed because it is too large Load Diff

50
rss/rss_feed.py Normal file
View File

@@ -0,0 +1,50 @@
class RssFeed():
"""RSS feed object"""
def __init__(self, **kwargs):
super().__init__()
self.name: str = kwargs.get("name", None)
self.last_title: str = kwargs.get("last_title", None)
self.last_link: str = kwargs.get("last_link", None)
self.last_time: str = kwargs.get("last_time", None)
self.template: str = kwargs.get("template", None)
self.url: str = kwargs.get("url", None)
self.template_tags: List[str] = kwargs.get("template_tags", [])
self.is_special: List[str] = kwargs.get("is_special", [])
self.embed: bool = kwargs.get("embed", True)
self.embed_color: str = kwargs.get("embed_color", None)
self.embed_image: str = kwargs.get("embed_image", None)
self.embed_thumbnail: str = kwargs.get("embed_thumbnail", None)
def to_json(self) -> dict:
return {
"name": self.name,
"last_title": self.last_title,
"last_link": self.last_link,
"last_time": self.last_time,
"template": self.template,
"url": self.url,
"template_tags": self.template_tags,
"is_special": self.is_special,
"embed": self.embed,
"embed_color": self.embed_color,
"embed_image": self.embed_image,
"embed_thumbnail": self.embed_thumbnail,
}
@classmethod
def from_json(cls, data: dict):
return cls(
name=data["name"] if data["name"] else None,
last_title=data["last_title"] if data["last_title"] else None,
last_link=data["last_link"] if data["last_link"] else None,
last_time=data["last_time"] if data["last_time"] else None,
template=data["template"] if data["template"] else None,
url=data["url"] if data["url"] else None,
template_tags=data["template_tags"] if data["template_tags"] else [],
is_special=data["is_special"] if data["is_special"] else [],
embed=data["embed"] if data["embed"] else True,
embed_color=data["embed_color"] if data["embed_color"] else None,
embed_image=data["embed_image"] if data["embed_image"] else None,
embed_thumbnail=data["embed_thumbnail"] if data["embed_thumbnail"] else None,
)

13
rss/tag_type.py Normal file
View File

@@ -0,0 +1,13 @@
from enum import Enum
INTERNAL_TAGS = ["is_special", "template_tags", "embed", "embed_color", "embed_image", "embed_thumbnail"]
VALID_IMAGES = ["png", "webp", "gif", "jpeg", "jpg"]
class TagType(Enum):
PLAINTEXT = 1
HTML = 2
DICT = 3
LIST = 4

View File

@@ -1,5 +1,10 @@
from .seen import Seen
__red_end_user_data_statement__ = (
"This cog does not persistently store end user data. "
"This cog does store discord IDs and last seen timestamp as needed for operation. "
)
async def setup(bot):
cog = Seen(bot)

View File

@@ -8,5 +8,6 @@
"seen",
"activity"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store end user data. This cog does store discord IDs and last seen timestamp as needed for operation. "
}

View File

@@ -1,7 +1,7 @@
import asyncio
import contextlib
import datetime
from typing import Union
from typing import Union, Literal
import discord
import time
@@ -14,6 +14,15 @@ _SCHEMA_VERSION = 2
class Seen(commands.Cog):
"""Shows last time a user was seen in chat."""
async def red_delete_data_for_user(
self, *, requester: Literal["discord", "owner", "user", "user_strict"], user_id: int,
):
if requester in ["discord", "owner"]:
data = await self.config.all_members()
for guild_id, members in data.items():
if user_id in members:
await self.config.member_from_ids(guild_id, user_id).clear()
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2784481001, force_registration=True)
@@ -29,9 +38,7 @@ class Seen(commands.Cog):
async def initialize(self):
asyncio.ensure_future(
self._migrate_config(
from_version=await self.config.schema_version(), to_version=_SCHEMA_VERSION
)
self._migrate_config(from_version=await self.config.schema_version(), to_version=_SCHEMA_VERSION)
)
async def _migrate_config(self, from_version: int, to_version: int):
@@ -75,9 +82,7 @@ class Seen(commands.Cog):
member_seen_cache = self._cache.get(author.guild.id, {}).get(author.id, None)
if not member_seen_cache and not member_seen_config:
embed = discord.Embed(
colour=discord.Color.red(), title="I haven't seen that user yet."
)
embed = discord.Embed(colour=discord.Color.red(), title="I haven't seen that user yet.")
return await ctx.send(embed=embed)
if not member_seen_cache:
@@ -132,10 +137,7 @@ class Seen(commands.Cog):
@commands.Cog.listener()
async def on_typing(
self,
channel: discord.abc.Messageable,
user: Union[discord.User, discord.Member],
when: datetime.datetime,
self, channel: discord.abc.Messageable, user: Union[discord.User, discord.Member], when: datetime.datetime,
):
if getattr(user, "guild", None):
if user.guild.id not in self._cache:
@@ -150,18 +152,14 @@ class Seen(commands.Cog):
self._cache[after.guild.id][after.author.id] = int(time.time())
@commands.Cog.listener()
async def on_reaction_remove(
self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]
):
async def on_reaction_remove(self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]):
if getattr(user, "guild", None):
if user.guild.id not in self._cache:
self._cache[user.guild.id] = {}
self._cache[user.guild.id][user.id] = int(time.time())
@commands.Cog.listener()
async def on_reaction_add(
self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]
):
async def on_reaction_add(self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]):
if getattr(user, "guild", None):
if user.guild.id not in self._cache:
self._cache[user.guild.id] = {}

View File

@@ -1,5 +1,7 @@
from .snacktime import Snacktime
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
async def setup(bot):
bot.add_cog(Snacktime(bot))

View File

@@ -5,5 +5,6 @@
"description" : "snackburr will come around every-so-often if you've asked him to.\nI hear snackburr likes to come around more often when people are partyin.",
"install_msg" : "A snack delivery bear has arrived ʕ•ᴥ• ʔ",
"tags" : ["snack", "snacktime", "snackburr", "party", "party time"],
"disabled" : false
"disabled" : false,
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -124,8 +124,6 @@ SNACKBURR_PHRASES = {
"partakes of",
"ingests",
],
"ENABLE": [
"Oh you guys want snacks?! Aight, I'll come around every so often to hand some out!"
],
"ENABLE": ["Oh you guys want snacks?! Aight, I'll come around every so often to hand some out!"],
"DISABLE": ["You guys don't want snacks anymore? Alright, I'll stop comin around."],
}

View File

@@ -17,6 +17,10 @@ log = logging.getLogger("red.aikaterna.snacktime")
class Snacktime(commands.Cog):
"""Snackburr's passing out pb jars!"""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2712291001, force_registration=True)
@@ -93,9 +97,7 @@ class Snacktime(commands.Cog):
first_phrase = randchoice(SNACKBURR_PHRASES["EAT_BEFORE"])
second_phrase = randchoice(SNACKBURR_PHRASES["EAT_AFTER"])
await ctx.send(
f"`{persona} {ctx.author.display_name} {first_phrase} {second_phrase} {amount} whole pb jars!`"
)
await ctx.send(f"`{persona} {ctx.author.display_name} {first_phrase} {second_phrase} {amount} whole pb jars!`")
@commands.guild_only()
@commands.group()
@@ -121,9 +123,7 @@ class Snacktime(commands.Cog):
msg = f"[Delivering in]: {humanize_list(channel_names)}\n"
msg += f"[Event start delay]: {guild_data['EVENT_START_DELAY']} seconds\n"
msg += (
f"[Event start variance]: {guild_data['EVENT_START_DELAY_VARIANCE']} seconds\n"
)
msg += f"[Event start variance]: {guild_data['EVENT_START_DELAY_VARIANCE']} seconds\n"
msg += f"[Friends status]: {invite_friends}\n"
msg += f"[Messages before event]: {guild_data['MSGS_BEFORE_EVENT']}\n"
msg += f"[Snack amount limit]: {guild_data['SNACK_AMOUNT']} pb\n"
@@ -136,18 +136,14 @@ class Snacktime(commands.Cog):
@snackset.command()
async def errandtime(self, ctx, seconds: int):
"""How long snackburr needs to be out doin errands.. more or less."""
event_start_delay_variance = await self.config.guild(
ctx.guild
).EVENT_START_DELAY_VARIANCE()
event_start_delay_variance = await self.config.guild(ctx.guild).EVENT_START_DELAY_VARIANCE()
if seconds <= event_start_delay_variance:
await ctx.send("errandtime must be greater than errandvariance!")
elif seconds <= 0:
await ctx.send("errandtime must be greater than 0")
else:
await self.config.guild(ctx.guild).EVENT_START_DELAY.set(seconds)
await ctx.send(
f"snackburr's errands will now take around {round(seconds/60, 2)} minutes!"
)
await ctx.send(f"snackburr's errands will now take around {round(seconds/60, 2)} minutes!")
@snackset.command()
async def errandvariance(self, ctx, seconds: int):
@@ -159,9 +155,7 @@ class Snacktime(commands.Cog):
await ctx.send("errandvariance must be 0 or greater!")
else:
await self.config.guild(ctx.guild).EVENT_START_DELAY_VARIANCE.set(seconds)
await ctx.send(
f"snackburr now might be {round(seconds/60, 2)} minutes early or late to snacktime"
)
await ctx.send(f"snackburr now might be {round(seconds/60, 2)} minutes early or late to snacktime")
@snackset.command(name="snacktime")
async def snacktimetime(self, ctx, seconds: int):
@@ -185,9 +179,7 @@ class Snacktime(commands.Cog):
await ctx.send("snackvariance must be 0 or greater!")
else:
await self.config.guild(ctx.guild).SNACK_DURATION_VARIANCE.set(seconds)
await ctx.send(
f"snackburr now may have to leave snacktime {round(seconds/60, 2)} minutes early or late"
)
await ctx.send(f"snackburr now may have to leave snacktime {round(seconds/60, 2)} minutes early or late")
@snackset.command()
async def msgsneeded(self, ctx, amt: int):
@@ -196,9 +188,7 @@ class Snacktime(commands.Cog):
await ctx.send("msgsneeded must be greater than 0")
else:
await self.config.guild(ctx.guild).MSGS_BEFORE_EVENT.set(amt)
await ctx.send(
f"snackburr will now wait until {amt} messages pass until he comes with snacks"
)
await ctx.send(f"snackburr will now wait until {amt} messages pass until he comes with snacks")
@snackset.command()
async def amount(self, ctx, amt: int):
@@ -272,13 +262,9 @@ class Snacktime(commands.Cog):
return
self.snacktimeCheckLock[scid] = True
if seconds < 0:
await ctx.send(
f"I'm not sure where snackburr is.. He's already {round(abs(seconds/60), 2)} minutes late!"
)
await ctx.send(f"I'm not sure where snackburr is.. He's already {round(abs(seconds/60), 2)} minutes late!")
else:
await ctx.send(
f"snackburr's out on errands! I think he'll be back in {round(seconds/60, 2)} minutes"
)
await ctx.send(f"snackburr's out on errands! I think he'll be back in {round(seconds/60, 2)} minutes")
await asyncio.sleep(40)
self.snacktimeCheckLock[scid] = False
@@ -306,20 +292,14 @@ class Snacktime(commands.Cog):
await self.config.channel(message.channel).repeatMissedSnacktimes.set(0)
else:
await message.channel.send(await self.get_response(message, "NO_TAKERS"))
repeat_missed_snacktimes = await self.config.channel(
message.channel
).repeatMissedSnacktimes()
await self.config.channel(message.channel).repeatMissedSnacktimes.set(
repeat_missed_snacktimes + 1
)
repeat_missed_snacktimes = await self.config.channel(message.channel).repeatMissedSnacktimes()
await self.config.channel(message.channel).repeatMissedSnacktimes.set(repeat_missed_snacktimes + 1)
await asyncio.sleep(2)
if (repeat_missed_snacktimes + 1) > 9: # move to a setting
await message.channel.send(await self.get_response(message, "LONELY"))
deliver_channels = await self.config.guild(message.guild).DELIVER_CHANNELS()
new_deliver_channels = deliver_channels.remove(message.channel.id)
await self.config.guild(message.guild).DELIVER_CHANNELS.set(
new_deliver_channels
)
await self.config.guild(message.guild).DELIVER_CHANNELS.set(new_deliver_channels)
await self.config.channel(message.channel).repeatMissedSnacktimes.set(0)
except:
log.error("Snacktime: Failed to send message in startSnack")
@@ -365,9 +345,7 @@ class Snacktime(commands.Cog):
# start snacktime
await self.startSnack(message)
# if no snack coming, schedule one
elif self.snackInProgress.get(scid, False) == False and not self.startLock.get(
scid, False
):
elif self.snackInProgress.get(scid, False) == False and not self.startLock.get(scid, False):
self.msgsPassed[scid] = self.msgsPassed.get(scid, 0) + 1
# check for collisions
msgs_before_event = await self.config.guild(message.guild).MSGS_BEFORE_EVENT()
@@ -385,9 +363,7 @@ class Snacktime(commands.Cog):
log.debug(f"Snacktime: {message.author.name} - I got the Lock")
self.lockRequests[scid] = []
# someone got through already
if self.msgsPassed[
scid
] < msgs_before_event or self.snackInProgress.get(scid, False):
if self.msgsPassed[scid] < msgs_before_event or self.snackInProgress.get(scid, False):
log.debug("Snacktime: Lock: someone got through already.")
return
else:
@@ -404,8 +380,7 @@ class Snacktime(commands.Cog):
log.debug(f"Snacktime: activity: {message.content}")
guild_data = await self.config.guild(message.guild).all()
timeTillSnack = guild_data["EVENT_START_DELAY"] + randint(
-guild_data["EVENT_START_DELAY_VARIANCE"],
guild_data["EVENT_START_DELAY_VARIANCE"],
-guild_data["EVENT_START_DELAY_VARIANCE"], guild_data["EVENT_START_DELAY_VARIANCE"],
)
log.debug(f"Snacktime: {str(timeTillSnack)} seconds till snacktime")
self.snacktimePrediction[scid] = msgTime + guild_data["EVENT_START_DELAY"]
@@ -450,10 +425,7 @@ class Snacktime(commands.Cog):
userWants = False
for agreePhrase in agree_phrases:
# no one word answers
if (
agreePhrase in message.content.lower()
and len(message.content.split()) > 1
):
if agreePhrase in message.content.lower() and len(message.content.split()) > 1:
userWants = True
break
if userWants:
@@ -478,8 +450,7 @@ class Snacktime(commands.Cog):
await bank.set_balance(message.author, b.max_balance)
except Exception as e:
log.info(
f"Failed to send pb message. {message.author.name} didn't get pb\n",
exc_info=True,
f"Failed to send pb message. {message.author.name} didn't get pb\n", exc_info=True,
)
else:

View File

@@ -1,5 +1,16 @@
from .timezone import Timezone
__red_end_user_data_statement__ = (
"This cog stores data provided by users "
"for the express purpose of redisplaying. "
"It does not store user data which was not "
"provided through a command. "
"Users may remove their own content "
"without making a data removal request. "
"This cog does not support data requests, "
"but will respect deletion requests."
)
def setup(bot):
bot.add_cog(Timezone(bot))

View File

@@ -12,5 +12,6 @@
"requirements": [
"pytz"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog stores data provided by users for the express purpose of redisplaying. It does not store user data which was not provided through a command. Users may remove their own content without making a data removal request. This cog does not support data requests, but will respect deletion requests."
}

View File

@@ -3,19 +3,32 @@ import pytz
from datetime import datetime
from pytz import common_timezones
from pytz import country_timezones
from typing import Optional
from typing import Optional, Literal, Tuple, Union
from redbot.core import Config, commands, checks
class Timezone(commands.Cog):
"""Gets times across the world..."""
async def red_delete_data_for_user(
self, *, requester: Literal["discord", "owner", "user", "user_strict"], user_id: int,
):
await self.config.user_from_id(user_id).clear()
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 278049241001, force_registration=True)
default_user = {"usertime": None}
self.config.register_user(**default_user)
async def get_usertime(self, user: discord.User):
tz = None
usertime = await self.config.user(user).usertime()
if usertime:
tz = pytz.timezone(usertime)
return usertime, tz
@commands.guild_only()
@commands.group()
async def time(self, ctx):
@@ -45,7 +58,7 @@ class Timezone(commands.Cog):
"<https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>"
)
else:
tz = tz.title() if '/' in tz else tz.upper()
tz = tz.title() if "/" in tz else tz.upper()
if tz not in common_timezones:
raise Exception(tz)
fmt = "**%H:%M** %d-%B-%Y **%Z (UTC %z)**"
@@ -81,14 +94,14 @@ class Timezone(commands.Cog):
Usage: [p]time me Continent/City
"""
if tz is None:
usertime = await self.config.user(ctx.message.author).usertime()
usertime, tz = await self.get_usertime(ctx.author)
if not usertime:
await ctx.send(
f"You haven't set your timezone. Do `{ctx.prefix}time me Continent/City`: "
"see <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>"
)
else:
time = datetime.now(pytz.timezone(usertime))
time = datetime.now(tz)
time = time.strftime("**%H:%M** %d-%B-%Y **%Z (UTC %z)**")
msg = f"Your current timezone is **{usertime}.**\n" f"The current time is: {time}"
await ctx.send(msg)
@@ -97,7 +110,7 @@ class Timezone(commands.Cog):
if exist:
if "'" in tz:
tz = tz.replace("'", "")
await self.config.user(ctx.message.author).usertime.set(tz.title())
await self.config.user(ctx.author).usertime.set(tz.title())
await ctx.send(f"Successfully set your timezone to **{tz.title()}**.")
else:
await ctx.send(
@@ -106,11 +119,11 @@ class Timezone(commands.Cog):
)
@time.command()
@checks.admin_or_permissions(manage_guild=True)
@commands.is_owner()
async def set(self, ctx, user: discord.Member, *, tz=None):
"""Allows the mods to edit timezones."""
if not user:
user = ctx.message.author
user = ctx.author
if tz is None:
await ctx.send("That timezone is invalid.")
return
@@ -133,14 +146,13 @@ class Timezone(commands.Cog):
if not user:
await ctx.send("That isn't a user!")
else:
usertime = await self.config.user(user).usertime()
usertime, tz = await self.get_usertime(user)
if usertime:
time = datetime.now(pytz.timezone(usertime))
time = datetime.now(tz)
fmt = "**%H:%M** %d-%B-%Y **%Z (UTC %z)**"
time = time.strftime(fmt)
await ctx.send(
f"{user.name}'s current timezone is: **{usertime}**\n"
f"The current time is: {str(time)}"
f"{user.name}'s current timezone is: **{usertime}**\n" f"The current time is: {str(time)}"
)
else:
await ctx.send("That user hasn't set their timezone.")
@@ -151,8 +163,8 @@ class Timezone(commands.Cog):
if not user:
return await ctx.send_help()
usertime = await self.config.user(ctx.message.author).usertime()
othertime = await self.config.user(user).usertime()
usertime, user_tz = await self.get_usertime(ctx.author)
othertime, other_tz = await self.get_usertime(user)
if not usertime:
return await ctx.send(
@@ -162,9 +174,9 @@ class Timezone(commands.Cog):
if not othertime:
return await ctx.send(f"That user's timezone isn't set yet.")
user_now = datetime.now(pytz.timezone(usertime))
user_now = datetime.now(user_tz)
user_diff = user_now.utcoffset().total_seconds() / 60 / 60
other_now = datetime.now(pytz.timezone(othertime))
other_now = datetime.now(other_tz)
other_diff = other_now.utcoffset().total_seconds() / 60 / 60
time_diff = int(abs(user_diff - other_diff))
fmt = "**%H:%M %Z (UTC %z)**"
@@ -174,6 +186,4 @@ class Timezone(commands.Cog):
position = "ahead of" if user_diff < other_diff else "behind"
position_text = "" if time_diff == 0 else f" {position} you"
await ctx.send(
f"{user.display_name}'s time is {other_time} which is {time_amt}{position_text}."
)
await ctx.send(f"{user.display_name}'s time is {other_time} which is {time_amt}{position_text}.")

View File

@@ -1,4 +1,7 @@
from .tools import Tools
__red_end_user_data_statement__ = "This cog does not persistently store data or metadata about users."
def setup(bot):
bot.add_cog(Tools(bot))
bot.add_cog(Tools(bot))

View File

@@ -51,9 +51,7 @@ class GuildChannelConverter(converter.IDConverter, converter.Converter):
else:
result = converter._get_from_guilds(bot, "get_channel", channel_id)
if not isinstance(
result, (discord.TextChannel, discord.VoiceChannel, discord.CategoryChannel)
):
if not isinstance(result, (discord.TextChannel, discord.VoiceChannel, discord.CategoryChannel)):
raise BadArgument('Channel "{}" not found.'.format(argument))
return result

View File

@@ -17,5 +17,6 @@
"tags": [
"tools"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View File

@@ -3,11 +3,8 @@ import datetime
import discord
import inspect
import logging
import random
import re
import os
import time
from redbot.core import Config, checks, commands
from redbot.core import checks, commands
from redbot.core.utils import chat_formatting as cf
from redbot.core.utils.common_filters import filter_invites
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS, close_menu
@@ -21,6 +18,10 @@ log = logging.getLogger("red.aikaterna.tools")
class Tools(commands.Cog):
"""Mod and Admin tools."""
async def red_delete_data_for_user(self, **kwargs):
""" Nothing to delete """
return
def __init__(self, bot):
self.bot = bot
@@ -53,13 +54,9 @@ class Tools(commands.Cog):
tcs = guild.text_channels
vcs = guild.voice_channels
except AttributeError:
return await ctx.send(
"User is not in that guild or I do not have access to that guild."
)
return await ctx.send("User is not in that guild or I do not have access to that guild.")
author_text_channels = [
c for c in tcs if c.permissions_for(ctx.author).read_messages is True
]
author_text_channels = [c for c in tcs if c.permissions_for(ctx.author).read_messages is True]
author_voice_channels = [c for c in vcs if c.permissions_for(ctx.author).connect is True]
user_text_channels = [c for c in tcs if c.permissions_for(user).read_messages is True]
@@ -72,9 +69,7 @@ class Tools(commands.Cog):
user_voice_channels
) # voice channels only the author has access to
user_only_t = set(user_text_channels) - set(
author_text_channels
) # text channels only the user has access to
user_only_t = set(user_text_channels) - set(author_text_channels) # text channels only the user has access to
user_only_v = set(user_voice_channels) - set(
author_voice_channels
) # voice channels only the user has access to
@@ -87,18 +82,14 @@ class Tools(commands.Cog):
) # voice channels that author and user have in common
msg = "```ini\n"
msg += "{} [TEXT CHANNELS IN COMMON]:\n\n{}\n\n".format(
len(common_t), ", ".join([c.name for c in common_t])
)
msg += "{} [TEXT CHANNELS IN COMMON]:\n\n{}\n\n".format(len(common_t), ", ".join([c.name for c in common_t]))
msg += "{} [TEXT CHANNELS {} HAS EXCLUSIVE ACCESS TO]:\n\n{}\n\n".format(
len(user_only_t), user.name.upper(), ", ".join([c.name for c in user_only_t])
)
msg += "{} [TEXT CHANNELS YOU HAVE EXCLUSIVE ACCESS TO]:\n\n{}\n\n\n".format(
len(author_only_t), ", ".join([c.name for c in author_only_t])
)
msg += "{} [VOICE CHANNELS IN COMMON]:\n\n{}\n\n".format(
len(common_v), ", ".join([c.name for c in common_v])
)
msg += "{} [VOICE CHANNELS IN COMMON]:\n\n{}\n\n".format(len(common_v), ", ".join([c.name for c in common_v]))
msg += "{} [VOICE CHANNELS {} HAS EXCLUSIVE ACCESS TO]:\n\n{}\n\n".format(
len(user_only_v), user.name.upper(), ", ".join([c.name for c in user_only_v])
)
@@ -119,16 +110,10 @@ class Tools(commands.Cog):
guild = self.bot.get_guild(guild)
try:
can_access = [
c.name
for c in guild.text_channels
if c.permissions_for(user).read_messages == True
]
can_access = [c.name for c in guild.text_channels if c.permissions_for(user).read_messages == True]
text_channels = [c.name for c in guild.text_channels]
except AttributeError:
return await ctx.send(
"User is not in that guild or I do not have access to that guild."
)
return await ctx.send("User is not in that guild or I do not have access to that guild.")
prefix = "You have" if user.id == ctx.author.id else user.name + " has"
msg = "```ini\n[{} access to {} out of {} text channels]\n\n".format(
@@ -136,9 +121,7 @@ class Tools(commands.Cog):
)
msg += "[ACCESS]:\n{}\n\n".format(", ".join(can_access))
msg += "[NO ACCESS]:\n{}\n```".format(
", ".join(list(set(text_channels) - set(can_access)))
)
msg += "[NO ACCESS]:\n{}\n```".format(", ".join(list(set(text_channels) - set(can_access))))
await ctx.send(msg)
@access.command()
@@ -152,14 +135,10 @@ class Tools(commands.Cog):
guild = self.bot.get_guild(guild)
try:
can_access = [
c.name for c in guild.voice_channels if c.permissions_for(user).connect is True
]
can_access = [c.name for c in guild.voice_channels if c.permissions_for(user).connect is True]
voice_channels = [c.name for c in guild.voice_channels]
except AttributeError:
return await ctx.send(
"User is not in that guild or I do not have access to that guild."
)
return await ctx.send("User is not in that guild or I do not have access to that guild.")
prefix = "You have" if user.id == ctx.author.id else user.name + " has"
msg = "```ini\n[{} access to {} out of {} voice channels]\n\n".format(
@@ -167,9 +146,7 @@ class Tools(commands.Cog):
)
msg += "[ACCESS]:\n{}\n\n".format(", ".join(can_access))
msg += "[NO ACCESS]:\n{}\n```".format(
", ".join(list(set(voice_channels) - set(can_access)))
)
msg += "[NO ACCESS]:\n{}\n```".format(", ".join(list(set(voice_channels) - set(can_access))))
await ctx.send(msg)
@commands.guild_only()
@@ -196,8 +173,7 @@ class Tools(commands.Cog):
embed_list = []
for page in cf.pagify(msg, shorten_by=1400):
embed = discord.Embed(
description="**Total bans:** {}\n\n{}".format(bancount, page),
colour=await ctx.embed_colour(),
description="**Total bans:** {}\n\n{}".format(bancount, page), colour=await ctx.embed_colour(),
)
embed_list.append(embed)
await menu(ctx, embed_list, DEFAULT_CONTROLS)
@@ -332,17 +308,13 @@ class Tools(commands.Cog):
role = roles[response - 1]
awaiter = await ctx.send(
embed=discord.Embed(
description="Getting member names...", colour=await ctx.embed_colour()
)
embed=discord.Embed(description="Getting member names...", colour=await ctx.embed_colour())
)
await asyncio.sleep(1.5) # taking time to retrieve the names
users_in_role = "\n".join(
sorted(m.display_name for m in guild.members if role in m.roles)
)
users_in_role = "\n".join(sorted(m.display_name for m in guild.members if role in m.roles))
if len(users_in_role) == 0:
embed = discord.Embed(
description=cf.bold(f"0 users found in the {role.name} role."), colour=await ctx.embed_colour()
description=cf.bold(f"0 users found in the {role.name} role."), colour=await ctx.embed_colour(),
)
await awaiter.edit(embed=embed)
return
@@ -385,8 +357,7 @@ class Tools(commands.Cog):
if ctx.channel.permissions_for(ctx.guild.me).embed_links:
embed = discord.Embed(
description=f"{user.mention} joined this guild on {joined_on}.",
color=await ctx.embed_colour(),
description=f"{user.mention} joined this guild on {joined_on}.", color=await ctx.embed_colour(),
)
await ctx.send(embed=embed)
else:
@@ -406,9 +377,9 @@ class Tools(commands.Cog):
form = "{gid} :: {mems:0{zpadding}} :: {name}"
all_forms = [
form.format(
gid=g.id,
mems=g.member_count,
name=filter_invites(cf.escape(g.name)),
gid=g.id,
mems=g.member_count,
name=filter_invites(cf.escape(g.name)),
zpadding=max_zpadding
)
for g in guilds
@@ -438,9 +409,7 @@ class Tools(commands.Cog):
topChannels_formed = "\n".join(self.channels_format(top_channels))
categories_formed = "\n\n".join([self.category_format(tup) for tup in category_channels])
await ctx.send(
f"{ctx.guild.name} has {len(channels)} channel{'s' if len(channels) > 1 else ''}."
)
await ctx.send(f"{ctx.guild.name} has {len(channels)} channel{'s' if len(channels) > 1 else ''}.")
for page in cf.pagify(topChannels_formed, delims=["\n"], shorten_by=16):
await ctx.send(asciidoc(page))
@@ -461,9 +430,7 @@ class Tools(commands.Cog):
header = "{:>33}\n{}\n\n".format(head1, "-" * 57)
user_body = (
" {mem} ({memid})\n"
" {spcs}Joined Guild: {sp1}{join}\n"
" {spcs}Account Created: {sp2}{created}\n\n"
" {mem} ({memid})\n" " {spcs}Joined Guild: {sp1}{join}\n" " {spcs}Account Created: {sp2}{created}\n\n"
)
disp = header
@@ -509,7 +476,7 @@ class Tools(commands.Cog):
perms = iter(ctx.channel.permissions_for(user))
perms_we_have = ""
perms_we_dont = ""
for x in perms:
for x in sorted(perms):
if "True" in str(x):
perms_we_have += "+\t{0}\n".format(str(x).split("'")[1])
else:
@@ -526,11 +493,7 @@ class Tools(commands.Cog):
else:
role = self._role_from_string(ctx.guild, rolename)
if role is None:
await ctx.send(
embed=discord.Embed(
description="Cannot find role.", colour=await ctx.embed_colour()
)
)
await ctx.send(embed=discord.Embed(description="Cannot find role.", colour=await ctx.embed_colour()))
return
await ctx.send(f"**{rolename} ID:** {role.id}")
@@ -558,7 +521,7 @@ class Tools(commands.Cog):
perms = iter(role.permissions)
perms_we_have = ""
perms_we_dont = ""
for x in perms:
for x in sorted(perms):
if "True" in str(x):
perms_we_have += "{0}\n".format(str(x).split("'")[1])
else:
@@ -576,14 +539,12 @@ class Tools(commands.Cog):
em.add_field(name="Server", value=role.guild.name)
em.add_field(name="Role Name", value=role.name)
em.add_field(name="Created", value=self._dynamic_time(role.created_at))
em.add_field(
name="Users in Role", value=len([m for m in guild.members if role in m.roles])
)
em.add_field(name="Users in Role", value=len([m for m in guild.members if role in m.roles]))
em.add_field(name="ID", value=role.id)
em.add_field(name="Color", value=role.color)
em.add_field(name="Position", value=role.position)
em.add_field(name="Valid Permissons", value="{}".format(perms_we_have))
em.add_field(name="Invalid Permissons", value="{}".format(perms_we_dont))
em.add_field(name="Valid Permissions", value="{}".format(perms_we_have))
em.add_field(name="Invalid Permissions", value="{}".format(perms_we_dont))
em.set_thumbnail(url=role.guild.icon_url)
try:
await loadingmsg.edit(embed=em)
@@ -597,7 +558,7 @@ class Tools(commands.Cog):
perms = iter(role.permissions)
perms_we_have2 = ""
perms_we_dont2 = ""
for x in perms:
for x in sorted(perms):
if "True" in str(x):
perms_we_have2 += "+{0}\n".format(str(x).split("'")[1])
else:
@@ -624,9 +585,7 @@ class Tools(commands.Cog):
form = "`{rpos:0{zpadding}}` - `{rid}` - `{rcolor}` - {rment} "
max_zpadding = max([len(str(r.position)) for r in ctx.guild.roles])
rolelist = [
form.format(
rpos=r.position, zpadding=max_zpadding, rid=r.id, rment=r.mention, rcolor=r.color
)
form.format(rpos=r.position, zpadding=max_zpadding, rid=r.id, rment=r.mention, rcolor=r.color)
for r in ctx.guild.roles
]
@@ -635,8 +594,7 @@ class Tools(commands.Cog):
embed_list = []
for page in cf.pagify(rolelist, shorten_by=1400):
embed = discord.Embed(
description=f"**Total roles:** {len(ctx.guild.roles)}\n\n{page}",
colour=await ctx.embed_colour(),
description=f"**Total roles:** {len(ctx.guild.roles)}\n\n{page}", colour=await ctx.embed_colour(),
)
embed_list.append(embed)
await menu(ctx, embed_list, DEFAULT_CONTROLS)
@@ -648,24 +606,8 @@ class Tools(commands.Cog):
guild = ctx.guild
if not user:
user = author
seen = len(
set(
[
member.guild.name
for member in self.bot.get_all_members()
if member.id == user.id
]
)
)
sharedservers = str(
set(
[
member.guild.name
for member in self.bot.get_all_members()
if member.id == user.id
]
)
)
seen = len(set([member.guild.name for member in self.bot.get_all_members() if member.id == user.id]))
sharedservers = str(set([member.guild.name for member in self.bot.get_all_members() if member.id == user.id]))
for shared in sharedservers:
shared = "".strip("'").join(sharedservers).strip("'")
shared = shared.strip("{").strip("}")
@@ -693,15 +635,7 @@ class Tools(commands.Cog):
guild = self.bot.get_guild(int(guild))
except TypeError:
return await ctx.send("Not a valid guild id.")
online = str(
len(
[
m.status
for m in guild.members
if str(m.status) == "online" or str(m.status) == "idle"
]
)
)
online = str(len([m.status for m in guild.members if str(m.status) == "online" or str(m.status) == "idle"]))
total_users = str(len(guild.members))
text_channels = [x for x in guild.channels if isinstance(x, discord.TextChannel)]
voice_channels = [x for x in guild.channels if isinstance(x, discord.VoiceChannel)]
@@ -739,17 +673,7 @@ class Tools(commands.Cog):
roles = [x.name for x in user.roles if x.name != "@everyone"]
if not roles:
roles = ["None"]
seen = str(
len(
set(
[
member.guild.name
for member in self.bot.get_all_members()
if member.id == user.id
]
)
)
)
seen = str(len(set([member.guild.name for member in self.bot.get_all_members() if member.id == user.id])))
load = "```\nLoading user info...```"
waiting = await ctx.send(load)
@@ -772,9 +696,7 @@ class Tools(commands.Cog):
if actwatch := discord.utils.get(user.activities, type=discord.ActivityType.watching):
data += "[Watching]: {}\n".format(cf.escape(str(actwatch.name)))
if actstream := discord.utils.get(user.activities, type=discord.ActivityType.streaming):
data += "[Streaming]: [{}]({})\n".format(
cf.escape(str(actstream.name)), cf.escape(actstream.url)
)
data += "[Streaming]: [{}]({})\n".format(cf.escape(str(actstream.name)), cf.escape(actstream.url))
if actcustom := discord.utils.get(user.activities, type=discord.ActivityType.custom):
if actcustom.name is not None:
data += "[Custom status]: {}\n".format(cf.escape(str(actcustom.name)))
@@ -784,12 +706,8 @@ class Tools(commands.Cog):
if caller != "invoke":
data += "[Joined]: {}\n".format(self._dynamic_time(joined_at))
data += "[Roles]: {}\n".format(", ".join(roles))
data += "[In Voice]: {}\n".format(
user.voice.channel if user.voice is not None else None
)
data += "[AFK]: {}\n".format(
user.voice.afk if user.voice is not None else False
)
data += "[In Voice]: {}\n".format(user.voice.channel if user.voice is not None else None)
data += "[AFK]: {}\n".format(user.voice.afk if user.voice is not None else False)
data += "```"
await asyncio.sleep(1)
await waiting.edit(content=data)
@@ -902,11 +820,7 @@ class Tools(commands.Cog):
type_justify = max([len(type_name(c)) for c in channels])
return [
channel_form.format(
name=c.name[:24].ljust(name_justify),
ctype=type_name(c).ljust(type_justify),
cid=c.id,
)
channel_form.format(name=c.name[:24].ljust(name_justify), ctype=type_name(c).ljust(type_justify), cid=c.id,)
for c in channels
]

View File

@@ -1,5 +1,14 @@
from .trickortreat import TrickOrTreat
__red_end_user_data_statement__ = (
"This cog does not persistently store end user data. "
"This cog does store discord IDs as needed for operation. "
"This cog does store user stats for the cog such as their score. "
"Users may remove their own content without making a data removal request."
"This cog does not support data requests, "
"but will respect deletion requests."
)
def setup(bot):
bot.add_cog(TrickOrTreat(bot))

View File

@@ -11,5 +11,6 @@
"candy",
"pick"
],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog does not persistently store end user data. This cog does store discord IDs as needed for operation. This cog does store user stats for the cog such as their score. Users may remove their own content without making a data removal request. This cog does not support data requests, but will respect deletion requests."
}

View File

@@ -1,5 +1,7 @@
import asyncio
import datetime
from typing import Literal, Optional
import discord
import random
import math
@@ -7,10 +9,20 @@ from redbot.core import commands, checks, Config, bank
from redbot.core.utils.chat_formatting import box, pagify, humanize_number
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
__version__ = "0.0.7"
__version__ = "0.1.3"
class TrickOrTreat(commands.Cog):
"""Trick or treating for your server."""
async def red_delete_data_for_user(
self,
*,
requester: Literal["discord", "owner", "user", "user_strict"],
user_id: int,
):
await self.config.user_from_id(user_id).clear()
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2710311393, force_registration=True)
@@ -19,6 +31,7 @@ class TrickOrTreat(commands.Cog):
default_user = {
"candies": 0,
"chocolate": 0,
"eaten": 0,
"last_tot": "2018-01-01 00:00:00.000001",
"lollipops": 0,
@@ -30,11 +43,28 @@ class TrickOrTreat(commands.Cog):
self.config.register_guild(**default_guild)
@commands.guild_only()
@commands.cooldown(1, 1, commands.BucketType.user)
@commands.command()
async def eat(self, ctx, number: int = 1, candy_type=None):
async def eatcandy(self, ctx, number: Optional[int] = 1, candy_type=None):
"""Eat some candy.
Valid types: candies, lollipops, stars"""
Valid types: candy/candie(s), chocolate(s), lollipop(s), star(s)
Examples:
`[p]eatcandy 3 lollipops`
`[p]eatcandy star`
\N{CANDY}
The star of this competition. You should try to eat all of these, but don't get too sick.
\N{CHOCOLATE BAR}
Reduces sickness by 10.
\N{LOLLIPOP}
Reduces sickness by 20.
\N{WHITE MEDIUM STAR}
Resets sickness to 0.
"""
userdata = await self.config.user(ctx.author).all()
pick = await self.config.guild(ctx.guild).pick()
if not candy_type:
@@ -49,11 +79,11 @@ class TrickOrTreat(commands.Cog):
candy_type = "lollipops"
if candy_type in ["stars", "star"]:
candy_type = "stars"
candy_list = ["candies", "lollipops", "stars"]
if candy_type in ["chocolate", "chocolates"]:
candy_type = "chocolate"
candy_list = ["candies", "chocolate", "lollipops", "stars"]
if candy_type not in candy_list:
return await ctx.send(
"That's not a candy type! Use the inventory command to see what you have."
)
return await ctx.send("That's not a candy type! Use the inventory command to see what you have.")
if userdata[candy_type] < number:
return await ctx.send(f"You don't have that many {candy_type}.")
if userdata[candy_type] == 0:
@@ -68,6 +98,10 @@ class TrickOrTreat(commands.Cog):
"You gobble up",
"You make a meal of",
"You devour",
"You monstrously pig out on",
"You hastily chomp down on",
"You daintily partake of",
"You earnestly consume",
]
if candy_type in ["candies", "candy"]:
if (userdata["sickness"] + number * 2) in range(70, 95):
@@ -76,9 +110,7 @@ class TrickOrTreat(commands.Cog):
if yuck == 10:
await self.config.user(ctx.author).sickness.set(userdata["sickness"] + 25)
if yuck in range(1, 9):
await self.config.user(ctx.author).sickness.set(
userdata["sickness"] + (yuck * 2)
)
await self.config.user(ctx.author).sickness.set(userdata["sickness"] + (yuck * 2))
if userdata["candies"] > 3 + number:
lost_candy = userdata["candies"] - random.randint(1, 3) - number
@@ -90,14 +122,10 @@ class TrickOrTreat(commands.Cog):
await self.config.user(ctx.author).candies.set(0)
await self.config.guild(ctx.guild).pick.set(pick_now + lost_candy)
else:
await self.config.user(ctx.author).candies.set(
userdata["candies"] - lost_candy
)
await self.config.user(ctx.author).candies.set(userdata["candies"] - lost_candy)
await self.config.guild(ctx.guild).pick.set(pick_now + lost_candy)
await self.config.user(ctx.author).eaten.set(
userdata["eaten"] + (userdata["candies"] - lost_candy)
)
await self.config.user(ctx.author).eaten.set(userdata["eaten"] + (userdata["candies"] - lost_candy))
return await ctx.send(
f"You begin to think you don't need all this candy, maybe...\n*{lost_candy} candies are left behind*"
@@ -117,9 +145,7 @@ class TrickOrTreat(commands.Cog):
)
await self.config.guild(ctx.guild).pick.set(pick + lost_candy)
await self.config.user(ctx.author).candies.set(0)
await self.config.user(ctx.author).eaten.set(
userdata["eaten"] + (userdata["candies"] - lost_candy)
)
await self.config.user(ctx.author).eaten.set(userdata["eaten"] + (userdata["candies"] - lost_candy))
message = await ctx.send("...")
await asyncio.sleep(2)
await message.edit(content="..........")
@@ -135,6 +161,19 @@ class TrickOrTreat(commands.Cog):
await self.config.user(ctx.author).sickness.set(userdata["sickness"] + (number * 2))
await self.config.user(ctx.author).candies.set(userdata["candies"] - number)
await self.config.user(ctx.author).eaten.set(userdata["eaten"] + number)
if candy_type in ["chocolates", "chocolate"]:
pluralchoc = "chocolate" if number == 1 else "chocolates"
await ctx.send(
f"{random.choice(eat_phrase)} {number} {pluralchoc}. You feel slightly better!\n*Sickness has gone down by {number * 10}*"
)
new_sickness = userdata["sickness"] - (number * 10)
if new_sickness < 0:
new_sickness = 0
await self.config.user(ctx.author).sickness.set(new_sickness)
await self.config.user(ctx.author).chocolate.set(userdata["chocolate"] - number)
await self.config.user(ctx.author).eaten.set(userdata["eaten"] + number)
if candy_type in ["lollipops", "lollipop"]:
pluralpop = "lollipop" if number == 1 else "lollipops"
await ctx.send(
@@ -146,6 +185,7 @@ class TrickOrTreat(commands.Cog):
await self.config.user(ctx.author).sickness.set(new_sickness)
await self.config.user(ctx.author).lollipops.set(userdata["lollipops"] - number)
await self.config.user(ctx.author).eaten.set(userdata["eaten"] + number)
if candy_type in ["stars", "star"]:
pluralstar = "star" if number == 1 else "stars"
await ctx.send(
@@ -165,7 +205,7 @@ class TrickOrTreat(commands.Cog):
@commands.guild_only()
@commands.command()
async def buy(self, ctx, pieces: int):
async def buycandy(self, ctx, pieces: int):
"""Buy some candy. Prices could vary at any time."""
candy_now = await self.config.user(ctx.author).candies()
credits_name = await bank.get_currency_name(ctx.guild)
@@ -235,12 +275,9 @@ class TrickOrTreat(commands.Cog):
for page in pagify(temp_msg, delims=["\n"], page_length=1000):
embed = discord.Embed(
colour=0xF4731C,
description=box(f"\N{CANDY} Global leaderboard \N{CANDY}", lang="prolog")
+ (box(page, lang="md")),
)
embed.set_footer(
text=f"Page {humanize_number(pages)}/{humanize_number(math.ceil(len(temp_msg) / 1500))}"
description=box(f"\N{CANDY} Global leaderboard \N{CANDY}", lang="prolog") + (box(page, lang="md")),
)
embed.set_footer(text=f"Page {humanize_number(pages)}/{humanize_number(math.ceil(len(temp_msg) / 1500))}")
pages += 1
page_list.append(embed)
return await menu(ctx, page_list, DEFAULT_CONTROLS)
@@ -255,6 +292,8 @@ class TrickOrTreat(commands.Cog):
msg = f"{ctx.author.mention}'s Candy Bag:"
em = discord.Embed(color=await ctx.embed_color())
em.description = f"{userdata['candies']} \N{CANDY}"
if userdata["chocolate"]:
em.description += f"\n{userdata['chocolate']} \N{CHOCOLATE BAR}"
if userdata["lollipops"]:
em.description += f"\n{userdata['lollipops']} \N{LOLLIPOP}"
if userdata["stars"]:
@@ -264,15 +303,11 @@ class TrickOrTreat(commands.Cog):
elif sickness in range(56, 71):
em.description += f"\n\n**Sickness is over 55/100**\n*You don't feel so good...*"
elif sickness in range(71, 86):
em.description += (
f"\n\n**Sickness is over 70/100**\n*You really don't feel so good...*"
)
em.description += f"\n\n**Sickness is over 70/100**\n*You really don't feel so good...*"
elif sickness in range(86, 101):
em.description += f"\n\n**Sickness is over 85/100**\n*The thought of more sugar makes you feel awful...*"
elif sickness > 100:
em.description += (
f"\n\n**Sickness is over 100/100**\n*Better wait a while for more candy...*"
)
em.description += f"\n\n**Sickness is over 100/100**\n*Better wait a while for more candy...*"
await ctx.send(msg, embed=em)
@commands.guild_only()
@@ -310,7 +345,7 @@ class TrickOrTreat(commands.Cog):
@commands.guild_only()
@commands.cooldown(1, 600, discord.ext.commands.BucketType.user)
@commands.command()
async def steal(self, ctx, user: discord.Member = None):
async def stealcandy(self, ctx, user: discord.Member = None):
"""Steal some candy."""
guild_users = [m.id for m in ctx.guild.members if m is not m.bot and not m == ctx.author]
candy_users = await self.config._all_from_scope(scope="USER")
@@ -351,9 +386,7 @@ class TrickOrTreat(commands.Cog):
else:
message = await ctx.send("You start looking around for a target...")
await asyncio.sleep(random.randint(3, 6))
return await message.edit(
content="You snuck around for a while but didn't find anything."
)
return await message.edit(content="You snuck around for a while but didn't find anything.")
user_candy_now = await self.config.user(ctx.author).candies()
multip = random.randint(1, 100) / 100
if multip > 0.7:
@@ -362,9 +395,7 @@ class TrickOrTreat(commands.Cog):
if pieces <= 0:
message = await ctx.send("You stealthily move over to an unsuspecting person...")
await asyncio.sleep(4)
return await message.edit(
content="You found someone to pickpocket, but they had nothing but pocket lint."
)
return await message.edit(content="You found someone to pickpocket, but they had nothing but pocket lint.")
chance = random.randint(1, 25)
sneak_phrases = [
"You look around furtively...",
@@ -374,9 +405,7 @@ class TrickOrTreat(commands.Cog):
if chance <= 10:
message = await ctx.send("You creep closer to the target...")
await asyncio.sleep(random.randint(3, 5))
return await message.edit(
content="You snuck around for a while but didn't find anything."
)
return await message.edit(content="You snuck around for a while but didn't find anything.")
if chance > 18:
await self.config.user(picked_user).candies.set(picked_candy_now - pieces)
await self.config.user(ctx.author).candies.set(user_candy_now + pieces)
@@ -412,15 +441,14 @@ class TrickOrTreat(commands.Cog):
@commands.group()
async def totchannel(self, ctx):
"""Channel management for Trick or Treat."""
if ctx.invoked_subcommand is not None or isinstance(
ctx.invoked_subcommand, commands.Group
):
if ctx.invoked_subcommand is not None or isinstance(ctx.invoked_subcommand, commands.Group):
return
channel_list = await self.config.guild(ctx.guild).channel()
channel_msg = "Trick or Treat Channels:\n"
for chan in channel_list:
channel_obj = self.bot.get_channel(chan)
channel_msg += f"{channel_obj.name}\n"
if channel_obj:
channel_msg += f"{channel_obj.name}\n"
await ctx.send(box(channel_msg))
@commands.guild_only()
@@ -463,20 +491,22 @@ class TrickOrTreat(commands.Cog):
await ctx.send(msg)
@commands.guild_only()
@commands.command()
@commands.command(hidden=True)
async def totversion(self, ctx):
"""Trick or Treat version."""
await ctx.send(
f"Trick or Treat, version {__version__}\n\n*0.0.6 updates:*\n**cooldown -> totcooldown\nGeneral cleanup for 2019**\n\n*0.0.5 updates:*\n**Save values before waiting on messages:\nQuick commands will not overwrite other values**\n\n*0.0.4 updates:*\n**+2% star chance on trick or treat (6% total)\n+5% lollipop chance on trick or treat (25% total)\nMore RP messages\nFix for steal mechanic freezing\n**"
)
await ctx.send(f"Trick or Treat version {__version__}")
async def has_perm(self, user):
return await self.bot.allowed_by_whitelist_blacklist(user)
@commands.Cog.listener()
async def on_message(self, message):
async def on_message_without_command(self, message):
if isinstance(message.channel, discord.abc.PrivateChannel):
return
if message.author.bot:
return
content = (message.content).lower()
if not await self.has_perm(message.author):
return
chance = random.randint(1, 12)
if chance % 4 == 0:
@@ -488,6 +518,13 @@ class TrickOrTreat(commands.Cog):
new_sickness = 0
await self.config.user(message.author).sickness.set(new_sickness)
pick_chance = random.randint(1, 12)
if pick_chance % 4 == 0:
random_candies = random.randint(1, 3)
guild_pool = await self.config.guild(message.guild).pick()
await self.config.guild(message.guild).pick.set(guild_pool + random_candies)
content = (message.content).lower()
if not content.startswith("trick or treat"):
return
toggle = await self.config.guild(message.guild).toggle()
@@ -501,10 +538,7 @@ class TrickOrTreat(commands.Cog):
last_time = datetime.datetime.strptime(str(userdata["last_tot"]), "%Y-%m-%d %H:%M:%S.%f")
now = datetime.datetime.now(datetime.timezone.utc)
now = now.replace(tzinfo=None)
if (
int((now - last_time).total_seconds())
< await self.config.guild(message.guild).cooldown()
):
if int((now - last_time).total_seconds()) < await self.config.guild(message.guild).cooldown():
messages = [
"The thought of candy right now doesn't really sound like a good idea.",
"All the lights on this street are dark...",
@@ -520,12 +554,42 @@ class TrickOrTreat(commands.Cog):
candy = random.randint(1, 25)
lollipop = random.randint(0, 100)
star = random.randint(0, 100)
chocolate = random.randint(0, 100)
win_message = f"{message.author.mention}\nYou received:\n{candy}\N{CANDY}"
await self.config.user(message.author).candies.set(userdata["candies"] + candy)
if lollipop > 75:
if chocolate == 100:
await self.config.user(message.author).chocolate.set(userdata["chocolate"] + 4)
win_message += "\n**BONUS**: 5 \N{CHOCOLATE BAR}"
elif 99 >= chocolate >= 95:
await self.config.user(message.author).chocolate.set(userdata["chocolate"] + 3)
win_message += "\n**BONUS**: 4 \N{CHOCOLATE BAR}"
elif 94 >= chocolate >= 90:
await self.config.user(message.author).chocolate.set(userdata["chocolate"] + 2)
win_message += "\n**BONUS**: 2 \N{CHOCOLATE BAR}"
elif 89 >= chocolate >= 80:
await self.config.user(message.author).chocolate.set(userdata["chocolate"] + 1)
win_message += "\n**BONUS**: 1 \N{CHOCOLATE BAR}"
if lollipop == 100:
await self.config.user(message.author).lollipops.set(userdata["lollipops"] + 3)
win_message += "\n**BONUS**: 3 \N{LOLLIPOP}"
elif 99 >= lollipop >= 95:
await self.config.user(message.author).lollipops.set(userdata["lollipops"] + 2)
win_message += "\n**BONUS**: 2 \N{LOLLIPOP}"
elif 94 >= lollipop >= 80:
await self.config.user(message.author).lollipops.set(userdata["lollipops"] + 1)
if star > 94:
win_message += "\n**BONUS**: 1 \N{LOLLIPOP}"
if star == 100:
await self.config.user(message.author).stars.set(userdata["stars"] + 3)
win_message += "\n**BONUS**: 3 \N{WHITE MEDIUM STAR}"
elif 99 >= star >= 97:
await self.config.user(message.author).stars.set(userdata["stars"] + 2)
win_message += "\n**BONUS**: 2 \N{WHITE MEDIUM STAR}"
elif 96 >= star >= 80:
await self.config.user(message.author).stars.set(userdata["stars"] + 1)
win_message += "\n**BONUS**: 1 \N{WHITE MEDIUM STAR}"
walking_messages = [
"*You hear footsteps...*",
@@ -567,10 +631,4 @@ class TrickOrTreat(commands.Cog):
]
await bot_talking.edit(content=random.choice(greet_messages))
await asyncio.sleep(2)
win_message = f"{message.author.mention}\nYou received:\n{candy}\N{CANDY}"
if lollipop > 75:
win_message += "\n**BONUS**: 1 \N{LOLLIPOP}"
if star > 94:
win_message += "\n**BONUS**: 1 \N{WHITE MEDIUM STAR}"
await message.channel.send(win_message)

View File

@@ -1,5 +1,16 @@
from .warcraftlogs import WarcraftLogs
__red_end_user_data_statement__ = (
"This cog stores data provided by users "
"for the express purpose of redisplaying. "
"It does not store user data which was not "
"provided through a command. "
"Users may remove their own content "
"without making a data removal request. "
"This cog does not support data requests, "
"but will respect deletion requests."
)
def setup(bot):
bot.add_cog(WarcraftLogs(bot))

View File

@@ -4,5 +4,7 @@
"install_msg": "Check out [p]help WarcraftLogs and set your WCL API key, available by signing into a WarcraftLogs account on their site and visiting the bottom of your settings page. ",
"short": "WarcraftLogs data for World of Warcraft Classic players.",
"tags": ["warcraft"],
"type": "COG"
"type": "COG",
"end_user_data_statement": "This cog stores data provided by users for the express purpose of redisplaying. It does not store user data which was not provided through a command. Users may remove their own content without making a data removal request. This cog does not support data requests, but will respect deletion requests."
}

Some files were not shown because too many files have changed in this diff Show More