Fix mute/kick commands, add async locks, update README
parent
e3f6f307b1
commit
15d272b46c
15
README.md
15
README.md
|
@ -1,23 +1,24 @@
|
|||
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!
|
||||
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 even *ban* 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? Let's take a vote!
|
||||
|
||||
<center><img src="mute_demo.png" width=350/></center>
|
||||
<center><img src="mute_demo.png" width=400/></center>
|
||||
|
||||
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.
|
||||
I didn't have as much time as I would have liked to work on this, but even 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`
|
||||
2. Grab the token of your frontend bot application and paste into `<path/to/bot>/token.txt`.
|
||||
3. `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`.
|
||||
- `>>manual` - Get ye some help
|
||||
- `>>mute` - Hold a 30-second vote to mute a user for 10 minutes (minimum voters: 4, over 50% majority required). You can set different requirements in `config.json`.
|
||||
- `>>kick` - Kick a user. The vote is up for 5 minutes, and requires that a minimum of 6 users and >50% approve.
|
||||
- `>>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
|
||||
|
|
200
bot.py
200
bot.py
|
@ -8,21 +8,59 @@ import json
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
MUTE_VOTE_TIME = 10
|
||||
MUTE_VOTER_MIN = 2
|
||||
MUTE_TIME = 600 # 10 mins
|
||||
BAN_VOTE_TIME = 8
|
||||
BAN_VOTER_MIN = 4
|
||||
# I know, config parsing is ugly and bad, I'll get around to refactoring later TwT
|
||||
|
||||
with open('config.json', 'r') as json_file:
|
||||
config = json.load(json_file)
|
||||
|
||||
MUTE_VOTE_TIME = config["MUTE_VOTE_TIME"]
|
||||
MIN_MUTE_VOTERS = config["MIN_MUTE_VOTERS"] # should be 3
|
||||
MUTE_TIME = config["MUTE_TIME"] # 10 mins
|
||||
|
||||
KICK_VOTE_TIME = config["KICK_VOTE_TIME"]
|
||||
MIN_KICK_VOTERS = config["MIN_KICK_VOTERS"]
|
||||
|
||||
BAN_VOTE_TIME = config["BAN_VOTE_TIME"]
|
||||
MIN_BAN_VOTERS = config["MIN_BAN_VOTERS"]
|
||||
|
||||
bot = commands.Bot(command_prefix='>>')
|
||||
|
||||
# To store users who are currently being voted on
|
||||
muted_users = []
|
||||
muting_users = []
|
||||
kicking_users = []
|
||||
banning_users = []
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print("Bot started.")
|
||||
print("--------------------------")
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def manual(ctx):
|
||||
embed = discord.Embed(title="How 2 Comrade")
|
||||
embed.add_field(
|
||||
name=">>mute",
|
||||
value="Hold a 30-second vote to mute a user for 10 minutes (minimum voters: 4, over 50% majority required). You can set different requirements in `config.json`."
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name=">>kick",
|
||||
value="Kick user. The vote is up for 5 minutes, and requires that a minimum of 6 users and >50% approve."
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name=">>exile",
|
||||
value="Ban user. By default, the vote lasts 3 hours, and requires that there be at least 10 votes and a 50% majority. Like the `>>mute`/`>>kick` commands, you can also tweak settings in `config.json`."
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@bot.command()
|
||||
async def ping(ctx):
|
||||
""" command: ping
|
||||
"""
|
||||
command: ping
|
||||
Use with caution, you might stir up a revolution.
|
||||
"""
|
||||
await ctx.send("What do you think I am, some sort of toy? I refuse to bend to the will of the bourgeoisie!")
|
||||
|
@ -38,8 +76,9 @@ async def anthem(ctx):
|
|||
"Da zdravstvuyet sozdannyiy volyey narodov\n"
|
||||
"Yedinyiy, moguchiy Sovyetskiy Soyuz!\n")
|
||||
|
||||
# not a command, just some functionality that's used across commands
|
||||
# not commands, just some functionality that's used across commands
|
||||
async def take_vote(ctx, question:str, wait_time):
|
||||
|
||||
"""
|
||||
take_vote(ctx, question:str) - Collects votes
|
||||
ctx: pass from command function
|
||||
|
@ -49,7 +88,13 @@ async def take_vote(ctx, question:str, wait_time):
|
|||
It's up to the context/use case to decide how these should be used.
|
||||
"""
|
||||
|
||||
votey_message = await ctx.send("**=== NEW VOTE ===**\n{}".format(question))
|
||||
votey_message = await ctx.send(
|
||||
embed=discord.Embed(
|
||||
type="rich",
|
||||
title="NEW VOTE",
|
||||
description="{}\n\n✅ - Yes\n\n❌ - No".format(question)
|
||||
)
|
||||
)
|
||||
|
||||
await votey_message.add_reaction('✅')
|
||||
await votey_message.add_reaction('❌')
|
||||
|
@ -59,63 +104,160 @@ async def take_vote(ctx, question:str, wait_time):
|
|||
finished_votey = await votey_message.channel.fetch_message(votey_message.id)
|
||||
all_in_favor = 0
|
||||
not_in_favor = 0
|
||||
|
||||
for reaction in finished_votey.reactions:
|
||||
if str(reaction.emoji) == '✅':
|
||||
all_in_favor += reaction.count-1 # don't include bot's reaction
|
||||
elif str(reaction.emoji) == '✅':
|
||||
if str(reaction.emoji) == '❌':
|
||||
not_in_favor += reaction.count-1
|
||||
else:
|
||||
pass
|
||||
|
||||
await ctx.send(
|
||||
"**=== VOTE RESULTS ===**\n"
|
||||
"✅ - {0}\n"
|
||||
"❌ - {1}\n".format(all_in_favor, not_in_favor))
|
||||
await ctx.send(embed=discord.Embed(type='rich', title="VOTE RESULTS", description="✅ - {0}\n\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):
|
||||
|
||||
if target_user in muting_users:
|
||||
await ctx.send("There is already a mute vote on `{}`!".format(target_user))
|
||||
return
|
||||
elif target_user in muted_users:
|
||||
await ctx.send("`{}` is already muted!".format(target_user))
|
||||
return
|
||||
|
||||
muting_users.append(target_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
|
||||
muting_users.remove(target_user)
|
||||
|
||||
if all_in_favor >= MIN_MUTE_VOTERS and all_in_favor - not_in_favor > 0:
|
||||
# Add to muted_users
|
||||
muted_users.append(target_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)
|
||||
|
||||
# change channel permissions for new role
|
||||
for channel in ctx.guild.channels:
|
||||
await channel.set_permissions(muted_role, read_messages=True, send_messages=False, add_reactions=False, connect=False)
|
||||
if channel is discord.TextChannel and target_user in channel.members:
|
||||
await channel.set_permissions(muted_role, read_messages=True, send_messages=False, add_reactions=False)
|
||||
|
||||
elif channel is discord.VoiceChannel:
|
||||
await channel.set_permissions(muted_role, connect=False)
|
||||
|
||||
# Give role to member
|
||||
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)))
|
||||
|
||||
endmessage = "**{0}, the majority has ruled that you should be muted.** See ya in {1} minutes!".format(target_user, int(MUTE_TIME/60))
|
||||
|
||||
await ctx.send(
|
||||
embed=discord.Embed(
|
||||
type='rich',
|
||||
title="MUTE VOTE VERDICT",
|
||||
description=endmessage
|
||||
)
|
||||
)
|
||||
|
||||
await asyncio.sleep(MUTE_TIME)
|
||||
|
||||
await muted_role.delete()
|
||||
|
||||
# Remove from muted_users
|
||||
muted_users.remove(target_user)
|
||||
|
||||
return
|
||||
|
||||
elif all_in_favor <= not_in_favor:
|
||||
endmessage = "A >50% vote was not reached."
|
||||
|
||||
elif all_in_favor < MIN_MUTE_VOTERS:
|
||||
endmessage = "Not enough users voted to mute `{0}` (min: {1})".format(target_user, MIN_MUTE_VOTERS)
|
||||
|
||||
else:
|
||||
await ctx.send("**{} has not been muted.**".format(target_user))
|
||||
endmessage = "**`{}` has not been muted.**".format(target_user)
|
||||
|
||||
await ctx.send(
|
||||
embed=discord.Embed(
|
||||
type='rich',
|
||||
title="MUTE VOTE VERDICT",
|
||||
description=endmessage
|
||||
)
|
||||
)
|
||||
|
||||
@bot.command()
|
||||
async def roletest(ctx):
|
||||
await ctx.send("`{}`".format(ctx.guild.roles))
|
||||
async def kick(ctx, target_user:discord.User):
|
||||
|
||||
if target_user in kicking_users:
|
||||
await ctx.send("There is already a kick vote on `{}`!".format(target_user))
|
||||
return
|
||||
|
||||
# add to kicking_users
|
||||
kicking_users.append(target_user)
|
||||
|
||||
results = await take_vote(ctx, "Kick `{}`?".format(target_user), KICK_VOTE_TIME)
|
||||
all_in_favor = results[0]
|
||||
not_in_favor = results[1]
|
||||
|
||||
if all_in_favor > not_in_favor and all_in_favor >= MIN_KICK_VOTERS: # change to 10 later
|
||||
await ctx.guild.ban(target_user)
|
||||
endmessage = "`{}` was kicked.".format(target_user.name)
|
||||
|
||||
elif all_in_favor <= not_in_favor:
|
||||
endmessage = "The majority (>50%) did not decide on kicking `{}`.".format(target_user.name)
|
||||
|
||||
elif all_in_favor < MIN_KICK_VOTERS:
|
||||
endmessage = "Not enough users voted to kick `{0}` (min: {1}).".format(target_user.name, MIN_KICK_VOTERS)
|
||||
|
||||
else:
|
||||
endmessage = "`{}` was not kicked.".format(target_user.name)
|
||||
|
||||
kicking_users.remove(target_user)
|
||||
|
||||
await ctx.send(
|
||||
embed=discord.Embed(
|
||||
type="rich",
|
||||
title="KICK VOTE VERDICT",
|
||||
description=endmessage
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def exile(ctx, target_user:discord.User):
|
||||
|
||||
if target_user in banning_users:
|
||||
await ctx.send("There is already a ban vote on `{}`!".format(target_user))
|
||||
return
|
||||
|
||||
# add to banning_users
|
||||
banning_users.append(target_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
|
||||
if all_in_favor > not_in_favor and all_in_favor >= MIN_BAN_VOTERS: # 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))
|
||||
endmessage = ":crab: :crab: `{}` IS GONE :crab: :crab:".format(target_user.name)
|
||||
elif all_in_favor <= not_in_favor:
|
||||
endmessage = "The majority (>50%) did not decide on banning `{}`.".format(target_user.name)
|
||||
elif all_in_favor < MIN_BAN_VOTERS:
|
||||
endmessage = "Not enough users voted to ban `{0}` (min: {1}.".format(target_user.name, MIN_BAN_VOTERS)
|
||||
else:
|
||||
endmessage = "`{}` was not banned.".format(target_user.name)
|
||||
|
||||
banning_users.remove(target_user)
|
||||
|
||||
await ctx.send(
|
||||
embed=discord.Embed(
|
||||
type="rich",
|
||||
title="BAN VOTE VERDICT",
|
||||
description=endmessage
|
||||
)
|
||||
)
|
||||
|
||||
bot.run(open("token.txt").read().strip())
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"MUTE_VOTE_TIME" : 30,
|
||||
"MIN_MUTE_VOTERS" : 4,
|
||||
"MUTE_TIME" : 600,
|
||||
|
||||
"KICK_VOTE_TIME" : 300,
|
||||
"MIN_KICK_VOTERS" : 6,
|
||||
|
||||
"BAN_VOTE_TIME" : 600,
|
||||
"MIN_BAN_VOTERS" : 8
|
||||
}
|
BIN
mute_demo.png
BIN
mute_demo.png
Binary file not shown.
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 26 KiB |
Loading…
Reference in New Issue