Skip to content

Commit

Permalink
Resolved config.py conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
tser0f committed Jul 24, 2020
2 parents d12b2a7 + 690eb8f commit 306fae2
Show file tree
Hide file tree
Showing 8 changed files with 370 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# Ignore virtual environments
venv/
env/

# Ignore editor files
.vscode/
.*.swp
.vs/
*.sln
*.pyproj
*.pyproj.user

# Configuration
/config*.toml
Expand Down
4 changes: 2 additions & 2 deletions futaba/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
import schema
from toml import TomlDecodeError

from . import client
from .config import load_config
from futaba import client
from futaba.config import load_config

LOG_FILE = "futaba.log"
LOG_FILE_MODE = "w"
Expand Down
30 changes: 30 additions & 0 deletions futaba/cogs/pingable/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#
# cogs/pingable/__init__.py
#
# futaba - A Discord Mod bot for the Programming server
# Copyright (c) 2017-2020 Jake Richardson, Ammon Smith, jackylam5
#
# futaba is available free of charge under the terms of the MIT
# License. You are free to redistribute and/or modify it under those
# terms. It is distributed in the hopes that it will be useful, but
# WITHOUT ANY WARRANTY. See the LICENSE file for more details.
#

from .core import Pingable

# Setup for when cog is loaded
def setup(bot):
setup_pingable(bot)


def setup_pingable(bot):
cog = Pingable(bot)
bot.add_cog(cog)


def teardown(bot):
teardown_pingable(bot)


def teardown_navi(bot):
bot.remove_cog(Pingable.__name__)
98 changes: 98 additions & 0 deletions futaba/cogs/pingable/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#
# cogs/pingable/core.py
#
# futaba - A Discord Mod bot for the Programming server
# Copyright (c) 2017-2020 Jake Richardson, Ammon Smith, jackylam5
#
# futaba is available free of charge under the terms of the MIT
# License. You are free to redistribute and/or modify it under those
# terms. It is distributed in the hopes that it will be useful, but
# WITHOUT ANY WARRANTY. See the LICENSE file for more details.
#

"""
Cog for pingable helper roles
"""

import logging
from datetime import datetime, timedelta

import discord
from discord.ext import commands
from futaba.permissions import mod_perm
from futaba.exceptions import CommandFailed
from futaba.utils import fancy_timedelta
from ..abc import AbstractCog

logger = logging.getLogger(__name__)

__all__ = ["Pingable"]


class Pingable(AbstractCog):
__slots__ = ("journal",)

def __init__(self, bot):
super().__init__(bot)
self.journal = bot.get_broadcaster("/pingable")
self.cooldowns = {}

def setup(self):
pass

@commands.command(name="pinghelpers", aliases=["helpme", "pinghelp"])
async def pinghelpers(self, ctx):
"""Pings helpers if used in the respective channel"""

cooldown_time = self.bot.config.helper_ping_cooldown

logger.info(
"User '%s' (%d) is pinging the helper role in channel '%s' in guild '%s' (%d)",
ctx.author,
ctx.author.mention,
ctx.channel,
ctx.guild,
ctx.guild.id,
)
pingable_channels = self.bot.sql.roles.get_pingable_role_channels(ctx.guild)
# this will return an empty list if there is nothing.
channel_role = [
(channel, role)
for channel, role in pingable_channels
if channel == ctx.channel
]

if not channel_role:
embed = discord.Embed(colour=discord.Colour.red())
embed.set_author(name="Failed to ping helper role.")
embed.description = f"There is no helper role set for this channel."
raise CommandFailed(embed=embed)

channel_user = (ctx.channel.id, ctx.author.id)
cooldown = self.cooldowns.get(channel_user)

if mod_perm(ctx) or not cooldown or cooldown <= datetime.now():
self.cooldowns[channel_user] = datetime.now() + timedelta(
seconds=cooldown_time
)

# This will loop over the dictionary and remove expired entries.
key_list = list(self.cooldowns.keys())
for k in key_list:
if self.cooldowns[k] < datetime.now():
del self.cooldowns[k]

