Skip to content

Commit

Permalink
Merge pull request #9 from organic-bots/rewrite
Browse files Browse the repository at this point in the history
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
s0lst1ce authored May 8, 2019
2 parents 303451d + cba6e0d commit 46fae00
Show file tree
Hide file tree
Showing 10 changed files with 940 additions and 275 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
forebot.log
85 changes: 85 additions & 0 deletions BotEssentials.py
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))
70 changes: 70 additions & 0 deletions Embedding.py
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))
195 changes: 195 additions & 0 deletions Poll.py
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))
Loading

0 comments on commit 46fae00

Please sign in to comment.