From 0ff00cd12a49b147efd9a950587afcdd82249af9 Mon Sep 17 00:00:00 2001 From: brandons209 Date: Sat, 13 Feb 2021 04:09:44 -0500 Subject: [PATCH] add valentines cog to send cards to members. also some touchups on other cogs --- moreadmin/moreadmin.py | 11 +- suggestion/__init__.py | 2 + suggestion/suggestion.py | 10 +- valentinecards/__init__.py | 7 + valentinecards/info.json | 10 ++ valentinecards/valentine.py | 250 ++++++++++++++++++++++++++++++++++++ 6 files changed, 286 insertions(+), 4 deletions(-) create mode 100644 valentinecards/__init__.py create mode 100644 valentinecards/info.json create mode 100644 valentinecards/valentine.py diff --git a/moreadmin/moreadmin.py b/moreadmin/moreadmin.py index 4ee3052..0ccbbab 100644 --- a/moreadmin/moreadmin.py +++ b/moreadmin/moreadmin.py @@ -192,6 +192,10 @@ class MoreAdmin(commands.Cog): guild = ctx.guild to_purge = [] + # update members + _guilds = [g for g in self.bot.guilds if g.large and not (g.chunked or g.unavailable)] + await self.bot.request_offline_members(*_guilds) + for member in guild.members: if member.id == self.bot.user.id: # don't want to purge the bot. continue @@ -499,13 +503,14 @@ class MoreAdmin(commands.Cog): color = await ctx.embed_color() # defines deleting a note for the user - async def delete_note(ctx: commands.GuildContext, + async def delete_note( + ctx: commands.GuildContext, pages: list, controls: dict, message: discord.Message, page: int, timeout: float, - emoji: str + emoji: str, ): async with self.config.member(member).notes() as notes: del notes[page] @@ -774,7 +779,7 @@ class MoreAdmin(commands.Cog): for i, user in enumerate(to_purge): try: await user.send(purge_msg) - except: + except discord.HTTPException: pass if check_messages: diff --git a/suggestion/__init__.py b/suggestion/__init__.py index 4b1b553..3c64fc6 100644 --- a/suggestion/__init__.py +++ b/suggestion/__init__.py @@ -1,5 +1,7 @@ from .suggestion import Suggestion +__red_end_user_data_statement__ = "This doesn't store any user data." + def setup(bot): bot.add_cog(Suggestion(bot)) diff --git a/suggestion/suggestion.py b/suggestion/suggestion.py index 13da336..00d5519 100644 --- a/suggestion/suggestion.py +++ b/suggestion/suggestion.py @@ -1,7 +1,7 @@ import asyncio import discord -from typing import Optional +from typing import Optional, Literal from discord.utils import get from datetime import timedelta @@ -722,3 +722,11 @@ class Suggestion(commands.Cog): embed.add_field(name="Votes:", value=f"For: `{num_up}`, Against: `{num_down}`", inline=False) return content, embed + + async def red_delete_data_for_user( + self, + *, + requester: Literal["discord_deleted_user", "owner", "user", "user_strict"], + user_id: int, + ): + pass diff --git a/valentinecards/__init__.py b/valentinecards/__init__.py new file mode 100644 index 0000000..316ce5b --- /dev/null +++ b/valentinecards/__init__.py @@ -0,0 +1,7 @@ +from .valentine import Valentine_Cards + +__red_end_user_data_statement__ = "This doesn't store any user data." + + +def setup(bot): + bot.add_cog(Valentine_Cards(bot)) diff --git a/valentinecards/info.json b/valentinecards/info.json new file mode 100644 index 0000000..68eddf1 --- /dev/null +++ b/valentinecards/info.json @@ -0,0 +1,10 @@ +{ + "author" : ["brandons209"], + "bot_version": [3,4,4], + "install_msg" : "Happy Valentines day! Make sure to set the fallback channel.", + "short" : "Happy Valentines day!", + "description" : "Sends a custom message and 'card' to all users in a guild. Messages and cards are choosen randomly from the available options for each user. ", + "tags" : ["valentine", "massdm", "cards", "holiday"], + "hidden" : false, + "end_user_data_statement": "This cog won't store anything for a user." +} diff --git a/valentinecards/valentine.py b/valentinecards/valentine.py new file mode 100644 index 0000000..63d15e7 --- /dev/null +++ b/valentinecards/valentine.py @@ -0,0 +1,250 @@ +from redbot.core import commands, checks, Config +from redbot.core.utils.chat_formatting import * +from redbot.core.utils.predicates import MessagePredicate +from typing import Literal +import asyncio, discord, random + + +class Valentine_Cards(commands.Cog): + """ + Distribute valentine (or any messages / pictures) to all members in a server. + Cards and messages are randomly selected for each person + """ + + def __init__(self, bot): + self.bot = bot + self.config = Config.get_conf(self, identifier=8465846534353571, force_registration=True) + default_guild = {"cards": [], "dm_msgs": [], "fallback_channel": None} + + self.config.register_guild(**default_guild) + + @staticmethod + def format_list(items: list) -> list: + """ + Format a list into a numbered list for printing + + Returns pages of text + """ + if not items: + return [] + + msg = "\n".join(f"{i+1}. {item}" for i, item in enumerate(items)) + return pagify(msg) + + @commands.group(name="vset") + @checks.admin_or_permissions(administrator=True) + @commands.guild_only() + async def vset(self, ctx): + """ + Manage cards and DM messages + """ + pass + + @vset.command(name="fallback") + async def vset_fallback_channel(self, ctx, *, channel: discord.TextChannel = None): + """ + Set a channel as fallback when DM fails + + Run command with no channel to reset channel + """ + if not channel: + current_channel = await self.config.guild(ctx.guild).fallback_channel() + if not current_channel: + await ctx.send(warning("No fallback channel set.")) + elif not ctx.guild.get_channel(current_channel): + await ctx.send( + warning("Channel set but I cannot see the channel, so I will reset the fallback channel saved.") + ) + await self.config.guild(ctx.guild).fallback_channel.clear() + else: + await ctx.send(f"Current fallback channel: {ctx.guild.get_channel(current_channel).mention}") + return + + await self.config.guild(ctx.guild).fallback_channel.set(channel.id) + await ctx.tick() + + @vset.group(name="cards") + async def vset_cards(self, ctx): + """ + Manage cards + """ + pass + + @vset_cards.command(name="add") + async def vset_cards_add(self, ctx, *, url: str): + """ + Add a possible card to be sent to a user + + Card should be a URL to the image + """ + async with self.config.guild(ctx.guild).cards() as cards: + cards.append(url) + + await ctx.tick() + + @vset_cards.command(name="list") + async def vset_cards_list(self, ctx): + """ + List the current cards + """ + cards = await self.config.guild(ctx.guild).cards() + if not cards: + await ctx.send(warning("No cards defined.")) + return + + pages = self.format_list(cards) + for page in pages: + await ctx.send(page) + + @vset_cards.command(name="del") + async def vset_cards_del(self, ctx): + """ + Delete a card + """ + async with self.config.guild(ctx.guild).cards() as cards: + if not cards: + await ctx.send(warning("No cards defined.")) + return + pages = self.format_list(cards) + for page in pages: + await ctx.send(box(page)) + + await ctx.send("Please choose the number of the message to delete.") + pred = MessagePredicate.contained_in([str(i + 1) for i in range(len(cards))], ctx=ctx) + try: + await self.bot.wait_for("message", check=pred, timeout=60) + except asyncio.TimeoutError: + await ctx.send("Took too long, cancelled.") + return + + del cards[pred.result] + + await ctx.tick() + + @vset.group(name="dms") + async def vset_dms(self, ctx): + """ + Manage DM messages + """ + pass + + @vset_dms.command(name="add") + async def vset_dms_add(self, ctx, *, msg: str): + """ + Add a possible DM message to be sent to a user + """ + async with self.config.guild(ctx.guild).dm_msgs() as dm_msgs: + dm_msgs.append(msg) + + await ctx.tick() + + @vset_dms.command(name="list") + async def vset_dms_list(self, ctx): + """ + List the current DM messages + """ + dm_msgs = await self.config.guild(ctx.guild).dm_msgs() + if not dm_msgs: + await ctx.send(warning("No DM messages defined.")) + return + pages = self.format_list(dm_msgs) + for page in pages: + await ctx.send(box(page)) + + @vset_dms.command(name="del") + async def vset_dms_del(self, ctx): + """ + Delete a DM message + """ + async with self.config.guild(ctx.guild).dm_msgs() as dm_msgs: + if not dm_msgs: + await ctx.send(warning("No DM messages defined.")) + return + pages = self.format_list(dm_msgs) + for page in pages: + await ctx.send(box(page)) + + await ctx.send("Please choose the number of the message to delete.") + pred = MessagePredicate.contained_in([str(i + 1) for i in range(len(dm_msgs))], ctx=ctx) + try: + await self.bot.wait_for("message", check=pred, timeout=60) + except asyncio.TimeoutError: + await ctx.send("Took too long, cancelled.") + return + + del dm_msgs[pred.result] + + await ctx.tick() + + @commands.command(name="sendcards") + @checks.admin_or_permissions(administrator=True) + @commands.guild_only() + async def valentine_cards(self, ctx): + """ + Send out valentine cards! + + Everyone in the server with DMs enabled will receive a custom message and card! + Failed DMs will be sent in the fallback channel, if available. + """ + # update members + _guilds = [g for g in self.bot.guilds if g.large and not (g.chunked or g.unavailable)] + await self.bot.request_offline_members(*_guilds) + + members = [m for m in ctx.guild.members if m != ctx.guild.me] + + fallback = ctx.guild.get_channel(await self.config.guild(ctx.guild).fallback_channel()) + + pred = MessagePredicate.yes_or_no(ctx) + if not fallback: + extra = warning("\n\nThere is no fallback channel set, you should set it before running this command.") + else: + extra = "" + + await ctx.send(f"Are you sure you want to send cards to {len(members)} members?{extra}") + try: + await self.bot.wait_for("message", check=pred, timeout=60) + except asyncio.TimeoutError: + await ctx.send("Took too long, cancelled.") + return + + if not pred.result: + await ctx.send("Cancelled.") + return + + # lets gooooo + dm_msgs = await self.config.guild(ctx.guild).dm_msgs() + cards = await self.config.guild(ctx.guild).cards() + update_msg = await ctx.send(f"Processed `1` out of `{len(members)}` members.") + failed = 0 + for i, member in enumerate(members): + msg = random.choice(dm_msgs) + card = random.choice(cards) + + try: + await member.send(msg) + await asyncio.sleep(0.01) + await member.send(card) + except discord.HTTPException: + if fallback: + await fallback.send(msg) + await asyncio.sleep(0.01) + await fallback.send(f"{member.mention}\n{card}") + failed += 1 + + if i % 10 == 0: + await update_msg.edit(content=f"Processed `{i+1}` out of `{len(members)}` members.") + + if failed: + await update_msg.edit( + content=f"DMed `{len(members) - failed}` members successfully and failed to send a DM to `{failed}` members, they were sent in the fallback channel if set." + ) + else: + await update_msg.edit(content=f"DMed `{len(members)}` members successfully.") + + async def red_delete_data_for_user( + self, + *, + requester: Literal["discord_deleted_user", "owner", "user", "user_strict"], + user_id: int, + ): + pass