Advertisement
Guest User

Untitled

a guest
Feb 25th, 2020
102
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.37 KB | None | 0 0
  1. from discord import Embed
  2. from discord.ext import commands
  3. import logging
  4. import pathlib
  5. import re
  6. from __main__ import path, dbPool
  7. import emoji
  8.  
  9. ##### Exceptions #####
  10. class PollsBaseException(commands.CommandError):
  11. """Base exception for all poll exceptions"""
  12. def __init__(self, guildId: int, channelId: int):
  13. self.guildId = guildId
  14. self.channelId = channelId
  15.  
  16. class ChannelNotInGuildException(PollsBaseException):
  17. """Exception that occurs when the channel specified by the caller does not match with the context guild"""
  18. def __init__(self, guildId: int, channelId: int, matchedChannelId: int):
  19. super().__init__(guildId, channelId)
  20. self.matchedChannelId = matchedChannelId
  21.  
  22. class NoOptionsException(PollsBaseException):
  23. """Exception that occurs when the caller does not specify any options"""
  24. def __init__(self, guildId: int, channelId: int):
  25. super().__init__(guildId, channelId)
  26.  
  27. ##### Classes #####
  28. class PollOption:
  29. """Dataclass defining a single pollOption"""
  30. def __init__(self, option: str, reaction: str, responses: int, responders: list):
  31. self.option = option
  32. self.reaction = reaction
  33. self.responses = responses
  34. self.responders = responders
  35.  
  36. class Poll:
  37. """Class defining the Poll object"""
  38. def __init__(self):
  39. self.guildId: int = None
  40. self.channelId: int = None
  41. self.messageId: int = None
  42. self.messageTitle: str = None
  43. self.verbose: bool = False
  44. self.options: list = []
  45.  
  46. @classmethod
  47. async def from_db(cls, bot: commands.Bot, pollId: int):
  48. """Creates a Poll object from the db using the poll id"""
  49. # Creates Poll object using classmethod placeholder
  50. poll = cls()
  51. async with dbPool.acquire() as conn:
  52. # Query the polls database
  53. pollQueryResults = await conn.fetch_row(f'SELECT * FROM polls WHERE id = \'{pollId}\';') # Returns Record object of row
  54.  
  55. # Initializes poll using the values from the database & option object
  56.  
  57. pollMessage = await bot.get_message(pollQueryResults['message_id'])
  58. poll.guildId = pollMessage.guild.id
  59. poll.channelId = pollMessage.channel
  60. poll.messageId = pollQueryResults['message_id']
  61. poll.messageTitle = pollQueryResults['title']
  62. poll.verbose = bool(pollQueryResults['verbose'])
  63. poll.options = []
  64.  
  65. # Query the poll_options database
  66. pollOptionsQueryResults = await conn.fetch(f'SELECT * FROM poll_options WHERE fk = \'{pollId}\'') # Returns list of Records
  67. for pollOptionsRecord in pollOptionsQueryResults:
  68. optionObject = PollOption(pollOptionsRecord['option'], pollOptionsRecord['reaction'], pollOptionsRecord['responses'], [])
  69.  
  70. # Query the poll_responders database for each option
  71. pollRespondersQueryResults = await conn.fetch(f'SELECT * FROM poll_responders WHERE fk = \'{pollOptionsRecord["id"]}\';') # Returns list of Records
  72. # Add the responder id's to the options object
  73. for pollRespondersRecord in pollRespondersQueryResults:
  74. optionObject.responders.append(pollRespondersRecord['responder_id'])
  75. # Add the options object to the poll object
  76. poll.options.append(optionObject)
  77. return poll
  78.  
  79. async def update_db(self):
  80. """Updates the database using the poll's data"""
  81. async with dbPool.acquire() as conn:
  82. async with conn.transaction():
  83. dbGuildId = await conn.fetchval(f'SELECT id FROM guilds WHERE guild_id = \'{self.guildId}\';')
  84. await conn.execute(f'UPDATE polls SET message_id = \'{self.messageId}\', title = \'{self.messageTitle}\', verbose = \'{int(self.verbose)}\' WHERE fk = \'{dbGuildId}\';')
  85. for option in self.options:
  86. await conn.execute(f'UPDATE poll_options SET option = \'{option.option}\', reaction = \'{option.reaction}\', responses = \'{option.responses}\';')
  87. dbOptionId = await conn.fetchval(f'SELECT id FROM poll_options WHERE fk = SELECT id FROM polls WHERE fk = \'{dbGuildId}\';')
  88. responderRecords = await conn.fetch(f'SELECT responder_id FROM poll_responders WHERE fk = \'{dbOptionId}\';')
  89. responderIdList = []
  90. for responderRecord in responderRecords:
  91. responderIdList.append(responderRecord['responder_id'])
  92. for responderId in option.responders:
  93. if not responderId in responderIdList:
  94. await conn.execute(f'INSERT INTO poll_responders(fk, responder_id) VALUES (\'{dbOptionId}\',\'{responderId}\');')
  95.  
  96. async def to_db(self):
  97. """Inserts a new poll into the database"""
  98. async with dbPool.acquire() as conn:
  99. async with conn.transaction():
  100. dbGuildId = await conn.fetchval(f'SELECT id FROM guilds WHERE guild_id = \'{self.guildId}\';')
  101. await conn.execute(f'INSERT INTO polls(fk, message_id, title, verbose) VALUES(\'{dbGuildId}\', \'{self.messageId}\', \'{self.messageTitle}\', \'{int(self.verbose)}\');')
  102. dbPollId = await conn.fetchval(f'SELECT id FROM polls WHERE message_id = \'{self.messageId}\'')
  103. for option in self.options:
  104. await conn.execute(f'INSERT INTO poll_options(fk, option, reaction, responses) VALUES(\'{dbPollId}\', \'{option.option}\', \'{option.reaction}\', \'{option.responses}\');')
  105. dbOptionId = await conn.fetchval(f'SELECT id FROM poll_options WHERE fk = \'{dbPollId}\' AND option = \'{option.option}\';')
  106. for responder in option.responders:
  107. await conn.execute(f'INSERT INTO pol_responders(fk, responder_id) VALUES(\'{dbOptionId}\', \'{responder}\');')
  108.  
  109. @classmethod
  110. def from_input(cls, bot: commands.Bot, ctx: commands.Context, input: str): ##################################### <- gets called by pollCommand
  111. """Creates a Poll object from user input and invocation context"""
  112. # Creates object from classmethod placeholder
  113. poll = cls()
  114. print(input) # debugging
  115. # Verbosity
  116. verboseMatch = re.search(r'verbose=(true)', input)
  117. if verboseMatch != None:
  118. if verboseMatch.group(1) == 'true':
  119. poll.verbose = True
  120.  
  121. # Guild id
  122. poll.guildId = ctx.guild.id
  123.  
  124. # Channel id
  125. poll.channelId = ctx.channel.id
  126. chIdMatch = re.search(r'channelid=([0123456789]*)', input)
  127. if chIdMatch != None:
  128. matchedId = int(chIdMatch.group(1))
  129. matchedGuild = bot.get_channel(matchedId).guild
  130. if ctx.guild == matchedGuild:
  131. poll.guildId = ctx.guild.id
  132. poll.channelId = matchedId
  133. else:
  134. raise ChannelNotInGuildException(ctx.guild.id, ctx.channel.id, matchedId)
  135.  
  136. # Reactions & options
  137. optMatch = re.search(r'options=\[(.*)\]', input)
  138. if optMatch != None:
  139. if optMatch.group(1) != '':
  140. optMatchList = re.findall(r'([^;]*?)\|([^;]*)', optMatch.group(1))
  141. for option, reaction in optMatchList:
  142. print(emoji.demojize(reaction))
  143. poll.options.append(PollOption(option, emoji.demojize(reaction), 0, []))
  144. else:
  145. raise NoOptionsException(ctx.guild.id, ctx.channel.id)
  146. else:
  147. raise NoOptionsException(ctx.guild.id, ctx.channel.id)
  148.  
  149. # Message title
  150. poll.messageTitle = 'No title specified'
  151. titleMatch = re.search(r'title=\'(.*)\'', input)
  152. if titleMatch != None:
  153. if titleMatch.group(1) != '':
  154. poll.messageTitle = titleMatch.group(1)
  155.  
  156. return poll
  157.  
  158. class PollsCog(commands.Cog):
  159. """The Cog object that gets loaded by the bot"""
  160. def __init__(self, bot):
  161. self.bot = bot
  162.  
  163. async def constructEmbed(self, bot: commands.Bot, poll: Poll):
  164. embed = Embed(title=poll.messageTitle)
  165. for option in poll.options:
  166. responseStr = '-----'
  167. if option.responses != 0:
  168. responseStr = ''
  169. if poll.verbose:
  170. for responderId in option.responders:
  171. responseStr += f'{await bot.get_user(responderId).display_name}\n'
  172. else:
  173. responseStr = str(poll.option.responses)
  174. embed.add_field(name=option.option, value=responseStr, inline=False)
  175. return embed
  176.  
  177. @commands.command(name='poll')
  178. async def pollCommand(self, ctx: commands.Context, *, args: str):
  179. print(dir(ctx), args)
  180. poll = Poll.from_input(self.bot, ctx, args) ##################################### <- here
  181. pollEmbed = await self.constructEmbed(self.bot, poll)
  182. msgChannel = self.bot.get_channel(poll.channelId)
  183. message = await msgChannel.send(embed=pollEmbed)
  184. poll.messageId = message.id
  185. # Add corresponding reactions to the message
  186. for option in poll.options:
  187. await message.add_reaction(option.reaction)
  188. # Commit the created poll to the database
  189. await poll.to_db()
  190.  
  191. @commands.Cog.listener()
  192. async def on_raw_reaction_add(self, reaction):
  193. """Called whenever a reaction is added to a message"""
  194. # Match the message being reacted to to the database
  195. async with dbPool.acquire() as conn:
  196. dbPollId = await conn.fetchval(f'SELECT id FROM polls WHERE message_id = \'{reaction.message.id}\';')
  197. if not dbPollId is None:
  198. # Create poll from message id
  199. poll = await Poll.from_db(self.bot, dbPollId)
  200. for option in poll.options:
  201. if option.reaction == reaction.emoji:
  202. option.responses += 1
  203. option.responders.append(reaction.user_id)
  204. await poll.update_db()
  205.  
  206. @commands.Cog.listener()
  207. async def on_raw_reaction_remove(self, reaction, user):
  208. """Called whenever a reaction is removed from a message"""
  209. # Match the message the reaction was removed from to the database
  210. async with dbPool.acquire() as conn:
  211. dbPollId = await conn.fetchval(f'SELECT id FROM polls WHERE message_id = \'{reaction.message.id}\';')
  212. if not dbPollId is None:
  213. # Create poll from message id
  214. poll = await Poll.from_db(self.bot, dbPollId)
  215. for option in poll.options:
  216. if option.reaction == reaction.emoji:
  217. option.responses -= 1
  218. option.responders.remove(reaction.user_id)
  219. await poll.update_db()
  220.  
  221. @pollCommand.error
  222. async def pollCommandErrorhandler (self, ctx, error):
  223. if isinstance(error, commands.MissingRequiredArgument):
  224. logging.info(f'{error.__class__.__name__}: {error}')
  225. await ctx.send(f'```ERROR: At least one argument needs to be specified for this command```')
  226. else:
  227. logging.error(error)
  228. raise error
  229.  
  230.  
  231. def setup(bot):
  232. bot.add_cog(PollsCog(bot))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement