-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from organic-bots/rewrite
This was a huge update I'm happy to pronounce complete ! Its main purpose has been achieved. We've been able to completely rewrite the entire code to make it more pythonic. This major rewrite now allows one to update the code without taking the bot offline by using the smart extension principle of the API. It is also much easier to find out bugs happening to the bot for a logging has been fully implemented to the bot and is easily extendable. This was one of the main purpose: make any future addition much easier to both build and implement. This should also allow a much wider range of contributors to help us in the development. This branch doesn't solve everything though, there are still efforts required toward documentation (see #1 ) and repository structure. But this is already an important milestone which paves the way to all future updates. Finally a number of issues have been fixed in this update as you can see in the following change log: admins can now update extensions on the fly with a new & safe ext suite of functions (#8 ) added smart and extensive logging support (#13 ) added a new suite of commands that allow smart task list management using labels directly from discord changed global settings from using unsafe names to unique snowflake discord IDs poll colors are now dynamic and change with every new reaction (#12 ) changed the design of embeds to give more credits to authors (#11 ) fixed a bug where users could have multiple opposite reactions to the same poll (#15 ) gave more freedom to admins by letting them choose how much they wish to pardon a user (#16 ) added the clear command that lets admins delete a set amount of messages to ease moderation (#17 ) embeds now support images (#6 ) users can now delete their own polls which prevents admin from having to clean mistakes themselves (#5 )
- Loading branch information
Showing
10 changed files
with
940 additions
and
275 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
forebot.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import logging | ||
from settings import * | ||
import discord | ||
from checks import * | ||
|
||
|
||
|
||
######################################### | ||
# # | ||
# # | ||
# Setting up logging # | ||
# # | ||
# # | ||
######################################### | ||
local_logger = logging.getLogger(__name__) | ||
local_logger.setLevel(LOGGING_LEVEL) | ||
local_logger.addHandler(LOGGING_HANDLER) | ||
local_logger.info("Innitalized {} logger".format(__name__)) | ||
|
||
|
||
|
||
######################################### | ||
# # | ||
# # | ||
# BotEssentials # | ||
# # | ||
# # | ||
######################################### | ||
'''This cog contains all the basemost functions that all bots should contain. | ||
See https://github.com/organic-bots/ForeBot for more information''' | ||
|
||
|
||
class BotEssentials(commands.Cog): | ||
"""All of the essential methods all of our bots should have""" | ||
def __init__(self, bot): | ||
self.bot = bot | ||
|
||
@commands.Cog.listener() | ||
async def on_ready(self): | ||
print('We have logged in as {0.user}'.format(self.bot)) | ||
|
||
@commands.Cog.listener() | ||
async def on_member_join(self, member): | ||
local_logger.info("User {0.name}[{0.id}] joined {1.name}[{1.id}]".format(member, member.guild)) | ||
await member.guild.system_channel.send("Welcome to {} {}! Please make sure to take a look at our {} and before asking a question, at the {}".format(member.guild.name, member.mention, CHANNELS["rules"].mention, CHANNELS["faq"].mention)) | ||
|
||
|
||
@commands.command() | ||
async def ping(self, ctx): | ||
'''This command responds with the current latency.''' | ||
latency = self.bot.latency | ||
await ctx.send("**Pong !** Latency of {0:.3f} seconds".format(latency)) | ||
|
||
|
||
#Command that shuts down the bot | ||
@commands.command() | ||
@is_runner() | ||
async def shutdown(self, ctx): | ||
print("Goodbye") | ||
local_logger.info("Switching to invisible mode") | ||
await self.bot.change_presence(status=discord.Status.offline) | ||
#time.sleep(0.1) | ||
await ctx.send("Goodbye") | ||
local_logger.info("Closing connection to discord") | ||
await self.bot.close() | ||
local_logger.info("Quitting python") | ||
await quit() | ||
|
||
@commands.command() | ||
@commands.has_any_role(*GESTION_ROLES) | ||
async def clear(slef, ctx, nbr:int): | ||
'''deletes specified <nbr> number of messages in the current channel''' | ||
async for msg in ctx.channel.history(limit=nbr): | ||
local_logger.info("Deleting {}".format(msg)) | ||
try: | ||
await msg.delete() | ||
except Exception as e: | ||
local_logger.exception("Couldn't delete {}".format(msg)) | ||
raise e | ||
|
||
|
||
|
||
|
||
def setup(bot): | ||
bot.add_cog(BotEssentials(bot)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import logging | ||
import discord | ||
from settings import * | ||
from checks import * | ||
|
||
######################################### | ||
# # | ||
# # | ||
# Setting up logging # | ||
# # | ||
# # | ||
######################################### | ||
local_logger = logging.getLogger(__name__) | ||
local_logger.setLevel(LOGGING_LEVEL) | ||
local_logger.addHandler(LOGGING_HANDLER) | ||
local_logger.info("Innitalized {} logger".format(__name__)) | ||
|
||
|
||
|
||
######################################### | ||
# # | ||
# # | ||
# Making commands # | ||
# # | ||
# # | ||
######################################### | ||
|
||
|
||
class Embedding(commands.Cog): | ||
"""A suite of command providing users with embeds manipulation tools.""" | ||
def __init__(self, bot): | ||
self.bot = bot | ||
#making poll_allowed channels according to the message's guild | ||
self.poll_allowed_chans = {} | ||
with open(POLL_ALLOWED_CHANNELS_FILE) as file: | ||
for line in file.readlines(): | ||
self.poll_allowed_chans[line.split(";")[0]] = [chan_id for chan_id in line.split(";")[1:]] | ||
|
||
|
||
@commands.command() | ||
async def embed(self, ctx, *args): | ||
"""allows you to post a message as an embed. Your msg will be reposted by the bot as an embed !""" | ||
if ctx.channel.name in self.poll_allowed_chans: | ||
local_logger.info("Preventing user from making an embed in a poll channel") | ||
await ctx.message.delete() | ||
return | ||
|
||
msg = "" | ||
for arg in args: | ||
if arg.startswith("https://"): | ||
img_url = arg | ||
else: | ||
msg += " {}".format(arg) | ||
|
||
print(msg) | ||
embed_msg = discord.Embed( | ||
title = None, | ||
description = msg, | ||
colour = ctx.author.color, | ||
url = None | ||
) | ||
if img_url: | ||
embed_msg.set_image(url=img_url) | ||
embed_msg.set_footer(text=ctx.message.author.name, icon_url=ctx.message.author.avatar_url) | ||
|
||
await ctx.message.delete() | ||
await ctx.message.channel.send(embed=embed_msg) | ||
|
||
def setup(bot): | ||
bot.add_cog(Embedding(bot)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
import os | ||
import logging | ||
import discord | ||
from settings import * | ||
from checks import * | ||
|
||
######################################### | ||
# # | ||
# # | ||
# Setting up logging # | ||
# # | ||
# # | ||
######################################### | ||
local_logger = logging.getLogger(__name__) | ||
local_logger.setLevel(LOGGING_LEVEL) | ||
local_logger.addHandler(LOGGING_HANDLER) | ||
local_logger.info("Innitalized {} logger".format(__name__)) | ||
|
||
|
||
|
||
######################################### | ||
# # | ||
# # | ||
# Making commands # | ||
# # | ||
# # | ||
######################################### | ||
|
||
|
||
class Poll(commands.Cog): | ||
"""TODO: A suite of commands providing users with tools to more easilly get the community's opinion on an idea""" | ||
def __init__(self, bot): | ||
self.bot = bot | ||
|
||
#making sure POLL_ALLOWED_CHANNELS_FILE exists | ||
if POLL_ALLOWED_CHANNELS_FILE not in os.listdir(): | ||
local_logger.warning("{} doesn't exist & not configured".format(POLL_ALLOWED_CHANNELS_FILE)) | ||
with open(POLL_ALLOWED_CHANNELS_FILE, "w") as file: | ||
pass | ||
|
||
#making poll_allowed channels according to the message's guild | ||
self.poll_allowed_chans = {} | ||
with open(POLL_ALLOWED_CHANNELS_FILE, "r") as file: | ||
for line in file.readlines(): | ||
guild_id = int(line.split(";")[0]) | ||
self.poll_allowed_chans[guild_id] = [int(chan_id) for chan_id in line.split(";")[1:]] | ||
|
||
|
||
|
||
@commands.Cog.listener() | ||
async def on_raw_reaction_add(self, payload): | ||
'''currently makes this checks for ALL channels. Might want to change the behavior to allow reactions on other msgs''' | ||
if not self.poll_allowed_chans[payload.guild_id]: | ||
local_logger.warning("Guild [{0.id}] doesn't have any channel for polls".format(payload.guild_id)) | ||
return | ||
|
||
#fetching concerned message and the user who added the reaction | ||
message = await self.bot.get_channel(payload.channel_id).fetch_message(payload.message_id) | ||
user = self.bot.get_user(payload.user_id) | ||
|
||
#checking that user isn't the bot | ||
if (payload.user_id != self.bot.user.id) and (payload.channel_id in self.poll_allowed_chans[payload.guild_id]): | ||
|
||
#checking if reaction is allowed | ||
if payload.emoji.name not in [EMOJIS["thumbsdown"],EMOJIS["thumbsup"],EMOJIS["shrug"]]: | ||
#deleting reaction of the user. Preserves other reactions | ||
try: | ||
#iterating over message's reactions to find out which was added | ||
for reaction in message.reactions: | ||
#testing if current emoji is the one just added | ||
if reaction.emoji == payload.emoji.name: | ||
#removing unauthorized emoji | ||
await reaction.remove(user) | ||
|
||
except Exception as e: | ||
local_logger.exception("Couldn't remove reaction {}".format("reaction")) | ||
raise e | ||
|
||
#if the reaction is allowed -> recalculating reactions ratio and changing embed's color accordingly | ||
else: | ||
#preventing users from having multiple reactions | ||
for reaction in message.reactions: | ||
if reaction.emoji != payload.emoji.name: | ||
await reaction.remove(user) | ||
|
||
#currently using integers -> may need to change to their values by checcking them one by one | ||
react_for = message.reactions[0].count | ||
react_against = message.reactions[2].count | ||
#changing color of the embed | ||
await self.balance_poll_color(message, react_for, react_against) | ||
|
||
|
||
|
||
@commands.Cog.listener() | ||
async def on_raw_reaction_remove(self, payload): | ||
if not self.poll_allowed_chans[payload.guild_id]: | ||
local_logger.warning("Guild [{0.id}] doesn't have any channel for polls".format(payload.guild_id)) | ||
return | ||
|
||
#fetching concerned message and the user who added the reaction | ||
message = await self.bot.get_channel(payload.channel_id).fetch_message(payload.message_id) | ||
|
||
#checking that user isn't the bot | ||
if (payload.user_id != self.bot.user.id) and (payload.channel_id in self.poll_allowed_chans[payload.guild_id]): | ||
|
||
react_for = message.reactions[0].count | ||
react_against = message.reactions[2].count | ||
#changing color of the embed | ||
await self.balance_poll_color(message, react_for, react_against) | ||
|
||
|
||
async def balance_poll_color(self, msg, rfor, ragainst): | ||
#determining their rgb values | ||
r_value = (ragainst/max(rfor, ragainst))*255 | ||
g_value = (rfor/max(rfor, ragainst))*255 | ||
#making the color | ||
color = int((r_value*65536) + (g_value*256)) | ||
#getting messages's embed (there should only be one) | ||
embed = msg.embeds[0].copy() | ||
embed.color = color | ||
await msg.edit(embed=embed) | ||
|
||
return msg | ||
|
||
|
||
@commands.Cog.listener() | ||
async def on_message(self, message): | ||
if message.author==self.bot.user: return | ||
|
||
if message.channel.id in self.poll_allowed_chans[message.guild.id] and message.content.startswith(PREFIX)!=True: | ||
embed_poll = discord.Embed( | ||
title = message.author.name, | ||
description = message.content, | ||
colour = discord.Color(16776960), | ||
url = None | ||
) | ||
embed_poll.set_thumbnail(url=message.author.avatar_url) | ||
#embed_poll.set_footer(text=message.author.name, icon_url=message.author.avatar_url) | ||
|
||
try: | ||
await message.delete() | ||
sent_msg = await message.channel.send(embed=embed_poll) | ||
await sent_msg.add_reaction(EMOJIS["thumbsup"]) | ||
await sent_msg.add_reaction(EMOJIS["shrug"]) | ||
await sent_msg.add_reaction(EMOJIS["thumbsdown"]) | ||
|
||
except Exception as e: | ||
local_logger.exception("Couldn't send and delete all reaction") | ||
|
||
|
||
@commands.group() | ||
async def poll(self, ctx): | ||
'''a suite of commands that lets one have more control over polls''' | ||
if ctx.invoked_subcommand == None: | ||
local_logger.warning("User didn't provide any subcommand") | ||
await ctx.send("NotEnoughArguments:\tYou must provide a subcommand") | ||
|
||
|
||
# @poll.command() | ||
# async def add(self, ctx, channel, *args): | ||
# pass | ||
|
||
|
||
@poll.command() | ||
async def rm(self, ctx, msg_id): | ||
'''allows one to delete one of their poll by issuing its id''' | ||
for chan in ctx.guild.text_channels: | ||
try: | ||
msg = await chan.fetch_message(msg_id) | ||
break | ||
|
||
#if the message isn't in this channel | ||
except discord.NotFound as e: | ||
local_logger.info("poll isn't in {0.name}[{0.id}]".format(chan)) | ||
|
||
except Exception as e: | ||
local_logger.exception("An unexpected error occured") | ||
raise e | ||
|
||
|
||
#making sure that the message is a poll. doesn't work, any msg with a embed could be considered a poll | ||
if len(msg.embeds)!=1: return | ||
#checking if the poll was created by the user. Is name safe enough ? | ||
if msg.embeds[0].title == ctx.author.name: | ||
try: | ||
await msg.delete() | ||
except Exception as e: | ||
local_logger.exception("Couldn't delete poll".format(msg)) | ||
raise e | ||
|
||
|
||
|
||
|
||
def setup(bot): | ||
bot.add_cog(Poll(bot)) |
Oops, something went wrong.