Brandon209-Red-bot-Cogs/nitroemoji/nitroemoji.py

371 lines
13 KiB
Python

from redbot.core.utils.chat_formatting import *
from redbot.core import Config, checks, commands, bank
from redbot.core.data_manager import cog_data_path
from typing import Literal
import discord
import aiohttp
import PIL
import os
class NitroEmoji(commands.Cog):
"""
Reward nitro boosters with a custom emoji.
Can also set roles that give emojis as well that stacks with boosting
"""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=123859659843, force_registration=True)
# roles maps: role id (str) -> number of allowed emojis (int)
default_guild = {"channel": None, "disabled": False, "roles": {}}
default_member = {"emojis": []}
self.config.register_guild(**default_guild)
self.config.register_member(**default_member)
async def initialize(self):
for member in self.bot.get_all_members():
async with self.config.member(member).emojis() as data:
to_remove = []
for e in data:
emoji = self.find_emoji(member.guild, e)
# clean removed emojis if bot is down
if not emoji:
to_remove.append(e)
for r in to_remove:
data.remove(r)
async def get_boosts(self, member: discord.Member):
# this will return number of boosts once api supports it
# for now, set custom roles that give how ever emojis set
# boosting stacks with custom roles and
# custom roles stack with each other
custom_roles = await self.config.guild(member.guild).roles()
members_roles = [str(r.id) for r in member.roles]
num_emojis = 0
for c_role in custom_roles.keys():
if c_role in members_roles:
num_emojis += custom_roles[c_role]
# TODO: add in checking for multiple emojis once supported
if member.premium_since is not None:
num_emojis += 1
return num_emojis
def find_emoji(self, guild, name):
emoji = self.bot.get_emoji(name)
# find by name:
if not emoji:
emoji = discord.utils.get(guild.emojis, name=name)
return emoji
async def add_emoji(self, member, name, attachment_or_url, reason=None):
path = str(cog_data_path(cog_instance=self))
path = os.path.join(path, str(member.id) + name)
if isinstance(attachment_or_url, discord.Attachment):
await attachment_or_url.save(path)
elif isinstance(attachment_or_url, str):
async with aiohttp.ClientSession(loop=self.bot.loop) as session:
async with session.get(attachment_or_url) as r:
if r.status == 200:
with open(path, "wb") as f:
f.write(await r.read())
# verify image
im = PIL.Image.open(path)
im.verify()
# upload emoji
with open(path, "rb") as f:
emoji = await member.guild.create_custom_emoji(name=name, image=f.read())
os.remove(path)
async with self.config.member(member).emojis() as e:
e.append(emoji.id)
channel = await self.config.guild(member.guild).channel()
channel = member.guild.get_channel(channel)
if not channel:
return
embed = discord.Embed(title="Custom Emoji Added", colour=member.colour)
embed.set_footer(text="User ID:{}".format(member.id))
embed.set_author(name=str(member), url=emoji.url)
embed.set_thumbnail(url=emoji.url)
if reason:
embed.add_field(name="Reason", value=reason)
await channel.send(embed=embed)
async def del_emoji(self, guild, member, emoji=None, reason=None):
channel = await self.config.guild(member.guild).channel()
channel = member.guild.get_channel(channel)
if channel:
embed = discord.Embed(title="Custom Emoji Removed", colour=member.colour)
embed.set_footer(text="User ID:{}".format(member.id))
embed.set_author(name=str(member), url=emoji.url)
embed.set_thumbnail(url=emoji.url)
embed.set_author(name=str(member))
if reason:
embed.add_field(name="Reason", value=reason)
await channel.send(embed=embed)
if emoji:
try:
await emoji.delete()
except:
pass
async with self.config.member(member).emojis() as e:
e.remove(emoji.id)
@commands.group(name="nitroset")
@commands.guild_only()
@checks.admin()
async def nitroset(self, ctx):
"""Manage nitro emoji settings."""
pass
@nitroset.command(name="channel")
async def nitroset_channel(self, ctx, channel: discord.TextChannel):
"""
Set the channel to log nitro events.
Logs boosts, unboosts, and added emojis.
"""
await self.config.guild(ctx.guild).channel.set(channel.id)
await ctx.tick()
@nitroset.command(name="disable")
async def nitroset_disable(self, ctx, *, on_off: bool = None):
"""
Disable users from adding more emojis.
Users can still remove and list their own emojis.
"""
if on_off is None:
curr = await self.config.guild(ctx.guild).disabled()
msg = "enabled" if not curr else "disabled"
await ctx.send(f"Nitro emojis is {msg}.")
return
await self.config.guild(ctx.guild).disabled.set(on_off)
await ctx.tick()
@nitroset.command(name="role")
async def nitroset_roles(self, ctx, num_emojis: int, *, role: discord.Role):
"""
Set the amount of emojis role is allowed to add.
Set to 0 to remove role.
"""
async with self.config.guild(ctx.guild).roles() as roles:
if num_emojis == 0:
try:
del roles[str(role.id)]
except:
pass
else:
roles[str(role.id)] = num_emojis
await ctx.tick()
@nitroset.command(name="list")
async def nitroset_list(self, ctx):
"""
List roles and the number of emojis they give.
"""
msg = ""
async with self.config.guild(ctx.guild).roles() as roles:
if roles:
msg += "Current roles:\n"
for role_id in list(roles.keys()):
role = ctx.guild.get_role(int(role_id))
if not role: # remove deleted roles silently
del roles[role_id]
continue
msg += f"{role.name}: {roles[role_id]}\n"
for page in pagify(msg):
await ctx.send(box(page))
else:
await ctx.send("No roles defined.")
@commands.group(name="nitroemoji")
@checks.bot_has_permissions(manage_emojis=True)
async def nitroemoji(self, ctx):
"""
Manage your emojis if you boosted the server, or have a special role
"""
pass
@nitroemoji.command(name="add")
async def nitroemoji_add(self, ctx, name: str, *, url: str = None):
"""
Add an emoji to the server, if you boosted or have a special role
Can only add an emoji for every boost you have in the server.
"""
disabled = await self.config.guild(ctx.guild).disabled()
if disabled:
await ctx.send("Sorry, adding emojis is currently disabled right now.")
return
curr = await self.config.member(ctx.author).emojis()
boosts = await self.get_boosts(ctx.author)
if boosts and len(curr) < boosts:
try:
if url:
emoji = await self.add_emoji(ctx.author, name, url)
else:
emoji = await self.add_emoji(ctx.author, name, ctx.message.attachments[0])
await ctx.tick()
except discord.errors.HTTPException as e:
await ctx.send(e.text)
except PIL.UnidentifiedImageError:
await ctx.send("That is not a valid picture! Pictures must be in PNG, JPEG, or GIF format.")
except:
await ctx.send(
"Something went wrong, make sure to add a valid picture (PNG, JPG, or GIF) of the right size (256KB) and a valid name."
)
return
elif not boosts:
await ctx.send("Sorry, you need to be a nitro booster or have a special role to add an emoji!")
elif curr:
await ctx.send(
"You already have the maximum number of custom emojis, please delete one first before adding another one."
)
@nitroemoji.command(name="rem")
async def nitroemoji_rem(self, ctx, name: str):
"""
Remove an emoji to the server, if you boosted or have a special role
"""
curr = await self.config.member(ctx.author).emojis()
emoji = self.find_emoji(ctx.guild, name)
if emoji:
if emoji.id in curr:
await self.del_emoji(ctx.guild, ctx.author, emoji=emoji, reason="Removed by user.")
await ctx.tick()
else:
await ctx.send("That isn't your custom emoji.")
else:
await ctx.send(warning("Emoji not found."))
@nitroemoji.command(name="list")
async def nitroemoji_list(self, ctx):
"""
List your custom emojis in the server
"""
curr = await self.config.member(ctx.author).emojis()
boosts = await self.get_boosts(ctx.author)
msg = f"Number of custom emojis used: {len(curr)}/{boosts}\n"
if curr:
msg += "Current emojis:\n"
for emoji in curr:
emoji = self.find_emoji(ctx.guild, emoji)
if not emoji:
continue
msg += f"{emoji.url}\n"
else:
msg += "`You have no custom emojis.`"
for page in pagify(msg):
await ctx.send(page)
@commands.Cog.listener()
async def on_member_update(self, before, after):
if await self.bot.cog_disabled_in_guild(self, after.guild):
return
# check if they stopped boosting
if (before.premium_since != after.premium_since and after.premium_since is None) or before.roles != after.roles:
boosts = await self.get_boosts(after)
emojis = await self.config.member(after).emojis()
if len(emojis) > boosts:
to_delete = []
for i in range(len(emojis) - (len(emojis) - boosts), len(emojis)):
to_delete.append(emojis[i])
for emoji in to_delete:
emoji = self.find_emoji(after.guild, emoji)
if not emoji:
continue
reason = ""
if before.premium_since != after.premium_since and after.premium_since is None:
reason += "Stopped boosting."
if before.roles != after.roles:
broles = set(before.roles)
aroles = set(after.roles)
removed = broles - aroles
reason += f"\nLost roles: {humanize_list(list(removed))}"
await self.del_emoji(after.guild, after, emoji=emoji, reason=reason)
@commands.Cog.listener()
async def on_member_leave(self, member):
if await self.bot.cog_disabled_in_guild(self, member.guild):
return
# remove all member emojis on leave
emojis = await self.config.member(member).emojis()
for emoji in emojis:
emoji = self.find_emoji(member.guild, emoji)
if not emoji:
continue
await self.del_emoji(member.guild, member, emoji=emoji, reason="Member left.")
await self.config.member(member).clear()
@commands.Cog.listener()
async def on_guild_emojis_update(self, guild, before, after):
if await self.bot.cog_disabled_in_guild(self, guild):
return
b_e = set(before)
a_e = set(after)
diff = b_e - a_e
if diff:
for e in diff:
user = None
try:
async for entry in guild.audit_logs(limit=2):
if entry.action is discord.AuditLogAction.emoji_delete:
# if the bot didnt delete emoji
if entry.target.id != guild.me.id:
user = entry.user
except:
continue
# sanity check
if not user or user.id == guild.me.id:
continue
for member in guild.members:
curr = await self.config.member(member).emojis()
try:
async with self.config.member(member).emojis() as curr:
curr.remove(e.id)
await self.del_emoji(
guild, member, emoji=e, reason=f"Manually deleted by admin {user.name} (id: {user.id})"
)
break
except ValueError:
pass
async def red_delete_data_for_user(
self,
*,
requester: Literal["discord_deleted_user", "owner", "user", "user_strict"],
user_id: int,
):
pass