class Audit(commands.Cog): def __init__(self, client): self.client = client self._logger = Logger(self) async def print_audit(self, author, type, message): self._logger.trace("print_audit") usr = cassandra.get_user((author.guild.id, author.id)) # TODO: Passed user class # TODO: Check for user not found # Check if the server audit log is enabled if usr.server.audit_channel is None: return False # Check if the audit log channel exists ch = self.client.get_channel(usr.server.audit_channel) if ch is None: return False # TODO: Check if audits of this type are enabled # Send audit log await ch.send("<@!%s>: %s" % (usr.ID, message)) return True
class Main(commands.Cog): def __init__(self, client): self.client = client self._logger = Logger(self) @commands.command(name="profile", help="Shows the profile.") async def profile(self, ctx, name=None): self._logger.trace("profile") # Do not do anything on private messages if ctx.guild == None: return uid = ctx.author.id if name is not None: if name.startswith("<@!") and name.endswith(">"): uid = name[3:len(name) - 1] usr = cassandra.get_user((ctx.guild.id, uid)) if usr is None: await ctx.send("User not found.") return discord_user = self.client.get_user(usr.ID) if discord_user is None: await ctx.send("User not found.") return content = "" cecon = self.client.get_cog('Economy') if cecon is not None: content = content + ":moneybag: Coins: %s" % cecon.get_balance(usr) cxp = self.client.get_cog('Xp') if cxp is not None: content = content + "\n:star: XP: %s" % cxp.get_xp(usr) content = content + "\n:star2: Level: %s" % cxp.get_level(usr) embed = discord.Embed(title="Profile for %s on server %s" % (discord_user.name, ctx.guild.name), description=content, colour=discord.Colour.gold()) embed.set_thumbnail(url=discord_user.avatar_url) stats = self.client.get_cog('Stats') if stats is not None: embed.add_field(name=":writing_hand: Messages", value="Total: %s" % stats.get_msg(usr), inline=True) embed.add_field(name=":heart: Reactions", value="Total: %s" % stats.get_reaction(usr), inline=True) embed.add_field(name=":microphone2: Voice", value="Time: %s" % util.sec2human(stats.get_voice(usr)), inline=True) await ctx.send(embed=embed)
class Economy(commands.Cog): def __init__(self, client): self.client = client self._logger = Logger(self) def get_balance(self, u): self._logger.trace("get_balance") usr = cassandra.get_user(u) return usr.balance # Balance def set_balance(self, u, amt): self._logger.trace("set_balance") usr = cassandra.get_user(u) old = usr.balance resp = cassandra.dispatch("balance-change", { "old": old, "new": amt, "user": usr }) usr.balance = resp.new return old # Old balance def add_balance(self, u, amt): self._logger.trace("add_balance") usr = cassandra.get_user(u) old = usr.balance resp = cassandra.dispatch("balance-change", { "old": old, "new": old + amt, "user": usr }) usr.balance = resp.new return usr.balance # New balance def has_balance(self, u, amt): self._logger.trace("has_balance") usr = cassandra.get_user(u) return usr.balance >= amt
class LevelRoles(commands.Cog): def __init__(self, client): self.client = client self._logger = Logger(self) self._cache_xp_upate = [] # Holds tuples for users to update def get_help_page(self): return { "title": "LevelRoles Commands", "description": "Some of these commands require moderation or administrator permissions.", "content": { "lvroles": "Lists all reward roles that can be gained by leveling up.", "lvrole <level>": "Removes a reward from the ladder. - Admin Only", "lvrole <level> <role>": "Adds a reward level to the ladder. - Admin Only" } } @commands.command(name="lvroles") async def lvroles(self, ctx): srv = cassandra.get_server(ctx.guild.id) if len(srv.level_roles) > 0: text = "**Level Roles**\n\n" lvrolecache = self._build_level_rank_cache(ctx.guild, srv) for lv, role in lvrolecache.items(): text = text + ("%s: <@&%s>\n" % (lv, role.id)) await ctx.send(text) else: await ctx.send("This server has no level roles configured.") @commands.command(name="lvrole") @commands.has_permissions(manage_roles=True) async def lvrole(self, ctx, lvstr, rolestr=None): lv = None try: lv = int(lvstr) except Exception: await ctx.send("<level> must be a positive number.") return if lv < 0: await ctx.send("<level> must be a positive number.") return srv = cassandra.get_server(ctx.guild.id) if rolestr is None: # Remove Level role if lv in srv.level_roles: srv.level_roles.pop(lv, None) await ctx.send("Removed level role for level %s." % lv) else: await ctx.send("No level role is set for level %s." % lv) else: if lvstr in srv.level_roles: await ctx.send( "Level %s currently has role %s.\nPlease remove it manualy first, if you want another role at this level." ) return if len(rolestr) == 22 and rolestr.startswith( "<@&") and rolestr.endswith(">"): try: roleid = int(rolestr[3:21]) except: await ctx.send("Role not found %s!" % rolestr) return role = get(ctx.guild.roles, id=int(roleid)) if role is None: await ctx.send("Role not found <@&%s>!" % roleid) return srv.level_roles[lvstr] = roleid cassandra.save_server(srv) await ctx.send("Role <@&%s> set for level %s." % (roleid, lv)) else: ctx.send("%s is not a vaild role!" % rolestr) return pass def _build_level_rank_cache(self, guild, srv=None): self._logger.trace("_build_level_rank_cache") if srv is None: srv = cassandra.get_server(guild.id) lv_role_cache = {} for lv in srv.level_roles: role = get(guild.roles, id=srv.level_roles[lv]) if role is None: continue lv_role_cache[int(lv)] = role return dict(sorted(lv_role_cache.items(), key=lambda item: item[0])) async def update_user(self, guild, user, lv_role_cache=None): self._logger.trace("update_user(%s)" % user.id) if lv_role_cache is None: lv_role_cache = self._build_level_rank_cache(guild) # If user is a bot, ignore if user.bot: return level = self.client.get_cog('Xp').get_level((guild.id, user.id)) highest_role = None for lv in lv_role_cache: role = lv_role_cache[lv] if level < lv: break highest_role = role if highest_role is None: return if highest_role in user.roles: return for urole in user.roles: if urole in lv_role_cache.values(): await user.remove_roles( urole, reason="Userlevel rank" ) # TODO: There must be a more efficient way to do this await user.add_roles(highest_role, reason="Userlevel change") # Dispatch event cassandra.dispatch( "lvrole-change", { "duser": user, "user": cassandra.get_user((guild.id, user.id)), "role": highest_role }) # TODO: Anounce async def update_guild(self, guild): self._logger.trace("update_guild(%s)" % guild.id) # Do not do anything for unavailable guilds if guild.unavailable == True: return srv = cassandra.load_server(guild.id) # If no level roles are configured, ignore if len(srv.level_roles) == 0: return # Get all groups into a dictionary lv_role_cache = self._build_level_rank_cache(guild, srv) for member in guild.members: await self.update_user(guild, member, lv_role_cache) @commands.Cog.listener() async def on_ready(self): self.update_slow.start() self.update_fast.start() cassandra.add_event_listener("xp-change", self, self.on_xp_change) def on_xp_change(self, args): self._logger.trace("on_xp_change") if args.old >= args.new: return if args.user in self._cache_xp_upate: return self._cache_xp_upate.append(args.user) @tasks.loop(seconds=UPDATE_TIMEOUT_FAST) async def update_fast(self): if len(self._cache_xp_upate) == 0: return self._logger.trace("update_fast") for entry in self._cache_xp_upate: guild = get(self.client.guilds, id=entry.server.ID) if guild is None: continue user = get(guild.members, id=entry.ID) if user is None: continue await self.update_user(guild, user) self._cache_xp_upate.clear() @tasks.loop(seconds=UPDATE_TIMEOUT_SLOW) async def update_slow(self): self._logger.trace("update_slow") for guild in self.client.guilds: await self.update_guild(guild)
class Fun(commands.Cog): def __init__(self, client): self.client = client self._logger = Logger(self) self.wyr_data = [] self.whatami_data = [] self.eightball_data = [] self.fortunes = {} self._load_wyr_assets() self._load_fortune_assets() self._load_whatami_assets() self._load_8ball_assets() def _load_wyr_assets(self): path = "assets/wyr.json" if not os.path.exists(path): return False with open(path) as f: jdata = json.load(f) self.wyr_data = jdata["questions"] self._logger.info("Loaded WYR Data") return True def _load_fortune_assets(self): dir_path = "assets/fortunes" if not os.path.exists(dir_path): return False for filename in os.listdir(dir_path): if not filename.endswith(".json"): continue path = dir_path + "/" + filename with open(path) as f: jdata = json.load(f) self.fortunes[filename[:-5]] = jdata["fortunes"] self._logger.info("Loaded fortune database %s" % filename) self._logger.info("Loaded fortune Data") return True def _load_whatami_assets(self): path = "assets/whatami.json" if not os.path.exists(path): return False with open(path) as f: jdata = json.load(f) self.whatami_data = jdata["quotes"] self._logger.info("Loaded whatami Data") return True def _load_8ball_assets(self): path = "assets/8ball.json" if not os.path.exists(path): return False with open(path) as f: jdata = json.load(f) self.eightball_data = jdata["answers"] self._logger.info("Loaded 8ball Data") return True def get_help_page(self): return { "title": "Fun Commands", "description": None, "content": { "8ball <Question ...>": "Ask the magic 8ball.", #"conn <User>": "Starts a game of connect-4 against the mentioned user.", "fortune": "Opens a fortune cookie.", #"rusr": "******", #"tord": "Play a game of truth or dare.", #"ttt <User>": "Starts a game of Tick-Tack-Toe.", "wyr": "Displays a would you rather question.", "whatami": "You better have thick skin...or a thick skull!" } } @commands.command(name="whatami") async def whatami(self, ctx): self._logger.trace("whatami") quote = random.choice(self.whatami_data) await ctx.send(quote) @commands.command(name="conn") async def conn(self, ctx): self._logger.trace("conn") await ctx.send("Not implemented") @commands.command(name="fortune") async def fortune(self, ctx): self._logger.trace("fortune") # TODO: Handle dictionary is empty db = random.choice(list(self.fortunes.keys())) # TODO: Handle db is empty line = random.choice(self.fortunes[db]) await ctx.send(line) @commands.command(name="tord") async def tord(self, ctx): self._logger.trace("tord") await ctx.send("Not implemented") @commands.command(name="ttt") async def ttt(self, ctx): self._logger.trace("ttt") await ctx.send("Not implemented") @commands.command(name="wyr") async def wyr(self, ctx): self._logger.trace("wyr") line = random.choice(self.wyr_data) embed = discord.Embed(title="Would You Rather", description="**" + line["question"] + "**\n\n\n" + EMOJI_A + " " + line["answer_1"] + "\n\n" + EMOJI_B + " " + line["answer_2"], colour=discord.Colour.red()) msg = await ctx.send(embed=embed) await msg.add_reaction(EMOJI_A) await msg.add_reaction(EMOJI_B) @commands.command(name="8ball") async def eightball(self, ctx): self._logger.trace("8ball") await ctx.send(EMOJI_8BALL + " " + random.choice(self.eightball_data))
class Stats(commands.Cog): def __init__(self, client): self.client = client self._logger = Logger(self) def get_msg(self, u): self._logger.trace("get_msg") usr = cassandra.get_user(u) return usr.msg_count def get_reaction(self, u): self._logger.trace("get_reaction") usr = cassandra.get_user(u) return usr.reaction_count def get_voice(self, u): self._logger.trace("get_voice") usr = cassandra.get_user(u) return usr.voice_time @commands.Cog.listener() async def on_ready(self): self.update_voice.start() @commands.Cog.listener() async def on_reaction_add(self, reaction, user): self._logger.trace("on_reaction_add") # Ignore reactions from bots if user.bot == True: return # Ignore private messages if reaction.message.guild is None: return # Ignore reactions made to messages written by bots if reaction.message.author.bot == True: return usr = cassandra.get_user((reaction.message.guild.id, user.id)) # TODO: Handle user not found ts = util.getts() usr.reaction_last = ts # if reaction timeout is reached award coins if ts - usr.reaction_awarded > REACTION_TIMEOUT: usr.reaction_awarded = ts # Add balance if economy cog is loaded cecon = self.client.get_cog('Economy') if cecon is not None: cecon.add_balance(usr, REACTION_BONUS_COINS) # Add xp if xp cog is loaded cxp = self.client.get_cog('Xp') if cxp is not None: cxp.add_xp(usr, REACTION_BONUS_XP) usr.reaction_count = usr.reaction_count + 1 cassandra.save_user(usr) @commands.Cog.listener() async def on_message(self, message): # Ignore messages from bots if message.author.bot == True: return # Ignore direct messages if message.guild is None: return self._logger.trace("on_message(%s)" % message.content) srv = cassandra.get_server(message.guild.id) # TODO: Check if server is not found # Ignore commands to cassandra if message.content.startswith(srv.prefix_used): return # Ignore commands to other bots for prefix in srv.prefix_blocked: if message.content.startswith(prefix): return usr = cassandra.get_user((srv, message.author.id)) # TODO: Check if user is not found ts = util.getts() usr.msg_last = ts usr.msg_count = usr.msg_count + 1 #if msg timeout is reached award coins if ts - usr.msg_awarded > MSG_TIMEOUT: usr.msg_awarded = ts # Add balance if economy cog is loaded cecon = self.client.get_cog('Economy') if cecon is not None: cecon.add_balance(usr, MSG_BONUS_COINS) # Add xp if xp cog is loaded cxp = self.client.get_cog('Xp') if cxp is not None: cxp.add_xp(usr, MSG_BONUS_XP) cassandra.save_user(usr) @tasks.loop(seconds=VOICE_TIMEOUT) async def update_voice(self): self._logger.trace("update_voice") # For all voice channels on each guild for guild in self.client.guilds: # Do not do anything for unavailable guilds if guild.unavailable == True: continue # For each voice channel in each guild for vchannel in guild.voice_channels: vchmem = len(vchannel.members) # No need to look into a voice channel with one or no active members if vchmem < 2: continue # For each current member for member in vchannel.members: if member.voice.afk: continue elif member.voice.deaf or member.voice.self_deaf: continue elif member.voice.mute or member.voice.self_mute: continue usr = cassandra.get_user((guild.id, member.id)) usr.voice_time = usr.voice_time + VOICE_TIMEOUT # Add balance if economy cog is loaded cecon = self.client.get_cog('Economy') if cecon is not None: cecon.add_balance(usr, VOICE_BONUS_COINS) # Add xp if xp cog is loaded cxp = self.client.get_cog('Xp') if cxp is not None: # Give an xp bonus for more people in the voicechat xp = VOICE_BONUS_XP + (vchmem - 2) cxp.add_xp(usr, xp) cassandra.save_user(usr)
class Cassino(commands.Cog): def __init__(self, client): self.client = client self._logger = Logger(self) def get_help_page(self): return { "title": "Casino Commands", "description": None, "content": { #"lottery": "Buy a lottery ticket.", "coinflip <heads|tails> [bet = 0]": "Flip a coin and bet on the outcome.", #"slots [bet = 0]": "Spin the slots for a chance to win the jackpot!", # "spin": "Spin the wheel every 5 minutes for a reward.", #"scratch": "Scratch a card for a chance to win a reward.", #"dice": Roll the dice, if you roll a 6 you'll get your bet x5!", # "snakeeyes": "Two dice are rolled. Roll a 1 on either one to win. Get both on 1 to recieve a special bonus reward.", # "blackjack": "Play a game of blackjack.", } } def _get_bet(self, bet): if bet is None: return 0 if not bet.isdigit(): return None # At this point the number can not be negative return int(bet) async def _can_afford(self, ctx, u, bet): cecon = self.client.get_cog('Economy') if cecon is None: await ctx.send(EMOJI_ERROR + " Betting for coins is not configured!") return False if not cecon.has_balance(u, bet): await ctx.send(EMOJI_ERROR + " You can't afford this bet!") return False return True async def _usage_coinflip(self, ctx): await ctx.send("Usage: $coinflip <heads|tails> [amt=0]") @commands.command(name="coinflip", help="Flip a coin and bet on the outcome.") async def coinflip(self, ctx, side=None, bet=None): self._logger.trace("coinflip") if side is None: await self._usage_coinflip(ctx) return bet = self._get_bet(bet) if bet is None: await self._usage_coinflip(ctx) return usr = None if bet > 0: usr = cassandra.get_user((ctx.guild.id, ctx.author.id)) # Check if the user of this command has that much money if not await self._can_afford(ctx, usr, bet): return rng_side = -1 if side == "heads": rng_side = 0 elif side == "tails": rng_side = 1 else: await self._usage_coinflip(ctx) return flip = random.randint(0, 1) thumbnail = None flip_str = None if flip == 1: flip_str = "tails" thumbnail = IMAGE_TAILS else: flip_str = "heads" thumbnail = IMAGE_HEADS won = (flip == rng_side) color = None description = "It's **%s**\n" % flip_str if won: color = discord.Colour.green() description = description + "You won" else: color = discord.Colour.red() description = description + "You lost" if bet > 0: cecon = self.client.get_cog( 'Economy' ) # This can always be found because of the _can_afford check if won: cecon.add_balance(usr, bet) else: cecon.add_balance(usr, -bet) description = description + (" %s " % bet) + EMOJI_MONEY + ( "\nYou now have %s " % usr.balance) + EMOJI_MONEY embed = discord.Embed(title="Coinflip", colour=color, description=description) embed.set_thumbnail(url=thumbnail) await ctx.send(embed=embed) if usr: cassandra.save_user(usr) @commands.command(name="lottery") async def lottery(self, ctx): self._logger.trace("lottery") await ctx.send("Not implemented") @commands.command(name="slots") async def slots(self, ctx): self._logger.trace("slots") await ctx.send("Not implemented") @commands.command(name="scratch") async def scratch(self, ctx): self._logger.trace("scratch") await ctx.send("Not implemented") @commands.command(name="dice") async def dice(self, ctx): self._logger.trace("dice") await ctx.send("Not implemented") @commands.command(name="snakeeyes") async def snakeeyes(self, ctx): self._logger.trace("snakeeyes") await ctx.send("Not implemented") @commands.command(name="blackjack") async def blackjack(self, ctx): self._logger.trace("blackjack") await ctx.send("Not implemented") # Casino @commands.command(name="spin", help="Spin the wheel every 5 minutes for a reward.") async def spin(self, ctx): self._logger.trace("spin") ods = [{ "symbol": "<:c500:710482148305403924>", "propbability": 1, "ods": 500, "name": "MYTHIC" }, { "symbol": "<:c100:710482154500653056>", "propbability": 2, "ods": 100, "name": "IMORTAL" }, { "symbol": "<:c50:710482156895469691>", "propbability": 4, "ods": 50, "name": "LEGENDARY" }, { "symbol": "<:c25:710482155310022758>", "propbability": 8, "ods": 25, "name": "EPIC" }, { "symbol": "<:c10:710482151484817439>", "propbability": 16, "ods": 10, "name": "RARE" }, { "symbol": "<:c5:710482137895403520>", "propbability": 24, "ods": 5, "name": "UNCOMMON" }, { "symbol": "<:c1:710482104705613845>", "propbability": 48, "ods": 1, "name": "COMMON" }] usr = cassandra.get_user((ctx.guild.id, ctx.author.id)) ts = util.getts() if ts - usr.casino_last_spin < 300: await ctx.send("You have to wait " + util.sec2human(300 - (ts - usr.casino_last_spin)) + " for your next chance of a reward.") return total = 0 for outcome in ods: total = total + outcome["propbability"] rand = random.randint(0, total) index = 0 out = None while rand > 0: outcome = ods[index] rand = rand - outcome["propbability"] if rand <= 0: out = outcome break index = index + 1 # FIX: This is a drity fix, as we should never end up here if out is None: out = ods[0] embed = discord.Embed( title=ctx.author.display_name + " spins the wheel ...", description="<a:spin:710491435006165032> | Spinning ...", colour=discord.Colour.green()) msg = await ctx.send(embed=embed) # FIX: Replace this with a timer, because this forces only one player (in al servers) to be able to spin at once time.sleep(1) description = "%s | Landed on **%s**" % (out["symbol"], out["name"]) cecon = self.client.get_cog('Economy') if cecon is not None: reward = out["ods"] # Multiply reward by user level cxp = self.client.get_cog('Xp') if cxp is not None: level = cxp.get_level(usr) if level > 1: reward = reward * level description = description + ("\nYou got a reward of %s coins" % reward) cecon.add_balance(usr, reward) else: description = description + ( "\n\n%s You did not get a reward, because the economy is not setup!" % EMOJI_WARN) embed = discord.Embed(title=ctx.author.display_name + " Spun the wheel", description=description, colour=discord.Colour.green()) await msg.edit(embed=embed) usr.casino_last_spin = ts cassandra.save_user(usr)
class AutoChannel(commands.Cog): def __init__(self, client): self.client = client self._logger = Logger(self) async def create_autochannel(self, s, dguild, duser=None): srv = cassandra.get_server(s) # Check if autochannels are configured if srv.autochannel_router is None: self._logger.debug("router channel not configured") return False # Find router channel router = get(dguild.channels, id=srv.autochannel_router) if router is None: self._logger.debug("router channel not found") return False # Get category cat = router.category if cat is None: self._logger.debug("router channel has no category") return False name = "Autochannel" if duser is not None: name = "AC %s" % (duser.display_name) channel = await cat.create_voice_channel(name, reason="New AutoChannel") # Save changes srv.autochannel_channels.append(channel.id) cassandra.save_server(srv) # Move member to new channel if duser is not None: await duser.edit(voice_channel=channel) return True async def delete_autochannel(self, s, dguild, channel): srv = cassandra.get_server(s) if not channel.id in srv.autochannel_channels: self._logger.debug("channel not an autochannel") return False # Check if the channel is empty if len(channel.members) > 0: self._logger.debug("still users in channel") return False # Remove from server data srv.autochannel_channels.remove(channel.id) cassandra.save_server(srv) # Delete empty channel await channel.delete(reason="Empty AutoChannel") return True async def cleanup(self, guild=None): self._logger.trace("cleanup(%s)" % guild) if guild is None: # For all guilds for g in self.client.guilds: # Do not do anything for unavailable guilds if g.unavailable == True: continue await self.cleanup(g) else: srv = cassandra.get_server(guild.id) for ac in srv.autochannel_channels: channel = get(guild.channels, id=ac) if channel is None: continue await self.delete_autochannel(srv, guild, channel) @commands.command(name="autochannel") @commands.has_permissions(manage_channels=True) async def autochannel(self, ctx, action, router=None): self._logger.trace("autochannel") srv = cassandra.get_server(ctx.guild.id) if action in ["disable", "stop"]: srv.autochannel_router = None cassandra.save_server(srv) await self.cleanup() await ctx.send( "Autochannels are no longer active.\nIf you want to use them in the future, you need to configure them again." ) elif action in ["setup", "start", "enable"]: if router is None: return # TODO: Print help routerid = None try: routerid = int(router) except Exception: return # TODO: User feedback routerch = get(ctx.guild.channels, id=routerid) if routerch is None: return # TODO: User feedback if routerch.category is None: await ctx.send( "The autochannel router must be part of a channel category!" ) return srv.autochannel_router = routerid cassandra.save_server(srv) await ctx.send("Autochannels successfully configured.") @commands.Cog.listener() async def on_voice_state_update(self, member, before, after): srv = cassandra.get_server(member.guild.id) if after.channel is not None: if after.channel.id == srv.autochannel_router: self._logger.debug("user join router") await self.create_autochannel(srv, member.guild, member) if before.channel is not None: if before.channel.id in srv.autochannel_channels: self._logger.debug("user left autochannel") await self.delete_autochannel(srv, member.guild, before.channel) @commands.Cog.listener() async def on_ready(self): self._logger.trace("on_ready") await self.cleanup()
class Tracker(commands.Cog): def __init__(self, client): self.client = client self._logger = Logger(self) def get_nicknames(self, u): self._logger.trace("get_nicknames") usr = cassandra.get_user(u) return usr.nicknames def get_names(self, u): self._logger.trace("get_names") usr = cassandra.get_user(u) return usr.names def add_nickname(self, u, dusr): self._logger.trace("add_nickname") usr = cassandra.get_user(u) if dusr.nick is None: return False # Has no nick if dusr.nick in usr.nicknames: # Dispatch event cassandra.dispatch("nickname-change", { "user": usr, "nick": dusr.nick, "known": True }) return False # Known nick # Dispatch event cassandra.dispatch("nickname-change", { "user": usr, "nick": dusr.nick, "known": False }) usr.nicknames.append(dusr.nick) return True # New nick def add_name(self, u, dusr): self._logger.trace("add_name") usr = cassandra.get_user(u) name = dusr.name + "#" + dusr.discriminator if name in usr.names: # Dispatch event cassandra.dispatch("name-change", { "user": usr, "name": name, "known": True }) return False # Known name # Dispatch event cassandra.dispatch("name-change", { "user": usr, "name": dusr.nick, "known": False }) usr.names.append(name) return True # New name @commands.Cog.listener() async def on_ready(self): self.update.start() @commands.command(name="whois") @commands.has_permissions(manage_messages=True) async def whois(self, ctx, member: discord.Member): self._logger.trace("whois") usr = cassandra.get_user((ctx.guild.id, member.id)) if usr is None: await ctx.send("Couldn't get user information") return title = "%s#%s" % (member.name, member.discriminator) if member.nick is not None: title = title + ("(%s)" % member.nick) nicknames = "No nicknames tracked" nicklist = self.get_nicknames(usr) if len(nicklist) > 0: nicknames = "```" + ', '.join(nicklist) + '```' names = "No names tracked" namelist = self.get_names(usr) if len(namelist) > 0: names = "```" + ', '.join(namelist) + '```' embed = discord.Embed(title=title) embed.set_thumbnail(url=member.avatar_url) embed.add_field(name="ID", value=str(member.id)) embed.add_field(name="Account Created", value=str(member.created_at), inline=True) embed.add_field(name="Joined At", value=str(member.joined_at), inline=True) embed.add_field(name="Nicknames", value=nicknames) embed.add_field(name="Names", value=names) await ctx.send(embed=embed) @commands.Cog.listener() # Username, discriminator async def on_member_join(self, member): self._logger.trace("on_member_join") usr = cassandra.get_user((member.guild.id, member.id)) update = False if self.add_name(usr, member): update = True if self.add_nickname(usr, member): update = True if update: cassandra.save_user(usr) @commands.Cog.listener() # Nickname async def on_member_update(self, before, after): self._logger.trace("on_member_update") usr = cassandra.get_user((after.guild.id, after.id)) if self.add_nickname(usr, after): cassandra.save_user(usr) @commands.Cog.listener() # Username, discriminator async def on_user_join(self, member): usr = cassandra.get_user((member.guild.id, member.id)) if self.add_nickname(usr, member): cassandra.save_user(usr) @tasks.loop(seconds=UPDATE_TIMEOUT) async def update(self): self._logger.trace("update") for guild in self.client.guilds: # Do not do anything for unavailable guilds if guild.unavailable == True: continue for member in guild.members: # If member is a bot, ignore if member.bot: continue usr = cassandra.get_user((guild.id, member.id)) self.add_nickname(usr, member) self.add_name(usr, member)
class Moderation(commands.Cog): def __init__(self, client): self.client = client self._logger = Logger(self) def get_help_page(self): return { "title": "Moderation Commands", "description": "These commands require moderation or administrator permissions.", "content": { "clear [count = 10]": "Clears the chat.", "ban <user> [reason...]": "Bans a user from the server.", "kick <user> [reason...]": "Kicks a user from the server.", "warn <user> [reason...]": "Warns a user.", "quarantine <user> [reason...]": "Strips a user from all roles.", "unquarantine <user>": "Restores a users roles.", # "history <user>": "Gets the moderation history for a user.", "whois <user>": "Displays some extended information about a user." } } def is_user_quarantined(self, u): usr = cassandra.get_user(u) return usr.quarantine_status @commands.command(name="warn") @commands.has_permissions(manage_messages=True) async def warn(self, ctx, member: discord.Member, *, reason=None): self._logger.trace("warn") # Audit log caudit = self.client.get_cog('Audit') if caudit is not None: if reason is None: await caudit.print_audit( ctx.author, 'warning', "Warned %s#%s for no reason." % (member.name, member.discriminator)) else: await caudit.print_audit( ctx.author, 'warning', "Warned %s#%s for **\"%s\"**." % (member.name, member.discriminator, reason)) reason_str = reason if reason is None: reason_str = "No reason given" embed = discord.Embed(title="%s %s: You have been warned!" % (EMOJI_WARN, ctx.guild.name), description="Reason: %s" % reason_str, colour=discord.Colour.gold()) await member.send(embed=embed) usr = cassandra.get_user((guild.id, member.id)) # Dispatch event cassandra.dispatch("mod-warning", { "dauthor": ctx.author, "reason": reason, "duser": member, "user": usr }) # TODO: Append to user history # usr = load_user(ctx.guild.id, member.id) # histentry = { # "type": "warning", # "time": getts(), # "by": ctx.author.id, # "reason": reason # } # usr["history"].append(histentry) # save_user(usr) @commands.command(name="ban") @commands.has_permissions(ban_members=True) async def ban(self, ctx, member: discord.Member, *, reason=None): self._logger.trace("ban") # TODO: Tempban # Audit log caudit = self.client.get_cog('Audit') if caudit is not None: if reason is None: await caudit.print_audit( ctx.author, 'ban', "Baned %s#%s for no reason." % (member.name, member.discriminator)) else: await caudit.print_audit( ctx.author, 'ban', "Baned %s#%s for **\"%s\"**." % (member.name, member.discriminator, reason)) if reason is None: reason = "No reason given" embed = discord.Embed(title="%s %s: The BAN-HAMMER has spoken!" % (EMOJI_BAN, ctx.guild.name), description="Reason: %s" % reason, colour=discord.Colour.red()) # We need to send the message first because discord does not let you send messages to users that have no servers in common await member.send(embed=embed) usr = cassandra.get_user((guild.id, member.id)) # Dispatch event cassandra.dispatch("mod-ban", { "dauthor": ctx.author, "reason": reason, "duser": member, "user": usr }) await member.ban(reason=reason) # TODO: User history # usr = load_user(ctx.guild.id, member.id) # histentry = { # "type": "ban", # "time": getts(), # "by": ctx.author.id, # "reason": reason # } # usr["history"].append(histentry) # save_user(usr) @commands.command(name="qrole") @commands.has_permissions(manage_channels=True) async def qrole(self, ctx, action, role: discord.Role = None): self._logger.trace("qrole") srv = cassandra.get_server(ctx.guild.id) if action in ["unset", "remove", "delete", "del", "rem"]: srv.quarantine_role = None cassandra.save_server(srv) await ctx.send("Quarantine role unset.") elif action in ["set", "add"]: if role is None: return # TODO: Print help srv.quarantine_role = role.id cassandra.save_server(srv) await ctx.send("Quarantine role set.") @commands.command(name="quarantine") @commands.has_permissions(manage_roles=True) async def quarantine(self, ctx, member: discord.Member, *, reason=None): self._logger.trace("quarantine") usr = cassandra.get_user((ctx.guild.id, member.id)) if usr.quarantine_status == True: await ctx.send("This user is already in quarantine!") return # Audit log caudit = self.client.get_cog('Audit') if caudit is not None: if reason is None: await caudit.print_audit( ctx.author, 'quarantine', "Quarantined %s#%s for no reason." % (member.name, member.discriminator)) else: await caudit.print_audit( ctx.author, 'quarantine', "Quarantined %s#%s for **\"%s\"**." % (member.name, member.discriminator, reason)) old_roles = [] for role in member.roles: if role == ctx.guild.default_role: continue old_roles.append(role.id) try: await member.remove_roles(role, reason="Quarantine") except Exception as ex: self._logger.warn( "Failed to remove role %s from member %s: %s" % (role.name, member.name, ex)) if usr.server.quarantine_role is not None: qrole = get(ctx.guild.roles, id=usr.server.quarantine_role) if qrole is not None: try: await member.add_roles(qrole, reason="Quarantine") except Exception as ex: self._logger.warn( "Failed to add role %s to member %s: %s" % (qrole.name, member.name, ex)) # Dispatch event cassandra.dispatch("mod-quarantine", { "dauthor": ctx.author, "reason": reason, "duser": member, "user": usr }) usr.quarantine_status = True usr.quarantine_roles = old_roles cassandra.save_user(usr) await ctx.send("User %s#%s has been quarantined." % (member.name, member.discriminator)) @commands.command(name="unquarantine") @commands.has_permissions(manage_roles=True) async def unquarantine(self, ctx, member: discord.Member): self._logger.trace("unquarantine") usr = cassandra.get_user((ctx.guild.id, member.id)) if usr.quarantine_status == False: await ctx.send("This user is not in quarantine!") return # Audit log caudit = self.client.get_cog('Audit') if caudit is not None: if reason is None: await caudit.print_audit( ctx.author, 'unquarantine', "Unquarantined %s#%s." % (member.name, member.discriminator)) if usr.server.quarantine_role is not None: qrole = get(ctx.guild.roles, id=usr.server.quarantine_role) if qrole is not None: try: await member.remove_roles(qrole, reason="Unquarantine") except Exception as ex: self._logger.warn( "Failed to remove role %s from member %s: %s" % (qrole.name, member.name, ex)) for roleid in usr.quarantine_roles: role = get(ctx.guild.roles, id=roleid) if role is None: continue if role == ctx.guild.default_role: continue try: await member.add_roles(role, reason="Unquarantine") except Exception as ex: self._logger.warn("Failed to add role %s to member %s: %s" % (role.name, member.name, ex)) # Dispatch event cassandra.dispatch("mod-unquarantine", { "dauthor": ctx.author, "duser": member, "user": usr }) usr.quarantine_status = False usr.quarantine_roles = [] cassandra.save_user(usr) await ctx.send("User %s#%s has been released form quarantine." % (member.name, member.discriminator)) @commands.command(name="kick") @commands.has_permissions(kick_members=True) async def kick(self, ctx, member: discord.Member, *, reason=None): self._logger.trace("kick") # Audit log caudit = self.client.get_cog('Audit') if caudit is not None: if reason is None: await caudit.print_audit( ctx.author, 'kick', "Kicked %s#%s for no reason." % (member.name, member.discriminator)) else: await caudit.print_audit( ctx.author, 'kick', "Kicked %s#%s for **\"%s\"**." % (member.name, member.discriminator, reason)) if reason is None: reason = "No reason given" embed = discord.Embed(title="%s %s: You have beed kicked!" % (EMOJI_INFO, ctx.guild.name), description="Reason: %s" % reason, colour=discord.Colour.dark_orange()) # We need to send the message first because discord does not let you send messages to users that have no servers in common await member.send(embed=embed) usr = cassandra.get_user((guild.id, member.id)) # Dispatch event cassandra.dispatch("mod-kick", { "dauthor": ctx.author, "reason": reason, "duser": member, "user": usr }) await member.kick(reason=reason) # TODO: User history # usr = load_user(ctx.guild.id, member.id) # histentry = { # "type": "kick", # "time": getts(), # "by": ctx.author.id, # "reason": reason # } # usr["history"].append(histentry) # save_user(usr) @commands.command(name="clear") @commands.has_permissions(manage_messages=True) async def clear(self, ctx, amt=10): self._logger.trace("clear") if not type(amt) is int: try: amt = int(amt, 10) except ValueError: await ctx.send("Invalid number of messages given") return if amt < 1: await ctx.send("Can not remove %s messages" % amt) return elif amt > 100: amt = 100 await ctx.channel.purge(limit=amt + 1) # Audit log caudit = self.client.get_cog('Audit') if caudit is not None: await caudit.print_audit( ctx.author, 'clear', "Cleared %s messages from channel <#%s>" % (amt, ctx.channel.id)) # Dispatch event cassandra.dispatch("mod-clear", { "dauthor": ctx.author, "amount": amt, "channel": ctx.channel })