mirror of
https://github.com/brandons209/Red-bot-Cogs.git
synced 2024-09-28 07:11:47 +12:00
numerous bug fixes, additions, and edits. new cog: nitroemoji
This commit is contained in:
parent
c478215cbc
commit
e2dd8fed2a
13 changed files with 591 additions and 49 deletions
|
@ -48,6 +48,10 @@ More admin commands that provide various functionality.
|
||||||
- List all users with a role/roles quickly and easily.
|
- List all users with a role/roles quickly and easily.
|
||||||
|
|
||||||
|
|
||||||
|
#### Nitro Emoji
|
||||||
|
Allows nitro boosters to add one emoji to your server. Log's additions and removal of custom emojis to a channel. Can turn this off to stop more people from adding, but those who added can remove their emoji.
|
||||||
|
|
||||||
|
|
||||||
#### Pony
|
#### Pony
|
||||||
Search derpibooru for pony images. Ported from [Alzarath](https://github.com/Alzarath/Booru-Cogs).
|
Search derpibooru for pony images. Ported from [Alzarath](https://github.com/Alzarath/Booru-Cogs).
|
||||||
**Features:**
|
**Features:**
|
||||||
|
@ -75,7 +79,6 @@ Modified from [Sinbad](https://github.com/mikeshardmind/SinbadCogs).
|
||||||
- Adds in subscription based roles which renew every customized interval.
|
- Adds in subscription based roles which renew every customized interval.
|
||||||
- Allows settings messages through DM to users who obtain a specific role (such as role info).
|
- Allows settings messages through DM to users who obtain a specific role (such as role info).
|
||||||
- Renames srole to selfrole and removes Red's default selfrole.
|
- Renames srole to selfrole and removes Red's default selfrole.
|
||||||
**In progress:**
|
|
||||||
- Makes listing roles a bit prettier.
|
- Makes listing roles a bit prettier.
|
||||||
- Allow setting roles to automatically add on guild join.
|
- Allow setting roles to automatically add on guild join.
|
||||||
- Enhance exclusive roles, allow setting custom role groups where the bot enforces only one role to be on a user at a time, even if it isn't a selfrole. The bot will automatically remove the old role if a new role from the same group is added. Also lists name of role group in list command to make it clearer.
|
- Enhance exclusive roles, allow setting custom role groups where the bot enforces only one role to be on a user at a time, even if it isn't a selfrole. The bot will automatically remove the old role if a new role from the same group is added. Also lists name of role group in list command to make it clearer.
|
||||||
|
|
|
@ -154,10 +154,10 @@ class ActivityLogger(commands.Cog):
|
||||||
for guild in guilds:
|
for guild in guilds:
|
||||||
self.cache[guild.id] = self.default_guild.copy()
|
self.cache[guild.id] = self.default_guild.copy()
|
||||||
|
|
||||||
if not channel_data:
|
guilds = self.bot.guilds
|
||||||
guilds = self.bot.guilds
|
for guild in guilds:
|
||||||
for guild in guilds:
|
for channel in guild.channels:
|
||||||
for channel in guild.channels:
|
if not channel.id in self.cache.keys():
|
||||||
self.cache[channel.id] = self.default_channel.copy()
|
self.cache[channel.id] = self.default_channel.copy()
|
||||||
|
|
||||||
for guild in self.bot.guilds:
|
for guild in self.bot.guilds:
|
||||||
|
|
19
disable/info.json
Normal file
19
disable/info.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"author": [
|
||||||
|
"Brandons209"
|
||||||
|
],
|
||||||
|
"bot_version": [
|
||||||
|
3,
|
||||||
|
3,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"description": "Small cog that allows disabling all bot commands for everyone except Admins/owner in a guild. Nice for things like bot setup. You can also set the error message when a person trys using a command when its disabled.",
|
||||||
|
"hidden": false,
|
||||||
|
"install_msg": "Thank you for using this cog!",
|
||||||
|
"requirements": [],
|
||||||
|
"short": "Disable all commands for everyone except admins.",
|
||||||
|
"tags": [
|
||||||
|
"brandons209",
|
||||||
|
"redbot"
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
from . import core
|
from . import core
|
||||||
from cog_shared.sinbad_libs import extra_setup
|
|
||||||
|
|
||||||
|
|
||||||
@extra_setup
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
bot.add_cog(core.EconomyTrickle(bot))
|
bot.add_cog(core.EconomyTrickle(bot))
|
||||||
|
|
|
@ -4,6 +4,8 @@ from redbot.core import commands
|
||||||
|
|
||||||
configable_guild_defaults = {
|
configable_guild_defaults = {
|
||||||
"interval": 5,
|
"interval": 5,
|
||||||
|
"fail_rate": 0.2,
|
||||||
|
"decay_rate": 0.5,
|
||||||
"level_xp_base": 100,
|
"level_xp_base": 100,
|
||||||
"xp_lv_increase": 50,
|
"xp_lv_increase": 50,
|
||||||
"maximum_level": None,
|
"maximum_level": None,
|
||||||
|
@ -56,4 +58,12 @@ def settings_converter(user_input: str) -> dict:
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
raise commands.BadArgument(f"{value} must be a non-negative integer value or `null`")
|
raise commands.BadArgument(f"{value} must be a non-negative integer value or `null`")
|
||||||
|
|
||||||
|
for value in ("fail_rate", "decay_rate"):
|
||||||
|
if value in args:
|
||||||
|
try:
|
||||||
|
assert args[value] >= 0
|
||||||
|
assert args[value] <= 1
|
||||||
|
except AssertionError:
|
||||||
|
raise commands.BadArgument(f"{value} must be a decimal value between 0 and 1.")
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
|
@ -4,6 +4,7 @@ import contextlib
|
||||||
from typing import no_type_check, Union
|
from typing import no_type_check, Union
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
import random
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from redbot.core import commands, checks
|
from redbot.core import commands, checks
|
||||||
|
@ -19,7 +20,7 @@ class EconomyTrickle(commands.Cog):
|
||||||
Automatic Economy gains for active users
|
Automatic Economy gains for active users
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "2.0.2"
|
__version__ = "2.1.0"
|
||||||
|
|
||||||
def __init__(self, bot, *args, **kwargs):
|
def __init__(self, bot, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -30,6 +31,7 @@ class EconomyTrickle(commands.Cog):
|
||||||
mode="blacklist",
|
mode="blacklist",
|
||||||
blacklist=[],
|
blacklist=[],
|
||||||
whitelist=[],
|
whitelist=[],
|
||||||
|
min_voice_members=2,
|
||||||
**configable_guild_defaults,
|
**configable_guild_defaults,
|
||||||
# custom_level_table={}, # TODO
|
# custom_level_table={}, # TODO
|
||||||
)
|
)
|
||||||
|
@ -55,21 +57,24 @@ class EconomyTrickle(commands.Cog):
|
||||||
|
|
||||||
while self is self.bot.get_cog(self.__class__.__name__):
|
while self is self.bot.get_cog(self.__class__.__name__):
|
||||||
await asyncio.sleep(60)
|
await asyncio.sleep(60)
|
||||||
now = datetime.utcnow()
|
|
||||||
|
|
||||||
data = await self.config.all_guilds()
|
data = await self.config.all_guilds()
|
||||||
|
|
||||||
for g in self.bot.guilds:
|
for g in self.bot.guilds:
|
||||||
if g.id in data and data[g.id]["active"]:
|
if g.id in data and data[g.id]["active"]:
|
||||||
minutes[g] += 1
|
minutes[g] += 1
|
||||||
if minutes[g] % data[g.id]["interval"]:
|
if minutes[g] % data[g.id]["interval"] == 0:
|
||||||
|
minutes[g] = 0
|
||||||
|
print(f"processing...{minutes[g]}, {data[g.id]['interval']}")
|
||||||
|
now = datetime.utcnow()
|
||||||
tsk = self.bot.loop.create_task(self.do_rewards_for(g, now, data[g.id]))
|
tsk = self.bot.loop.create_task(self.do_rewards_for(g, now, data[g.id]))
|
||||||
self.extra_tasks.append(tsk)
|
self.extra_tasks.append(tsk)
|
||||||
|
|
||||||
async def do_rewards_for(self, guild: discord.Guild, now: datetime, data: dict):
|
async def do_rewards_for(self, guild: discord.Guild, now: datetime, data: dict):
|
||||||
|
|
||||||
after = now - timedelta(minutes=data["interval"])
|
after = now - timedelta(minutes=data["interval"], seconds=10)
|
||||||
|
print(f"after: {after}")
|
||||||
|
voice_mem = await self.config.guild(guild).min_voice_members()
|
||||||
if data["mode"] == "blacklist":
|
if data["mode"] == "blacklist":
|
||||||
|
|
||||||
def mpred(m: discord.Message):
|
def mpred(m: discord.Message):
|
||||||
|
@ -77,7 +82,7 @@ class EconomyTrickle(commands.Cog):
|
||||||
|
|
||||||
def vpred(mem: discord.Member):
|
def vpred(mem: discord.Member):
|
||||||
with contextlib.suppress(AttributeError):
|
with contextlib.suppress(AttributeError):
|
||||||
return mem.voice.channel.id not in data["blacklist"] and not mem.bot
|
return len(mem.voice.channel.members) > voice_mem and mem.voice.channel.id not in data["blacklist"] and not mem.bot
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
@ -86,16 +91,42 @@ class EconomyTrickle(commands.Cog):
|
||||||
|
|
||||||
def vpred(mem: discord.Member):
|
def vpred(mem: discord.Member):
|
||||||
with contextlib.suppress(AttributeError):
|
with contextlib.suppress(AttributeError):
|
||||||
return mem.voice.channel.id in data["whitelist"] and not mem.bot
|
return len(mem.voice.channel.members) > voice_mem and mem.voice.channel.id in data["whitelist"] and not mem.bot
|
||||||
|
|
||||||
has_active_message = set(self.recordhandler.get_active_for_guild(guild=guild, after=after, message_check=mpred))
|
has_active_message = set(self.recordhandler.get_active_for_guild(guild=guild, after=after, message_check=mpred))
|
||||||
|
|
||||||
is_active_voice = {m for m in guild.members if vpred(m)}
|
is_active_voice = {m for m in guild.members if vpred(m)}
|
||||||
|
print(f"active: {[m.name for m in has_active_message]}")
|
||||||
is_active = has_active_message | is_active_voice
|
is_active = has_active_message | is_active_voice
|
||||||
|
|
||||||
for member in is_active:
|
# take exp away from inactive users
|
||||||
|
for member in guild.members:
|
||||||
|
if member in is_active:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# loose exp per interval
|
||||||
|
xp = min(data["xp_per_interval"] * data["decay_rate"], 1)
|
||||||
|
xp = await self.config.member(member).xp() - xp
|
||||||
|
xp = max(xp, 0)
|
||||||
|
await self.config.member(member).xp.set(xp)
|
||||||
|
|
||||||
|
# update level on these users
|
||||||
|
level, next_needed = 0, data["level_xp_base"]
|
||||||
|
|
||||||
|
while xp >= next_needed:
|
||||||
|
level += 1
|
||||||
|
xp -= next_needed
|
||||||
|
next_needed += data["xp_lv_increase"]
|
||||||
|
|
||||||
|
if data["maximum_level"] is not None:
|
||||||
|
level = min(data["maximum_level"], level)
|
||||||
|
|
||||||
|
await self.config.member(member).level.set(level)
|
||||||
|
|
||||||
|
for member in is_active:
|
||||||
|
# failed for this member, skip
|
||||||
|
if data["fail_rate"] > random.random():
|
||||||
|
continue
|
||||||
# xp processing first
|
# xp processing first
|
||||||
xp = data["xp_per_interval"]
|
xp = data["xp_per_interval"]
|
||||||
if member in has_active_message:
|
if member in has_active_message:
|
||||||
|
@ -155,15 +186,17 @@ class EconomyTrickle(commands.Cog):
|
||||||
|
|
||||||
@ect.command()
|
@ect.command()
|
||||||
@no_type_check
|
@no_type_check
|
||||||
async def setstuff(self, ctx, *, data: settings_converter):
|
async def setstuff(self, ctx, *, data: settings_converter = None):
|
||||||
"""
|
"""
|
||||||
Set other variables
|
Set other variables
|
||||||
|
|
||||||
format for this (and defaults):
|
format *example* for this (and defaults):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
bonus_per_level: 5
|
bonus_per_level: 5
|
||||||
econ_per_interval: 20
|
econ_per_interval: 20
|
||||||
|
fail_rate: 0.2
|
||||||
|
decay_rate: 0.5
|
||||||
extra_message_xp: 0
|
extra_message_xp: 0
|
||||||
extra_voice_xp: 0
|
extra_voice_xp: 0
|
||||||
interval: 5
|
interval: 5
|
||||||
|
@ -174,6 +207,17 @@ class EconomyTrickle(commands.Cog):
|
||||||
xp_per_interval: 10
|
xp_per_interval: 10
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
if not data:
|
||||||
|
data = await self.config.guild(ctx.guild).all()
|
||||||
|
keys = list(configable_guild_defaults.keys())
|
||||||
|
msg = "Current data: (run `help trickleset setstuff to set`)\n```yaml\n"
|
||||||
|
for key in keys:
|
||||||
|
msg += f"{key}: {data[key]}\n"
|
||||||
|
msg += "```"
|
||||||
|
|
||||||
|
await ctx.send(msg)
|
||||||
|
return
|
||||||
|
|
||||||
for k, v in data.items():
|
for k, v in data.items():
|
||||||
await self.config.guild(ctx.guild).get_attr(k).set(v)
|
await self.config.guild(ctx.guild).get_attr(k).set(v)
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
@ -191,6 +235,23 @@ class EconomyTrickle(commands.Cog):
|
||||||
await self.config.guild(ctx.guild).mode.set(mode)
|
await self.config.guild(ctx.guild).mode.set(mode)
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
|
||||||
|
@ect.command(name="voice")
|
||||||
|
async def rset_voicemem(self, ctx, min_voice_members: int = 0):
|
||||||
|
"""
|
||||||
|
Minimum number of voice members needed to count as active.
|
||||||
|
|
||||||
|
If users are in a voice chat, only trickle if there are at least min_voice_members
|
||||||
|
in there.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if min_voice_members < 1:
|
||||||
|
curr = await self.config.guild(ctx.guild).min_voice_members()
|
||||||
|
await ctx.send(f"Current: {curr}")
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.config.guild(ctx.guild).min_voice_members.set(min_voice_members)
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
@ect.command(name="addchan")
|
@ect.command(name="addchan")
|
||||||
async def rset_add_chan(self, ctx, *channels: Union[discord.TextChannel, discord.VoiceChannel]):
|
async def rset_add_chan(self, ctx, *channels: Union[discord.TextChannel, discord.VoiceChannel]):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -47,7 +47,7 @@ class MoreAdmin(commands.Cog):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.config = Config.get_conf(self, identifier=213438438248, force_registration=True)
|
self.config = Config.get_conf(self, identifier=213438438248, force_registration=True)
|
||||||
|
|
||||||
default_guild = {"user_count_channel": None, "sus_user_channel": None, "sus_user_threshold": None}
|
default_guild = {"user_count_channel": None, "sus_user_channel": None, "sus_user_threshold": None, "prefixes": []}
|
||||||
|
|
||||||
default_role = {"addable": []} # role ids who can add this role
|
default_role = {"addable": []} # role ids who can add this role
|
||||||
self.config.register_role(**default_role)
|
self.config.register_role(**default_role)
|
||||||
|
@ -58,6 +58,11 @@ class MoreAdmin(commands.Cog):
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
await self.register_casetypes()
|
await self.register_casetypes()
|
||||||
|
for guild in self.bot.guilds:
|
||||||
|
async with self.config.guild(guild).prefixes() as prefixes:
|
||||||
|
if not prefixes:
|
||||||
|
curr = await self.bot.get_valid_prefixes()
|
||||||
|
prefixes.extend(curr)
|
||||||
|
|
||||||
def cog_unload(self):
|
def cog_unload(self):
|
||||||
self.user_task.cancel()
|
self.user_task.cancel()
|
||||||
|
@ -65,19 +70,18 @@ class MoreAdmin(commands.Cog):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def register_casetypes():
|
async def register_casetypes():
|
||||||
# register mod case
|
# register mod case
|
||||||
punish_case = {
|
purge_case = {
|
||||||
"name": "Purge",
|
"name": "Purge",
|
||||||
"default_setting": True,
|
"default_setting": True,
|
||||||
"image": "\N{WOMANS BOOTS}",
|
"image": "\N{WOMANS BOOTS}",
|
||||||
"case_str": "Purge",
|
"case_str": "Purge",
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
await modlog.register_casetype(**punish_case)
|
await modlog.register_casetype(**purge_case)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
async def find_last_message(self, guild: discord.Guild, role: discord.Role, include_bot_commands: bool):
|
||||||
async def find_last_message(guild: discord.Guild, role: discord.Role):
|
|
||||||
"""
|
"""
|
||||||
Finds last message of EVERY user with role in a guild.
|
Finds last message of EVERY user with role in a guild.
|
||||||
**WARNING VERY SLOW AND COSTLY OPERATION!**
|
**WARNING VERY SLOW AND COSTLY OPERATION!**
|
||||||
|
@ -86,12 +90,21 @@ class MoreAdmin(commands.Cog):
|
||||||
"""
|
"""
|
||||||
last_msgs = {}
|
last_msgs = {}
|
||||||
text_channels = [channel for channel in guild.channels if isinstance(channel, discord.TextChannel)]
|
text_channels = [channel for channel in guild.channels if isinstance(channel, discord.TextChannel)]
|
||||||
|
prefixes = await self.config.guild(guild).prefixes()
|
||||||
for channel in text_channels:
|
for channel in text_channels:
|
||||||
async for message in channel.history(limit=None):
|
async for message in channel.history(limit=None):
|
||||||
if isinstance(message.author, discord.Member) and role in message.author.roles:
|
if isinstance(message.author, discord.Member) and role in message.author.roles:
|
||||||
if message.author.id not in last_msgs.keys():
|
# prefix check
|
||||||
|
skip = False
|
||||||
|
if include_bot_commands:
|
||||||
|
for prefix in prefixes:
|
||||||
|
if message.content and prefix == message.content[:len(prefix)]:
|
||||||
|
skip = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if message.author.id not in last_msgs.keys() and not skip:
|
||||||
last_msgs[message.author.id] = message
|
last_msgs[message.author.id] = message
|
||||||
else:
|
elif not skip:
|
||||||
curr_last = last_msgs[message.author.id]
|
curr_last = last_msgs[message.author.id]
|
||||||
if message.created_at > curr_last.created_at:
|
if message.created_at > curr_last.created_at:
|
||||||
last_msgs[message.author.id] = message
|
last_msgs[message.author.id] = message
|
||||||
|
@ -209,6 +222,26 @@ class MoreAdmin(commands.Cog):
|
||||||
await self.config.guild(ctx.guild).sus_user_threshold.set(int(threshold.total_seconds()))
|
await self.config.guild(ctx.guild).sus_user_threshold.set(int(threshold.total_seconds()))
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
|
||||||
|
@adminset.command(name="prefixes")
|
||||||
|
async def adminset_prefixes(self, ctx, *, prefixes: str = None):
|
||||||
|
"""
|
||||||
|
Set prefixes for bot commands to check for when purging.
|
||||||
|
|
||||||
|
Seperate prefixes with spaces.
|
||||||
|
|
||||||
|
Used for purge command.
|
||||||
|
"""
|
||||||
|
if not prefixes:
|
||||||
|
prefixes = await self.config.guild(ctx.guild).prefixes()
|
||||||
|
curr = [f"`{p}`" for p in prefixes]
|
||||||
|
await ctx.send("Current Prefixes: " + humanize_list(curr))
|
||||||
|
return
|
||||||
|
|
||||||
|
prefixes = [p for p in prefixes.split(" ")]
|
||||||
|
await self.config.guild(ctx.guild).prefixes.set(prefixes)
|
||||||
|
prefixes = [f"`{p}`" for p in prefixes]
|
||||||
|
await ctx.send("Prefixes set to: " + humanize_list(prefixes))
|
||||||
|
|
||||||
@adminset.command(name="addable")
|
@adminset.command(name="addable")
|
||||||
async def adminset_addable(self, ctx, role: discord.Role, *, role_list: str = None):
|
async def adminset_addable(self, ctx, role: discord.Role, *, role_list: str = None):
|
||||||
"""
|
"""
|
||||||
|
@ -358,7 +391,7 @@ class MoreAdmin(commands.Cog):
|
||||||
@commands.command(name="purge")
|
@commands.command(name="purge")
|
||||||
@checks.admin_or_permissions(administrator=True)
|
@checks.admin_or_permissions(administrator=True)
|
||||||
@checks.bot_has_permissions(kick_members=True)
|
@checks.bot_has_permissions(kick_members=True)
|
||||||
async def purge(self, ctx, role: discord.Role, check_messages: bool = True, *, threshold: str = None):
|
async def purge(self, ctx, role: discord.Role, check_messages: bool = True, include_bot_commands: bool = False, *, threshold: str = None):
|
||||||
"""
|
"""
|
||||||
Purge inactive users with role.
|
Purge inactive users with role.
|
||||||
|
|
||||||
|
@ -368,6 +401,9 @@ class MoreAdmin(commands.Cog):
|
||||||
If check_messages is yes/true/1 then purging is dictated by the user's last message.
|
If check_messages is yes/true/1 then purging is dictated by the user's last message.
|
||||||
If check_messages is no/false/0 then purging is dictated by the user's join date.
|
If check_messages is no/false/0 then purging is dictated by the user's join date.
|
||||||
|
|
||||||
|
If checking last message and bot is yes/true/1 then the bot won't count bot include_bot_commands as a valid last message for purge.
|
||||||
|
**Make sure to set prefixes with [p]adminset**
|
||||||
|
|
||||||
Threshold should be an interval.
|
Threshold should be an interval.
|
||||||
|
|
||||||
Intervals look like:
|
Intervals look like:
|
||||||
|
@ -389,7 +425,7 @@ class MoreAdmin(commands.Cog):
|
||||||
errored = []
|
errored = []
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
if check_messages:
|
if check_messages:
|
||||||
last_msgs = await self.find_last_message(guild, role)
|
last_msgs = await self.find_last_message(guild, role, include_bot_commands)
|
||||||
|
|
||||||
for member in guild.members:
|
for member in guild.members:
|
||||||
if role in member.roles:
|
if role in member.roles:
|
||||||
|
|
6
nitroemoji/__init__.py
Normal file
6
nitroemoji/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from .nitroemoji import NitroEmoji
|
||||||
|
|
||||||
|
async def setup(bot):
|
||||||
|
n = NitroEmoji(bot)
|
||||||
|
await n.initialize()
|
||||||
|
bot.add_cog(n)
|
21
nitroemoji/info.json
Normal file
21
nitroemoji/info.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"author": [
|
||||||
|
"Brandons209"
|
||||||
|
],
|
||||||
|
"bot_version": [
|
||||||
|
3,
|
||||||
|
3,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"description": "Allow nitro boosters to add an emoji to the server, with channel logging.",
|
||||||
|
"hidden": false,
|
||||||
|
"install_msg": "Thank you for using this cog! Please make sure to set the log channel using `[p]nitroset`",
|
||||||
|
"requirements": ["aiohttp"],
|
||||||
|
"short": "Allow boosters to have a server emoji.",
|
||||||
|
"tags": [
|
||||||
|
"brandons209",
|
||||||
|
"nitro",
|
||||||
|
"boost",
|
||||||
|
"emoji"
|
||||||
|
]
|
||||||
|
}
|
257
nitroemoji/nitroemoji.py
Normal file
257
nitroemoji/nitroemoji.py
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
from redbot.core.utils.chat_formatting import *
|
||||||
|
from redbot.core import Config, checks, commands, bank
|
||||||
|
from redbot.core.data_manager import cog_data_path
|
||||||
|
import discord
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import PIL
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class NitroEmoji(commands.Cog):
|
||||||
|
"""
|
||||||
|
Reward nitro boosters with a custom emoji.
|
||||||
|
"""
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
self.config = Config.get_conf(self, identifier=123859659843, force_registration=True)
|
||||||
|
default_guild = {
|
||||||
|
"channel": None,
|
||||||
|
"disabled": False
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_boosts(member: discord.Member):
|
||||||
|
# this will return number of boosts once api supports it
|
||||||
|
return member.premium_since is not None
|
||||||
|
|
||||||
|
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)
|
||||||
|
embed.set_author(name=str(member))
|
||||||
|
|
||||||
|
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:
|
||||||
|
await emoji.delete()
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
@commands.group(name="nitroemoji")
|
||||||
|
@checks.bot_has_permissions(manage_emojis=True)
|
||||||
|
async def nitroemoji(self, ctx):
|
||||||
|
"""
|
||||||
|
Manage your emojis if you boosted the server.
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 = self.get_boosts(ctx.author)
|
||||||
|
# TODO: add in checking for multiple emojis once supported
|
||||||
|
if boosts and not curr:
|
||||||
|
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 to add an emoji!")
|
||||||
|
elif curr:
|
||||||
|
await ctx.send("You already have a custom emoji, please delete it 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.
|
||||||
|
"""
|
||||||
|
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()
|
||||||
|
msg = ""
|
||||||
|
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):
|
||||||
|
# check if they stopped boosting
|
||||||
|
if before.premium_since != after.premium_since and after.premium_since is None:
|
||||||
|
emojis = await self.config.member(after).emojis()
|
||||||
|
for emoji in emojis:
|
||||||
|
emoji = self.find_emoji(after.guild, emoji)
|
||||||
|
if not emoji:
|
||||||
|
continue
|
||||||
|
await self.del_emoji(after.guild, after, emoji=emoji, reason="Stopped boosting.")
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_guild_emojis_update(self, guild, before, after):
|
||||||
|
b_e = set(before)
|
||||||
|
a_e = set(after)
|
||||||
|
diff = b_e - a_e
|
||||||
|
if diff:
|
||||||
|
for e in diff:
|
||||||
|
for member in guild.premium_subscribers:
|
||||||
|
curr = await self.config.member(member).emojis()
|
||||||
|
if e.id in curr:
|
||||||
|
curr.remove(e.id)
|
||||||
|
await self.config.member(member).emojis.set(curr)
|
||||||
|
await self.del_emoji(guild, member, emoji=e, reason="Manually deleted by admin.")
|
||||||
|
break
|
|
@ -11,10 +11,10 @@ import discord
|
||||||
from discord.ext.commands import CogMeta as DPYCogMeta
|
from discord.ext.commands import CogMeta as DPYCogMeta
|
||||||
from redbot.core import checks, commands, bank
|
from redbot.core import checks, commands, bank
|
||||||
from redbot.core.config import Config
|
from redbot.core.config import Config
|
||||||
from redbot.core.utils.chat_formatting import box, pagify, warning
|
from redbot.core.utils.chat_formatting import box, pagify, warning, humanize_list
|
||||||
|
|
||||||
from .events import EventMixin
|
from .events import EventMixin
|
||||||
from .exceptions import RoleManagementException, PermissionOrHierarchyException
|
from .exceptions import RoleManagementException, PermissionOrHierarchyException, MissingRequirementsException, ConflictingRoleException
|
||||||
from .massmanager import MassManagementMixin
|
from .massmanager import MassManagementMixin
|
||||||
from .utils import UtilMixin, variation_stripper_re, parse_timedelta, parse_seconds
|
from .utils import UtilMixin, variation_stripper_re, parse_timedelta, parse_seconds
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ class CompositeMetaClass(DPYCogMeta, ABCMeta):
|
||||||
|
|
||||||
MIN_SUB_TIME = 3600
|
MIN_SUB_TIME = 3600
|
||||||
SLEEP_TIME = 300
|
SLEEP_TIME = 300
|
||||||
|
MAX_EMBED = 25
|
||||||
|
|
||||||
class RoleManagement(
|
class RoleManagement(
|
||||||
UtilMixin, MassManagementMixin, EventMixin, commands.Cog, metaclass=CompositeMetaClass,
|
UtilMixin, MassManagementMixin, EventMixin, commands.Cog, metaclass=CompositeMetaClass,
|
||||||
|
@ -62,7 +62,7 @@ class RoleManagement(
|
||||||
self.config = Config.get_conf(self, identifier=78631113035100160, force_registration=True)
|
self.config = Config.get_conf(self, identifier=78631113035100160, force_registration=True)
|
||||||
self.config.register_global(handled_variation=False, handled_full_str_emoji=False)
|
self.config.register_global(handled_variation=False, handled_full_str_emoji=False)
|
||||||
self.config.register_role(
|
self.config.register_role(
|
||||||
exclusive_to=[],
|
exclusive_to={},
|
||||||
requires_any=[],
|
requires_any=[],
|
||||||
requires_all=[],
|
requires_all=[],
|
||||||
sticky=False,
|
sticky=False,
|
||||||
|
@ -79,7 +79,7 @@ class RoleManagement(
|
||||||
self.config.register_custom(
|
self.config.register_custom(
|
||||||
"REACTROLE", roleid=None, channelid=None, guildid=None
|
"REACTROLE", roleid=None, channelid=None, guildid=None
|
||||||
) # ID : Message.id, str(React)
|
) # ID : Message.id, str(React)
|
||||||
self.config.register_guild(notify_channel=None, s_roles=[], free_roles=[])
|
self.config.register_guild(notify_channel=None, s_roles=[], free_roles=[], join_roles=[])
|
||||||
self._ready = asyncio.Event()
|
self._ready = asyncio.Event()
|
||||||
self._start_task: Optional[asyncio.Task] = None
|
self._start_task: Optional[asyncio.Task] = None
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
|
@ -155,6 +155,10 @@ class RoleManagement(
|
||||||
if not member: # clean absent members
|
if not member: # clean absent members
|
||||||
del role_data["subscribed_users"][user_id]
|
del role_data["subscribed_users"][user_id]
|
||||||
continue
|
continue
|
||||||
|
# make sure they still have the role
|
||||||
|
if role not in member.roles:
|
||||||
|
del role_data["subscribed_users"][user_id]
|
||||||
|
continue
|
||||||
# charge user
|
# charge user
|
||||||
cost = await self.config.role(role).cost()
|
cost = await self.config.role(role).cost()
|
||||||
currency_name = await bank.get_currency_name(guild)
|
currency_name = await bank.get_currency_name(guild)
|
||||||
|
@ -406,6 +410,56 @@ class RoleManagement(
|
||||||
await self.config.role(role).dm_msg.set(msg)
|
await self.config.role(role).dm_msg.set(msg)
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
|
||||||
|
@rgroup.group(name="join")
|
||||||
|
async def join_roles(self, ctx: GuildContext):
|
||||||
|
"""
|
||||||
|
Set roles to add to users on join.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@join_roles.command(name="add")
|
||||||
|
async def join_roles_add(self, ctx: GuildContext, *, role: discord.Role):
|
||||||
|
"""
|
||||||
|
Add a role to the join list.
|
||||||
|
"""
|
||||||
|
async with self.config.guild(ctx.guild).join_roles() as join_roles:
|
||||||
|
if role.id not in join_roles:
|
||||||
|
join_roles.append(role.id)
|
||||||
|
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@join_roles.command(name="rem")
|
||||||
|
async def join_roles_rem(self, ctx: GuildContext, *, role: discord.Role):
|
||||||
|
"""
|
||||||
|
Remove a role from the join list.
|
||||||
|
"""
|
||||||
|
async with self.config.guild(ctx.guild).join_roles() as join_roles:
|
||||||
|
try:
|
||||||
|
join_roles.remove(role.id)
|
||||||
|
except:
|
||||||
|
await ctx.send("Role not in join list!")
|
||||||
|
return
|
||||||
|
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@join_roles.command(name="list")
|
||||||
|
async def join_roles_list(self, ctx: GuildContext):
|
||||||
|
"""
|
||||||
|
List join roles.
|
||||||
|
"""
|
||||||
|
roles = await self.config.guild(ctx.guild).join_roles()
|
||||||
|
if not roles:
|
||||||
|
await ctx.send("No roles defined.")
|
||||||
|
return
|
||||||
|
roles = [ctx.guild.get_role(role) for role in roles]
|
||||||
|
missing = len([role for role in roles if role is None])
|
||||||
|
roles = [f"{i+1}.{role.name}" for i, role in enumerate(roles) if role is not None]
|
||||||
|
|
||||||
|
msg = "\n".join(sorted(roles))
|
||||||
|
msg = pagify(msg)
|
||||||
|
for m in msg:
|
||||||
|
await ctx.send(box(m))
|
||||||
|
|
||||||
@rgroup.command(name="viewrole")
|
@rgroup.command(name="viewrole")
|
||||||
async def rg_view_role(self, ctx: GuildContext, *, role: discord.Role):
|
async def rg_view_role(self, ctx: GuildContext, *, role: discord.Role):
|
||||||
"""
|
"""
|
||||||
|
@ -426,8 +480,12 @@ class RoleManagement(
|
||||||
rstring = ", ".join(r.name for r in ctx.guild.roles if r.id in rsets["requires_all"])
|
rstring = ", ".join(r.name for r in ctx.guild.roles if r.id in rsets["requires_all"])
|
||||||
output += f"\nThis role requires all of the following roles: {rstring}"
|
output += f"\nThis role requires all of the following roles: {rstring}"
|
||||||
if rsets["exclusive_to"]:
|
if rsets["exclusive_to"]:
|
||||||
rstring = ", ".join(r.name for r in ctx.guild.roles if r.id in rsets["exclusive_to"])
|
rstring = ""
|
||||||
output += f"\nThis role is mutually exclusive to the following roles: {rstring}"
|
for group, roles in rsets["exclusive_to"].items():
|
||||||
|
rstring = f"`{group}`: "
|
||||||
|
rstring += ", ".join(r.name for r in ctx.guild.roles if r.id in roles)
|
||||||
|
rstring += "\n"
|
||||||
|
output += f"\nThis role is mutually exclusive to the following role groups:\n{rstring}"
|
||||||
if rsets["cost"]:
|
if rsets["cost"]:
|
||||||
curr = await bank.get_currency_name(ctx.guild)
|
curr = await bank.get_currency_name(ctx.guild)
|
||||||
cost = rsets["cost"]
|
cost = rsets["cost"]
|
||||||
|
@ -535,9 +593,13 @@ class RoleManagement(
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
|
||||||
@rgroup.command(name="exclusive")
|
@rgroup.command(name="exclusive")
|
||||||
async def set_exclusivity(self, ctx: GuildContext, *roles: discord.Role):
|
async def set_exclusivity(self, ctx: GuildContext, group: str, *roles: discord.Role):
|
||||||
"""
|
"""
|
||||||
|
Set exclusive roles for group
|
||||||
Takes 2 or more roles and sets them as exclusive to eachother
|
Takes 2 or more roles and sets them as exclusive to eachother
|
||||||
|
|
||||||
|
The group can be any name, use spaces for names with spaces.
|
||||||
|
Groups will show up in role list etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_roles = set(roles)
|
_roles = set(roles)
|
||||||
|
@ -547,13 +609,20 @@ class RoleManagement(
|
||||||
|
|
||||||
for role in _roles:
|
for role in _roles:
|
||||||
async with self.config.role(role).exclusive_to() as ex_list:
|
async with self.config.role(role).exclusive_to() as ex_list:
|
||||||
ex_list.extend([r.id for r in _roles if r != role and r.id not in ex_list])
|
if group not in ex_list.keys():
|
||||||
|
ex_list[group] = []
|
||||||
|
ex_list[group].extend([r.id for r in _roles if r != role and r.id not in ex_list[group]])
|
||||||
|
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
|
||||||
@rgroup.command(name="unexclusive")
|
@rgroup.command(name="unexclusive")
|
||||||
async def unset_exclusivity(self, ctx: GuildContext, *roles: discord.Role):
|
async def unset_exclusivity(self, ctx: GuildContext, group: str, *roles: discord.Role):
|
||||||
"""
|
"""
|
||||||
|
Remove exclusive roles for group
|
||||||
Takes any number of roles, and removes their exclusivity settings
|
Takes any number of roles, and removes their exclusivity settings
|
||||||
|
|
||||||
|
The group can be any name, use spaces for names with spaces.
|
||||||
|
If all roles are removed from a group then
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_roles = set(roles)
|
_roles = set(roles)
|
||||||
|
@ -563,7 +632,11 @@ class RoleManagement(
|
||||||
|
|
||||||
for role in _roles:
|
for role in _roles:
|
||||||
ex_list = await self.config.role(role).exclusive_to()
|
ex_list = await self.config.role(role).exclusive_to()
|
||||||
ex_list = [idx for idx in ex_list if idx not in [r.id for r in _roles]]
|
if group not in ex_list.keys():
|
||||||
|
continue
|
||||||
|
ex_list[group] = [idx for idx in ex_list if idx not in [r.id for r in _roles]]
|
||||||
|
if not ex_list[group]:
|
||||||
|
del ex_list[group]
|
||||||
await self.config.role(role).exclusive_to.set(ex_list)
|
await self.config.role(role).exclusive_to.set(ex_list)
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
|
||||||
|
@ -586,7 +659,6 @@ class RoleManagement(
|
||||||
|
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
|
||||||
# TODO set roles who don't need to pay for roles
|
|
||||||
@rgroup.command(name="requireall")
|
@rgroup.command(name="requireall")
|
||||||
async def reqall(self, ctx: GuildContext, role: discord.Role, *roles: discord.Role):
|
async def reqall(self, ctx: GuildContext, role: discord.Role, *roles: discord.Role):
|
||||||
"""
|
"""
|
||||||
|
@ -724,7 +796,7 @@ class RoleManagement(
|
||||||
data[role] = vals["cost"]
|
data[role] = vals["cost"]
|
||||||
else:
|
else:
|
||||||
data = {
|
data = {
|
||||||
role: (vals["cost"], vals["subscription"])
|
role: (vals["cost"], vals["subscription"], vals["exclusive_to"])
|
||||||
for role_id, vals in (await self.config.all_roles()).items()
|
for role_id, vals in (await self.config.all_roles()).items()
|
||||||
if (role := ctx.guild.get_role(role_id)) and vals["self_role"]
|
if (role := ctx.guild.get_role(role_id)) and vals["self_role"]
|
||||||
}
|
}
|
||||||
|
@ -734,15 +806,20 @@ class RoleManagement(
|
||||||
|
|
||||||
embed = discord.Embed(title="Roles", colour=ctx.guild.me.colour)
|
embed = discord.Embed(title="Roles", colour=ctx.guild.me.colour)
|
||||||
i = 0
|
i = 0
|
||||||
for role, (cost, sub) in sorted(data.items(), key=lambda kv: kv[1]):
|
for role, (cost, sub, ex_groups) in sorted(data.items(), key=lambda kv: kv[1][0]):
|
||||||
|
if ex_groups:
|
||||||
|
groups = humanize_list(list(ex_groups.keys()))
|
||||||
|
else:
|
||||||
|
groups = None
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name=f"__**{i+1}. {role.name}**__",
|
name=f"__**{i+1}. {role.name}**__",
|
||||||
value="%s%s"
|
value="%s%s%s"
|
||||||
% ((f"Cost: {cost}" if cost else "Free"), (f", every {parse_seconds(sub)}" if sub else "")),
|
% ((f"Cost: {cost}" if cost else "Free"), (f", every {parse_seconds(sub)}" if sub else ""), (f"\nunique groups: `{groups}`" if groups else ""))
|
||||||
)
|
)
|
||||||
i += 1
|
i += 1
|
||||||
if i % 25 == 0:
|
if i % MAX_EMBED == 0:
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
embed.set_footer(text="You can only have one role in the same unique group!")
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@ -759,10 +836,21 @@ class RoleManagement(
|
||||||
eligible = await self.config.role(role).self_role()
|
eligible = await self.config.role(role).self_role()
|
||||||
cost = await self.config.role(role).cost()
|
cost = await self.config.role(role).cost()
|
||||||
subscription = await self.config.role(role).subscription()
|
subscription = await self.config.role(role).subscription()
|
||||||
except RoleManagementException:
|
|
||||||
return
|
|
||||||
except PermissionOrHierarchyException:
|
except PermissionOrHierarchyException:
|
||||||
await ctx.send("I cannot assign roles which I can not manage. (Discord Hierarchy)")
|
await ctx.send("I cannot assign roles which I can not manage. (Discord Hierarchy)")
|
||||||
|
except MissingRequirementsException as e:
|
||||||
|
msg = ""
|
||||||
|
if e.miss_all:
|
||||||
|
roles = [r for r in ctx.guild.roles if r in e.miss_all]
|
||||||
|
msg += f"You need all of these roles in order to get this role: {humanize_list(roles)}\n"
|
||||||
|
if e.miss_any:
|
||||||
|
roles = [r for r in ctx.guild.roles if r in e.miss_any]
|
||||||
|
msg += f"You need one of these roles in order to get this role: {humanize_list(roles)}\n"
|
||||||
|
await ctx.send(msg)
|
||||||
|
except ConflictingRoleException as e:
|
||||||
|
roles = [r for r in ctx.guild.roles if r in e.conflicts]
|
||||||
|
plural = "are" if len(roles) > 1 else "is"
|
||||||
|
await ctx.send(f"You have {humanize_list(roles)}, which you are not allowed to remove and {plural} exclusive to: {role.name}")
|
||||||
else:
|
else:
|
||||||
if not eligible:
|
if not eligible:
|
||||||
return await ctx.send(f"You aren't allowed to add `{role}` to yourself {ctx.author.mention}!")
|
return await ctx.send(f"You aren't allowed to add `{role}` to yourself {ctx.author.mention}!")
|
||||||
|
@ -792,6 +880,9 @@ class RoleManagement(
|
||||||
if role.id not in s:
|
if role.id not in s:
|
||||||
s.append(role.id)
|
s.append(role.id)
|
||||||
|
|
||||||
|
if remove:
|
||||||
|
plural = "s" if len(remove) > 1 else ""
|
||||||
|
await ctx.send(f"Removed `{humanize_list([r.name for r in remove])}` role{plural} since they are exclusive to the role you added.")
|
||||||
await self.update_roles_atomically(who=ctx.author, give=[role], remove=remove)
|
await self.update_roles_atomically(who=ctx.author, give=[role], remove=remove)
|
||||||
await self.dm_user(ctx, role)
|
await self.dm_user(ctx, role)
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
@ -808,10 +899,22 @@ class RoleManagement(
|
||||||
remove = await self.is_self_assign_eligible(ctx.author, role)
|
remove = await self.is_self_assign_eligible(ctx.author, role)
|
||||||
eligible = await self.config.role(role).self_role()
|
eligible = await self.config.role(role).self_role()
|
||||||
cost = await self.config.role(role).cost()
|
cost = await self.config.role(role).cost()
|
||||||
except RoleManagementException:
|
|
||||||
return
|
|
||||||
except PermissionOrHierarchyException:
|
except PermissionOrHierarchyException:
|
||||||
await ctx.send("I cannot assign roles which I can not manage. (Discord Hierarchy)")
|
await ctx.send("I cannot assign roles which I can not manage. (Discord Hierarchy)")
|
||||||
|
except MissingRequirementsException as e:
|
||||||
|
msg = ""
|
||||||
|
if e.miss_all:
|
||||||
|
roles = [r for r in ctx.guild.roles if r in e.miss_all]
|
||||||
|
msg += f"You need all of these roles in order to get this role: {humanize_list(roles)}\n"
|
||||||
|
if e.miss_any:
|
||||||
|
roles = [r for r in ctx.guild.roles if r in e.miss_any]
|
||||||
|
msg += f"You need one of these roles in order to get this role: {humanize_list(roles)}\n"
|
||||||
|
await ctx.send(msg)
|
||||||
|
except ConflictingRoleException as e:
|
||||||
|
print(e.conflicts)
|
||||||
|
roles = [r for r in ctx.guild.roles if r in e.conflicts]
|
||||||
|
plural = "are" if len(roles) > 1 else "is"
|
||||||
|
await ctx.send(f"You have {humanize_list(roles)}, which you are not allowed to remove and {plural} exclusive to: {role.name}")
|
||||||
else:
|
else:
|
||||||
if not eligible:
|
if not eligible:
|
||||||
await ctx.send(f"You aren't allowed to add `{role}` to yourself {ctx.author.mention}!")
|
await ctx.send(f"You aren't allowed to add `{role}` to yourself {ctx.author.mention}!")
|
||||||
|
@ -821,6 +924,9 @@ class RoleManagement(
|
||||||
"This role is not free. " "Please use `[p]selfrole buy` if you would like to purchase it."
|
"This role is not free. " "Please use `[p]selfrole buy` if you would like to purchase it."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
if remove:
|
||||||
|
plural = "s" if len(remove) > 1 else ""
|
||||||
|
await ctx.send(f"Removed `{humanize_list([r.name for r in remove])}` role{plural} since they are exclusive to the role you added.")
|
||||||
await self.update_roles_atomically(who=ctx.author, give=[role], remove=remove)
|
await self.update_roles_atomically(who=ctx.author, give=[role], remove=remove)
|
||||||
await self.dm_user(ctx, role)
|
await self.dm_user(ctx, role)
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
|
|
@ -51,6 +51,17 @@ class EventMixin(MixinMeta):
|
||||||
lost, gained = lost - gained, gained - lost
|
lost, gained = lost - gained, gained - lost
|
||||||
sym_diff = lost | gained
|
sym_diff = lost | gained
|
||||||
|
|
||||||
|
# check if new member roles are exclusive to others.
|
||||||
|
ex = []
|
||||||
|
for r in gained:
|
||||||
|
ex_groups = (await self.config.role_from_id(r).exclusive_to()).values()
|
||||||
|
for ex_roles in ex_groups:
|
||||||
|
ex.extend(ex_roles)
|
||||||
|
|
||||||
|
to_remove = [r for r in after.roles if r.id in ex]
|
||||||
|
if to_remove:
|
||||||
|
await after.remove_roles(*to_remove, reason="conflict with exclusive roles")
|
||||||
|
|
||||||
for r in sym_diff:
|
for r in sym_diff:
|
||||||
if not await self.config.role_from_id(r).sticky():
|
if not await self.config.role_from_id(r).sticky():
|
||||||
lost.discard(r)
|
lost.discard(r)
|
||||||
|
@ -83,6 +94,17 @@ class EventMixin(MixinMeta):
|
||||||
to_add = [r for r in to_add if r < guild.me.top_role]
|
to_add = [r for r in to_add if r < guild.me.top_role]
|
||||||
await member.add_roles(*to_add)
|
await member.add_roles(*to_add)
|
||||||
|
|
||||||
|
# join roles
|
||||||
|
async with self.config.guild(guild).join_roles() as join_roles:
|
||||||
|
to_add: List[discord.Role] = []
|
||||||
|
for role_id in join_roles:
|
||||||
|
role = discord.utils.get(guild.roles, id=role_id)
|
||||||
|
if role:
|
||||||
|
to_add.append(role)
|
||||||
|
if to_add:
|
||||||
|
to_add = [r for r in to_add if r < guild.me.top_role]
|
||||||
|
await member.add_roles(*to_add)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_raw_reaction_add(self, payload: discord.raw_models.RawReactionActionEvent):
|
async def on_raw_reaction_add(self, payload: discord.raw_models.RawReactionActionEvent):
|
||||||
await self.wait_for_ready()
|
await self.wait_for_ready()
|
||||||
|
|
|
@ -170,7 +170,10 @@ class UtilMixin(MixinMeta):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data = await self.config.all_roles()
|
data = await self.config.all_roles()
|
||||||
ex = data.get(role.id, {}).get("exclusive_to", [])
|
ex_data = data.get(role.id, {}).get("exclusive_to", {}).values()
|
||||||
|
ex = []
|
||||||
|
for ex_roles in ex_data:
|
||||||
|
ex.extend(ex_roles)
|
||||||
conflicts: List[discord.Role] = [r for r in who.roles if r.id in ex]
|
conflicts: List[discord.Role] = [r for r in who.roles if r.id in ex]
|
||||||
|
|
||||||
for r in conflicts:
|
for r in conflicts:
|
||||||
|
|
Loading…
Reference in a new issue