# channel[0] will be the first tuple in the list. there will only be one, since the
# channel's id is a primary key (tb_pingable_role_channel in roles.py). channel[0][1] is the role.
await ctx.send(
f"{channel_role[0][1].mention}, {ctx.author.mention} needs help."
)

elif cooldown > datetime.now():
# convert deltatime into string: Hh, Mm, Ss
time_remaining = cooldown - datetime.now()

embed = discord.Embed(colour=discord.Colour.red())
embed.set_author(name="Failed to ping helper role.")
embed.description = f"You can ping the helper role for this channel again in {fancy_timedelta(time_remaining)}"
raise CommandFailed(embed=embed)
161 changes: 157 additions & 4 deletions futaba/cogs/roles/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
# Copyright (c) 2017-2020 Jake Richardson, Ammon Smith, jackylam5
#
# futaba is available free of charge under the terms of the MIT
# License. You are free to redistribute and/or modify it under those
# terms. It is distributed in the hopes that it will be useful, but
# WITHOUT ANY WARRANTY. See the LICENSE file for more details.
# License. You are free to redistribute and/or modify it under those
# terms. It is distributed in the hopes that it will be useful, but
# WITHOUT ANY WARRANTY. See the LICENSE file for more details.
#

"""
Cog for maintaining a guild's list of self-assignable roles, roles which
do not provide any special permissions, but are markers that members can
Expand Down Expand Up @@ -110,6 +109,43 @@ async def role_show(self, ctx):
embed.description = str(descr)
await ctx.send(embed=embed)

@role.command(name="pshow", aliases=["pdisplay", "plist", "plsar", "pls"])
@commands.guild_only()
@permissions.check_mod()
async def pingable_show(self, ctx):
""" Shows all channels where a role is pingable. """
logger.info(
"Displaying pingable channels and roles in guild '%s' (%d)",
ctx.guild.name,
ctx.guild.id,
)

# r[0] == current channel.
channel_role = sorted(
self.bot.sql.roles.get_pingable_role_channels(ctx.guild),
key=lambda r: r[0].name,
)

if not channel_role:
prefix = self.bot.prefix(ctx.guild)
embed = discord.Embed(colour=discord.Colour.dark_purple())
embed.set_author(name="No pingable roles in this guild")
embed.description = (
f"Moderators can use the `{prefix}role pingable/unpingable` "
"commands to change this list!"
)
await ctx.send(embed=embed)
return

embed = discord.Embed(colour=discord.Colour.dark_teal())
embed.set_author(name="Pingable roles (channel, role)")

descr = StringBuilder(sep="\n")
for channel, role in channel_role:
descr.write(f"{channel.mention}: {role.mention}")
embed.description = str(descr)
await ctx.send(embed=embed)

def check_roles(self, ctx, roles):
if not roles:
raise CommandFailed()
Expand Down Expand Up @@ -265,6 +301,123 @@ async def role_unjoinable(self, ctx, *roles: RoleConv):
"joinable/remove", ctx.guild, content, icon="role", roles=roles
)

@staticmethod
def str_channels(channels):
return " ".join(f"`{channel.name}`" for channel in channels)

@role.command(name="pingable")
@commands.guild_only()
@permissions.check_mod()
async def role_pingable(self, ctx, role: RoleConv, *channels: TextChannelConv):
logger.info(
"Making role '%s' pingable in guild '%s' (%d), channel(s) [%s]",
role.name,
ctx.guild.name,
ctx.guild.id,
self.str_channels(channels),
)

if not channels:
raise CommandFailed()

# self.bot.sql.roles.get_pingable_role_channels(ctx.guild) gets a set
# of tuples (channel, role).
# Zip converts it into a set of channels and a set of roles.
channel_role = zip(*self.bot.sql.roles.get_pingable_role_channels(ctx.guild))
# this makes a list of all channels and rows, currently it's a channel and row pair.
# next gets the next item from the zip iterator which is the set of channels.
# if the iterator is exhausted i.e there's no pingable channels, the default value set() will be used.
pingable_channels = next(channel_role, set())

# channels that were already in the database will not be added, user
# will be informed.
exempt_channels = []

with self.bot.sql.transaction():
for channel in channels:
if channel not in pingable_channels:
self.bot.sql.roles.add_pingable_role_channel(
ctx.guild, channel, role
)
else:
exempt_channels.append(channel)

if exempt_channels:
embed = discord.Embed(colour=discord.Colour.dark_grey())
embed.set_author(name="Failed to make role pingable in channels: ")
descr = StringBuilder(sep=", ")
for channel in exempt_channels:
descr.write(channel.mention)
embed.description = str(descr)
await ctx.send(embed=embed)
# Did not put the embed in CommandFailed. All channels must fail
# to be added for the entire command to 'fail', imo.
if set(exempt_channels) == set(channels):
raise CommandFailed()

# Send journal event
content = f"Role was set as pingable in channels: {self.str_channels(channels)}, except {self.str_channels(exempt_channels)}"
self.journal.send(
"pingable/add",
ctx.guild,
content,
icon="role",
role=role,
channels=channels,
)

@role.command(name="unpingable")
@commands.guild_only()
@permissions.check_mod()
async def role_unpingable(self, ctx, role: RoleConv, *channels: TextChannelConv):
logger.info(
"Making role '%s' not pingable in guild '%s' (%d), channel(s) [%s]",
role.name,
ctx.guild.name,
ctx.guild.id,
self.str_channels(channels),
)

if not channels:
raise CommandFailed()

# See role_pingable for an explanation
channel_role = zip(*self.bot.sql.roles.get_pingable_role_channels(ctx.guild))
pingable_channels = next(channel_role, set())

exempt_channels = []

with self.bot.sql.transaction():
for channel in channels:
if channel in pingable_channels:
self.bot.sql.roles.remove_pingable_role_channel(
ctx.guild, channel, role
)
else:
exempt_channels.append(channel)

if exempt_channels:
embed = discord.Embed(colour=discord.Colour.dark_grey())
embed.set_author(name="Failed to make role unpingable in channels: ")
descr = StringBuilder(sep=", ")
for channel in exempt_channels:
descr.write(channel.mention)
embed.description = str(descr)
await ctx.send(embed=embed)
if set(exempt_channels) == set(channels):
raise CommandFailed()

# Send journal event
content = f"Role was set as not pingable in channels: {self.str_channels(channels)}, except {self.str_channels(exempt_channels)}"
self.journal.send(
"pingable/remove",
ctx.guild,
content,
icon="role",
role=role,
channels=channels,
)

def channel_journal(self, guild):
all_channels = self.bot.sql.roles.get_role_command_channels(guild)
str_channels = " ".join(chan.mention for chan in all_channels)
Expand Down
7 changes: 6 additions & 1 deletion futaba/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ def wrapper(value):
"error-channel-id": Or(And(str, ID_REGEX.match), "0"),
},
"cogs": {"example": object, "statbot": object, "gist": object},
"moderation": {"max-cleanup-messages": And(str, _check_gtz(int))},
"moderation": {
"max-cleanup-messages": And(str, _check_gtz(int)),
"ping-cooldown": And(str, _check_gtz(int)),
},
"delay": {
"chunk-size": And(str, _check_gtz(int)),
"sleep": And(str, _check_gtz(float)),
Expand All @@ -68,6 +71,7 @@ def wrapper(value):
"error_channel_id",
"optional_cogs",
"max_cleanup_messages",
"helper_ping_cooldown",
"delay_chunk_size",
"delay_sleep",
"anger_emoji_id",
Expand All @@ -92,6 +96,7 @@ def load_config(path):
error_channel_id=int(config["bot"]["error-channel-id"]),
optional_cogs=config["cogs"],
max_cleanup_messages=int(config["moderation"]["max-cleanup-messages"]),
helper_ping_cooldown=int(config["moderation"]["ping-cooldown"]),
delay_chunk_size=int(config["delay"]["chunk-size"]),
delay_sleep=float(config["delay"]["sleep"]),
anger_emoji_id=int(config["emojis"]["anger"]),
Expand Down
Loading

0 comments on commit 306fae2

Please sign in to comment.