class Verification(Cog): def __init__(self, bot): self.bot = bot self.log = bot.log.getChild(type(self).__name__) async def _verify(self, member, auto=True): await ctx.send(embed=Embed(title='Welcome to HackTAMS', color=Color.orange(), description='To start the verification process enter the email you used to register for hackTAMS\nEx: `[email protected]`').set_footer(text="If you haven't registered yet you can do so at hacktams.org/register")) msg = await self.bot.wait_for('message',timeout=60 * (int(auto)+1) check=lambda msg:msg.author.id==member.id, msg.channel.id==member.id) @Cog.listener() async def on_member_join(self, member): # put the member data into storage self.log.info(f"{member.name} has joined {member.guild.name}") await self._verify(member) @command() @check_any(dm_only(), has_permissions(administrator=True)) async def verify(self, ctx, member:Member=None): if not ctx.channel: member = ctx.author member = member or ctx.author await self._verify(member, auto=False) @Cog.listener() async def on_member_leave( self, member ): # move the member out of storage (?) might not self.log.info(f"{member.name} has left {member.guild.name}") @Cog.listener() async def on_member_verify( self, member, data ): # whenever a user verifies we can edit stuff on them like roles await member.edit(name=data["name"])
} logger = logging.getLogger('logs_uploader') logger.setLevel(logging.INFO) handler = logging.StreamHandler(sys.stdout) handler.setLevel(logging.INFO) logger.addHandler(handler) # Don't post to team channels and force the guild used so testing can you DMs DISCORD_TESTING = bool(os.getenv('DISCORD_TESTING')) # Just post all messages to calling channel, allow DMs DISCORD_DEBUG = bool(os.getenv('DISCORD_DEBUG')) if DISCORD_TESTING or DISCORD_DEBUG: # Allow DMs in testing guild_only = commands.check_any(commands.guild_only(), commands.dm_only()) # type: ignore # print all debug messages logger.setLevel(logging.DEBUG) handler.setLevel(logging.DEBUG) else: guild_only = commands.guild_only() bot = commands.Bot(command_prefix='!') async def log_and_reply(ctx: commands.Context, error_str: str) -> None: logger.error(error_str) await ctx.reply(error_str) async def get_channel(
Bot command check: Returns `true` if the message is sent to the proper channel. Throws `WrongChannelError` on failure. """ def predicate(context: commands.Context): if postentries_channel != 0 and context.channel.id == postentries_channel: return True raise WrongChannelError() return commands.check(predicate) @client.command() @commands.check(is_admin) @commands.check_any(is_postentries_channel(), commands.dm_only()) async def postentries(context: commands.Context) -> None: """ Post the entries of the week to the current channel. Works in the postentries channel or in DMs. """ week = compo.get_week(False) await publish_entries(context, week) @client.command() @commands.check(is_admin) @commands.dm_only() async def postentriespreview(context: commands.Context) -> None: """ Post the entries of the next week. Only works in DMs.
class management(commands.Cog): def __init__(self, bot): self.bot = bot self.brackets = ["()", "{}", "[]", "<>", None] @commands.command( aliases=["set_prefix"], description="Changes the prefix for the bot in the server") @commands.check_any(commands.has_permissions(manage_guild=True), commands.dm_only()) async def change_prefix(self, ctx, new_prefix): if not ctx.guild: f = open("data/prefixes-dm", "r") prefixes = f.read().split("\n")[:-1] f.close() for i in range(len(prefixes)): if prefixes[i][:prefixes[i].index(":")] == str(ctx.author.id): prefixes[i] = f"{ctx.author.id}:{new_prefix}" await ctx.send("Prefix changed") output = "" f = open("data/prefixes-dm", "w+") for user in prefixes: output += f"{user}\n" f.write(output) return f = open("data/prefixes", "r") prefixes = f.read().split("\n")[:-1] f.close() for i in range(len(prefixes)): if prefixes[i][:prefixes[i].index(":")] == str(ctx.guild.id): prefixes[i] = f"{ctx.guild.id}:{new_prefix}" await ctx.send("Prefix changed") output = "" f = open("data/prefixes", "w+") for guild in prefixes: output += f"{guild}\n" f.write(output) @commands.command( description= f"Changes the bot's nickname to include the prefix in a specified set of brackets in a specified place." ) @commands.has_permissions(manage_nicknames=True) async def update_username(self, ctx, brackets=None, place=None): if not ctx.guild: await ctx.send("I have no nickname here") return if not brackets: await ctx.send("Resetting username") await ctx.guild.me.edit(nick=self.bot.user.name) if not brackets in self.brackets: await ctx.send( f"Unrecognised brackets\nSuitable brackets are {self.brackets}" ) return place = place.lower() if not place in ["end", "start"]: await ctx.send( "Unsuitable place - Please type \"end\" or \"start\"") return try: if place == "end": await ctx.guild.me.edit( nick= f"{self.bot.user.name} {brackets[0]}{ctx.prefix}{brackets[1]}" ) else: await ctx.guild.me.edit( nick= f"{brackets[0]}{ctx.prefix}{brackets[1]} {self.bot.user.name}" ) except Exception as e: await ctx.send(e)
class ElectionCog(commands.Cog): def __init__(self, bot): self.bot = bot data = load( open("electionsbot/applications.json", "r", encoding="UTF-8")) self.candidateData = data["candidates"] self.candidates = {} self.voteSessions = {} # CONFIG SETTINGS self.CHOICE_MINIMUM = 2 self.CHOICE_MAXIMUM = 2 self.ready = False self.START_TIME = datetime.utcfromtimestamp(data["starttime"]) self.END_TIME = datetime.utcfromtimestamp(data["endtime"]) self.CREATION_CUTOFF = datetime.utcfromtimestamp( data["creationcutoff"]) # This will be automatically disabled if the number of candidates reaches 20. self.REACTION_INTERFACE = False @commands.Cog.listener() async def on_ready(self): # Load up all the relevant candidates! self.backendGuild = self.bot.get_guild(EMOJI_SERVER_ID) emoji = await self.backendGuild.fetch_emojis() for id, info in self.candidateData.items(): candidate = Candidate(id) user = self.bot.get_user(int(id)) if user: candidate.discorduser = user candidate.username = user.name + "#" + user.discriminator candidate.avatar = user.avatar_url_as(format='png', size=32) emojiimage = await candidate.avatar.read() emojiname = re.sub(r"\W+", "", (user.name[:28] + user.discriminator).replace(" ", "_")) for x in emoji: if x.name == emojiname: candidate.emoji = x break else: candidate.emoji = await self.backendGuild.create_custom_emoji( name=emojiname, image=emojiimage) else: candidate.username = info.get("username") candidate.avatar = info.get("avatar") try: if candidate.avatar: emojiimage = urllib.request.urlopen( url=candidate.avatar) else: emojiimage = urllib.request.urlopen( url="https://cdn.beano.dev/question-mark.png") except urllib.error.HTTPError: emojiimage = urllib.request.urlopen( url="https://cdn.beano.dev/question-mark.png") emojiname = re.sub(r"\W+", "", candidate.username.replace(" ", "_")) for x in emoji: if x.name == emojiname: candidate.emoji = x break else: candidate.emoji = await self.backendGuild.create_custom_emoji( name=emojiname, image=emojiimage.read()) candidate.campaign = info.get("campaign") self.candidates[int(id)] = candidate print(self.candidates) if len(self.candidates) >= 20: self.REACTION_INTERFACE = False self.ready = True def getCandidate(self, candidateID): return self.candidates.get(int(candidateID)) def getCandidateFromName(self, candidateName): for c in self.candidates.values(): if c.username == candidateName: return c return None def getCandidateFromEmoji(self, emoji): for c in self.candidates.values(): if c.emoji == emoji: return c return None def getAllCandidates(self): candidates = list(self.candidates.values()) print(candidates) random.shuffle( candidates) # Randomise the order each time for neutrality. return candidates @commands.command(aliases=["list"]) async def candidateList(self, ctx): names = [ str(candidate.emoji) + " " + candidate.username for candidate in self.getAllCandidates() ] await ctx.send( f"In a random order, the candidates currently standing are:\n" f"{chr(10).join(names)}") @commands.check_any(commands.has_role(ROOT_ROLE_ID), commands.dm_only()) @commands.command(aliases=["listAll"]) async def allCandidateDetails(self, ctx): for candidate in self.getAllCandidates(): await ctx.send(embed=candidate.getEmbed()) @commands.has_role(ROOT_ROLE_ID) @commands.command(aliases=["electiontotals"]) async def viewTotals(self, ctx): votes = await (await connectPostgres()).fetch( """select candidate, count(*) as votecount from ( select vote_1 as candidate from votes union all select vote_2 from votes ) t1 group by candidate order by votecount DESC""") out = "" for candidateid, count in votes: candidate = self.getCandidate(candidateid) out += f"{str(candidate.emoji)} {candidate.username}[{candidate.id}]: {count}\n" await ctx.send( f"The following is each member, followed by their number of votes.\n {out}" ) # @commands.has_role(ROOT_ROLE_ID) @commands.command(aliases=["myvote"]) async def viewMyVote(self, ctx): if not isinstance(ctx.channel, discord.DMChannel): return await ctx.channel.send( "You can only use this command in DMs.") votes = await (await connectPostgres()).fetch( "SELECT voter_id, vote_1, vote_2, datetime FROM votes WHERE voter_id=$1", ctx.author.id, ) if len(votes) > 0: vote = votes[0] chosen = [vote[1], vote[2]] candidates = [ c for c in self.getAllCandidates() if int(c.id) in chosen ] names = [ str(candidate.emoji) + " " + candidate.username for candidate in candidates ] return await ctx.send("You voted for: \n" + chr(10).join(names)) else: return await ctx.send("You haven't voted yet!") @commands.command(aliases=["start"]) async def vote(self, ctx): if not self.ready: return await ctx.send("I'm just getting ready, hold on!") if not isinstance(ctx.channel, discord.DMChannel): return await ctx.send("You can only use this command in DMs.", delete_after=20) if self.START_TIME > datetime.utcnow( ) or self.END_TIME < datetime.utcnow(): return await ctx.send("Voting is currently closed.") if self.CREATION_CUTOFF < ctx.author.created_at: return await ctx.send( "This account was created after the cutoff, and is therefore not eligible to vote." ) guild = self.bot.get_guild(VOTE_SERVER_ID) member = guild.get_member(ctx.author.id) # If the member exists on the server, the user has a role equal to or greater than the level 5 role in list # OR has Death To Bots; and Muted isn't the greatest role, continue, else return a message and abort vote. if not (member and (guild.get_role(LEVEL_ROLE_ID) <= member.top_role or guild.get_role(DTB_ROLE_ID) in member.roles) and not guild.get_role(MUTED_ROLE_ID) == member.top_role): return await ctx.send( "In order to be eligible to vote, you must have reached at least Mee6 Level 5, " "and not currently be muted.") if (len(await (await connectPostgres()).fetch( "SELECT voter_id FROM votes WHERE voter_id=$1", ctx.author.id)) > 0): return await ctx.send("You have already voted!") currentSession = self.voteSessions.get(ctx.author.id) if currentSession and not currentSession.hasTimedOut(): return await ctx.send( "You already have an active voting session! Please use that, or wait for it to expire." ) # Check to see if the person has already voted self.voteSessions[ctx.author.id] = VoteSession(user=ctx.author, timeout=300) if self.REACTION_INTERFACE: message = await ctx.send( "Click on the reactions representing the users you wish to vote for. Once you're " "done, react with a ✅ to confirm. If you need more time to decide, just ignore this message.\n\n" "**Remember, you can only vote for exactly two candidates, and " "you can't change your mind once you confirm!**\n\n" "*This prompt will expire in 5 minutes.*") for emoji in [ candidate.emoji for candidate in self.getAllCandidates() ]: await message.add_reaction(emoji) self.voteSessions[ctx.author.id].setMessage(message) await message.add_reaction("✅") else: names = [ str(candidate.emoji) + " " + candidate.username for candidate in self.getAllCandidates() ] message = await ctx.send( "Run the `:choose <candidate>` command, specifying the Discord Name of the user you wish to " "vote for; and repeat this for each choice you wish to make.\n" "If you want to cancel a choice, use `unchoose <candidate>`. \nOnce you're " "done, run the `confirm` command to confirm. To view a candidate's statement, use " "`candidate <candidate>`, or `listall` if you want them all at once." " If you need more time to decide, just ignore this message.\n\n" "**Remember, you can only vote for exactly two candidates, and" " you can't change your mind once you confirm!**\n\n" "As a reminder, in a random order, the candidates currently standing are:\n" f"**{chr(10).join(names)}**\n\n" "*This session will expire in 5 minutes.*") @commands.command(aliases=["lock", "submit"]) async def confirm(self, ctx): await self.confirm_callback(ctx.channel, ctx.author) async def confirm_callback(self, channel, author): if not self.ready: return await channel.send("I'm just getting ready, hold on!") if not isinstance(channel, discord.DMChannel): return await channel.send("You can only use this command in DMs.", delete_after=20) voteSession = self.voteSessions.get(author.id) if not voteSession: return await author.send( "You don't have a vote session active to confirm.") user = author chosenCandidates = voteSession.choices await user.send( f"You are about to vote for the following candidates:\n** " f'{chr(10).join([str(c.emoji) + " " + c.username for c in chosenCandidates])}**' ) if len(chosenCandidates) > self.CHOICE_MAXIMUM: return await user.send( f"""You've selected too many candidates. You can only select a maximum of {self.CHOICE_MAXIMUM} \ candidates. Please modify your choices, and confirm again once done.""") if len(chosenCandidates) < self.CHOICE_MINIMUM: return await user.send( f"""You've selected too few candidates! You need to select a minimum of {self.CHOICE_MINIMUM} \ candidates. Please modify your choices, and confirm again once done.""") else: m = await user.send( "Are you sure you wish to vote this way? React with a ✅ to finalise, or a 🚫 to cancel.\n" "*This prompt will expire in 90 seconds. Reactions will appear after 5 seconds.*" ) self.voteSessions[user.id].setMessage(m) self.voteSessions[user.id].confirm() self.voteSessions[user.id].setTimeout(95) await sleep(5) await m.add_reaction("✅") await m.add_reaction("🚫") @commands.command(aliases=["info", "candidate"]) async def candidateInfo(self, ctx, *, candidate: User): info = self.candidates.get(int(candidate.id)) print(info) if not info: await ctx.send( "We couldn't find any information on that candidate! They may not be running, or we might " "have identified the wrong user (We think you're asking about " f"`{candidate.name + '#' + candidate.discriminator}`).") else: await ctx.send(embed=info.getEmbed()) @commands.command(aliases=["pick", "select"]) async def choose(self, ctx, *, candidate: User): if not isinstance(ctx.channel, discord.DMChannel): await ctx.message.delete() return await ctx.send("You can only use this command in DMs.", delete_after=20) info = self.candidates.get(int(candidate.id)) voteSession = self.voteSessions.get(ctx.author.id) if not voteSession: return await ctx.send( "You must have an active votesession to make a choice. Use the vote " "command to start a votesession.") if voteSession.state != "PICK": return await ctx.send( "You cannot modify your choices if you are confirming them.") if not info: await ctx.send( "We couldn't find that candidate! (We think you're asking about " f"`{candidate.name + '#' + candidate.discriminator}`).") else: voteSession.addChoice(info) await ctx.send(f"Added {info.username} as a choice.") @commands.command(aliases=["unpick", "deselect"]) async def unchoose(self, ctx, *, candidate: User): if not isinstance(ctx.channel, discord.DMChannel): await ctx.message.delete() return await ctx.send("You can only use this command in DMs.", delete_after=20) info = self.candidates.get(int(candidate.id)) voteSession = self.voteSessions.get(ctx.author.id) if not voteSession: return await ctx.send( "You must have an active votesession to make a choice. Use the vote " "command to start a votesession.") if voteSession.state != "PICK": return await ctx.send( "You cannot modify your choices if you are confirming them.") if not info: await ctx.send( "We couldn't find that candidate! (We think you're asking about " f"`{candidate.name + '#' + candidate.discriminator}`).") else: voteSession.removeChoice(info) await ctx.send(f"Removed {info.username} as a choice.") @commands.has_role(ROOT_ROLE_ID) @commands.command() async def clearvote(self, ctx, voter: User): await (await connectPostgres()).execute( "DELETE FROM votes WHERE voter_id=$1", voter.id) await ctx.send(f"{voter.mention}'s votes have been cleared") return await voter.send( f"Your votes were cleared by <@{ctx.author.id}> - you can now place a new set." ) @commands.Cog.listener() async def on_reaction_add(self, reaction, user): """Reaction interface - Suited for under 20 members""" voteSession = self.voteSessions.get(user.id) if not voteSession or not self.ready: return if voteSession.message.id != reaction.message.id: return if (reaction.emoji == "✅" and not voteSession.hasTimedOut() and voteSession.state == "PICK"): voteSession.clearChoice() reactions = reaction.message.reactions for reaction in reactions: if user in await reaction.users().flatten(): choice = self.getCandidateFromEmoji(reaction.emoji) if choice: voteSession.addChoice(choice) await self.confirm_callback(reaction.channel, user) elif not voteSession.hasTimedOut() and voteSession.state == "CONFIRM": if reaction.emoji == "✅": # Votes have been confirmed. Go for it! await self.voteSessions[user.id].commit() # Committed the vote, now delete the session del self.voteSessions[user.id] await user.send("Your vote has been confirmed! Thank you :)") elif reaction.emoji == "🚫": # Vote was canceled, just delete the session without committing. del self.voteSessions[user.id] await user.send( "OK, I've canceled the voting session. When you've made up your mind, use the command again." ) elif voteSession.hasTimedOut(): del self.voteSessions[user.id] await user.send( "The voting session you had has now expired; you need to start a new one." ) @commands.Cog.listener() async def on_user_update(self, before, after): # Check if the user is a candidate if after.id in self.candidates.keys(): # Update the relevant candidate information candidate = self.getCandidate(after.id) user = after await self.backendGuild.fetch_emojis() candidate.discorduser = after candidate.username = user.name + "#" + user.discriminator candidate.avatar = user.avatar_url.avatar_url_as(format='png', size=32) emojiimage = await candidate.avatar.read() emojiname = re.sub(r"\W+", "", candidate.username.replace(" ", "_")) await candidate.emoji.delete() candidate.emoji = await self.backendGuild.create_custom_emoji( name=emojiname, image=emojiimage)
# Variables # Constants LOCALISATION_PATH = os.path.join(os.path.dirname(__file__), 'localisation', '') COMMANDES_PATH = os.path.join(os.path.dirname(__file__), 'commandes', '') COMMANDES = import_modules_from_path(COMMANDES_PATH) DM_COMMANDES_PATH = os.path.join(os.path.dirname(__file__), 'dm_commandes', '') DM_COMMANDES = import_modules_from_path(DM_COMMANDES_PATH) BOT = customBot(command_prefix="!", localisation_path=LOCALISATION_PATH, localisation='es') # Commands for commande in COMMANDES: BOT.command()(commande) for dm_commande in DM_COMMANDES: BOT.command()(commands.dm_only()(dm_commande)) # Events @client.event async def on_ready(): print('Beepboop o0/')