diff --git a/README.md b/README.md index 4c3e424..4ad31b1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,23 @@ -# karl-marx-2 -A poll-based moderation bot to take care of that noisy scallywag in #general +Karl Marx 2 +=========== +Karl Marx 2 takes a somewhat different spin on moderation for smaller servers (~10-35 users), handing the controls back to the users themselves. Instead of a single moderator issuing a command to mute, kick or banish a given user, the choice is left up to anyone and everyone present in the channel. Is the user at hand being annoying or spammy enough to be muted? Are they toxic enough to be kicked/banned? We'll take a vote! + +
+ +I didn't have as much time as I would have liked to work on this, but I after Hack Week I intend to progressively flesh out its functionality beyond just moderation and further improve it. + +Made with love. [Enjoy!](https://discordapp.com/oauth2/authorize?client_id=592852914553487370&permissions=8&scope=bot) :) + +**IMPORTANT:** json storage is still under development. + +Installation +------------ +1. Clone the repo wherever you want and make sure you have python3 installed with the discord.py package. (If you don't then just `pip3 install discord.py` and you should be good to go!) +2. `python3 bot.py` + +Commands +-------- +- `>>mute` - Hold a 30-second vote to mute a user for 10 minutes. You can set different values in `config.json`. +- `>>exile` - Euphamism for banning a user, I guess. By default, the vote lasts 3 hours, and requires that there be at least 10 votes and a 50% majority. Like the `>>mute` command, you can also tweak settings in `config.json`. +- `>>anthem` - First verse of the Soviet National Anthem. Might need that at some point to show your communist pride. +- `>>ping` - Fun leftovers from when I was first setting up the bot. :P diff --git a/bot.py b/bot.py index d535001..8424cd1 100644 --- a/bot.py +++ b/bot.py @@ -1,7 +1,18 @@ +""" +Karl Marx 2 +A bot by turtlebasket +""" + +import asyncio +import json import discord from discord.ext import commands -from time import sleep +MUTE_VOTE_TIME = 10 +MUTE_VOTER_MIN = 2 +MUTE_TIME = 600 # 10 mins +BAN_VOTE_TIME = 8 +BAN_VOTER_MIN = 4 bot = commands.Bot(command_prefix='>>') @bot.event @@ -22,38 +33,36 @@ async def anthem(ctx): command: anthem some people just need a reference, ya know? """ + await ctx.send("Soyuz nyerushimyiy ryespublik svobodnyikh\n" + "Splotila navyeki Vyelikaya Rus’.\n" + "Da zdravstvuyet sozdannyiy volyey narodov\n" + "Yedinyiy, moguchiy Sovyetskiy Soyuz!\n") - await ctx.send( - "Soyuz nyerushimyiy ryespublik svobodnyikh\n" - "Splotila navyeki Vyelikaya Rus’.\n" - "Da zdravstvuyet sozdannyiy volyey narodov\n" - "Yedinyiy, moguchiy Sovyetskiy Soyuz!\n" - ) - -@bot.command() -async def mute(ctx, target_user:str): +# not a command, just some functionality that's used across commands +async def take_vote(ctx, question:str, wait_time): """ - command: soft_mute - The actual good stuff. - If someone's spamming or being annoying, just mute 'em. Requires 3 votes by default. + take_vote(ctx, question:str) - Collects votes + ctx: pass from command function + question: what to ask + + returns [, ]. + It's up to the context/use case to decide how these should be used. """ - votey_message = await ctx.send( - "=== NEW MUTE VOTE ===\n" - "Mute {}?".format(str(target_user))) + votey_message = await ctx.send("**=== NEW VOTE ===**\n{}".format(question)) await votey_message.add_reaction('✅') await votey_message.add_reaction('❌') - sleep(10) + await asyncio.sleep(wait_time) finished_votey = await votey_message.channel.fetch_message(votey_message.id) - print(finished_votey.reactions) all_in_favor = 0 not_in_favor = 0 + for reaction in finished_votey.reactions: if str(reaction.emoji) == '✅': - all_in_favor += reaction.count-1 + all_in_favor += reaction.count-1 # don't include bot's reaction elif str(reaction.emoji) == '✅': not_in_favor += reaction.count-1 else: @@ -63,11 +72,50 @@ async def mute(ctx, target_user:str): "**=== VOTE RESULTS ===**\n" "✅ - {0}\n" "❌ - {1}\n".format(all_in_favor, not_in_favor)) + return [all_in_favor, not_in_favor] + +@bot.command() +async def mute(ctx, target_user:discord.User): + + results = await take_vote(ctx, "Mute `{}`?".format(target_user), MUTE_VOTE_TIME) + all_in_favor = results[0] + not_in_favor = results[1] + + if all_in_favor > 0 and all_in_favor - not_in_favor > 0: + # Take action to mute user + + # add temp. role for mute + muted_role = await ctx.guild.create_role(name="Muted") + # edit role position to take precedence over other roles + await muted_role.edit(position=ctx.guild.get_member(target_user.id).top_role.position+1) + + for channel in ctx.guild.channels: + await channel.set_permissions(muted_role, read_messages=True, send_messages=False, add_reactions=False, connect=False) + + await ctx.guild.get_member(target_user.id).add_roles(muted_role) + await ctx.send("**{0}, the majority has ruled that you should be muted.** See ya in {1} minutes!".format(target_user, int(MUTE_TIME/60))) + await asyncio.sleep(MUTE_TIME) + await muted_role.delete() - if all_in_favor > 2 and all_in_favor - not_in_favor > 0: - await ctx.send("{} **has** been muted.".format(target_user)) else: - await ctx.send("{} **has not** been muted.".format(target_user)) - print("all in favor: {}".format(all_in_favor)) + await ctx.send("**{} has not been muted.**".format(target_user)) + +@bot.command() +async def roletest(ctx): + await ctx.send("`{}`".format(ctx.guild.roles)) + +@bot.command() +async def exile(ctx, target_user:discord.User): + results = await take_vote(ctx, "Ban `{}`?".format(target_user), BAN_VOTE_TIME) + all_in_favor = results[0] + not_in_favor = results[1] + + if all_in_favor > not_in_favor and all_in_favor >= BAN_VOTER_MIN: # change to 10 later + await ctx.guild.ban(target_user) + await ctx.send(":crab: :crab: `{}` IS GONE :crab: :crab:".format(target_user.name)) + elif all_in_favor <= all_in_favor: + await ctx.send("The majority (>50%) did not decide on banning `{}`.".format(target_user.name)) + elif all_in_favor < 1: + await ctx.send("Not enough users voted to ban `{}`.".format(target_user.name)) bot.run(open("token.txt").read().strip()) diff --git a/mute_demo.png b/mute_demo.png new file mode 100644 index 0000000..8b66807 Binary files /dev/null and b/mute_demo.png differ