A multipurpose discord bot.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
9.4KB

  1. # kirigiri - A discord bot.
  2. # Copyright (C) 2019 - Valentijn "noirscape" V.
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as published
  6. # by the Free Software Foundation at version 3 of the License.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU Affero General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Affero General Public License
  14. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  15. #
  16. # In addition, the additional clauses 7b and 7c are in effect for this program.
  17. #
  18. # b) Requiring preservation of specified reasonable legal notices or
  19. # author attributions in that material or in the Appropriate Legal
  20. # Notices displayed by works containing it; or
  21. #
  22. # c) Prohibiting misrepresentation of the origin of that material, or
  23. # requiring that modified versions of such material be marked in
  24. # reasonable ways as different from the original version
  25. # Starboard.
  26. # Idea:
  27. # - User adds reaction (star) to message
  28. # - x stars (user settable?) or higher mean a post in starboard channel.
  29. # - Bot keeps track of added stars and edits the starboard on each added and removed reaction.
  30. # - Different emojis the more stars something has? (star, glowing star, dizzy, sparkles)
  31. # - Need to have error handling in case message fails to be edited.
  32. import discord
  33. from discord.ext import commands
  34. from db import StarboardChannel, StarredMessage, VoteConfig
  35. import textwrap
  36. from utils.intermediaryreaction import IntermediaryReaction
  37. class Starboard(commands.Cog):
  38. stars = {
  39. 0: '\N{HOLE}',
  40. 1: '✴',
  41. 2: '⭐',
  42. 5: '🌟',
  43. 10: '💫',
  44. 15: '✨'
  45. }
  46. def __init__(self, bot):
  47. self.bot = bot
  48. async def get_star(self, idx):
  49. star = None
  50. while not star:
  51. if idx in self.stars:
  52. star = self.stars[idx]
  53. idx -= 1
  54. return star
  55. @commands.command()
  56. @commands.has_permissions(administrator=True)
  57. async def set_starboard_channel(self, ctx, channel: discord.TextChannel=None):
  58. """Set the starboard channel. Leave empty to unset."""
  59. if not channel:
  60. session = self.bot.db.dbsession()
  61. starboard_channel = session.query(StarboardChannel).filter_by(guild_id=ctx.guild.id).one()
  62. if starboard_channel:
  63. session.delete(starboard_channel)
  64. session.commit()
  65. session.close()
  66. return await ctx.send("Starboard channel unset.")
  67. else:
  68. session.close()
  69. return await ctx.send("No starboard channel set!")
  70. if channel not in ctx.guild.channels:
  71. return await ctx.send("This channel is not in this guild!")
  72. session = self.bot.db.dbsession()
  73. new_starboard_channel = StarboardChannel(channel_id=channel.id, guild_id=ctx.guild.id)
  74. try:
  75. vote_config = session.query(VoteConfig).filter_by(guild_id=ctx.guild.id).one()
  76. except:
  77. vote_config = None
  78. if not vote_config:
  79. new_votes = VoteConfig(guild_id=ctx.guild.id, minimum_votes=3)
  80. session.merge(new_votes)
  81. session.merge(new_starboard_channel)
  82. session.commit()
  83. session.close()
  84. return await ctx.send(f"Starboard channel has been set to {channel}.")
  85. @commands.command()
  86. @commands.has_permissions(administrator=True)
  87. async def set_star_count(self, ctx, vote_count: int):
  88. """Set minimum amount of stars needed to make the bot post a message."""
  89. if vote_count <= 0:
  90. return await ctx.send("Invalid vote count!")
  91. session = self.bot.db.dbsession()
  92. new_star_count = VoteConfig(guild_id=ctx.guild.id, minimum_votes=vote_count)
  93. session.merge(new_star_count)
  94. session.commit()
  95. session.close()
  96. await ctx.send(f"Minimum stars set to {vote_count}.")
  97. async def generate_starboard_message(self, star_count, original_message):
  98. """Generate a message to be put on the starboard.
  99. Returns a tuple:
  100. embed, content
  101. Embed is the embed to send.
  102. Content is a string to send along.
  103. """
  104. embed = discord.Embed()
  105. embed.description = f"[Jump to message!]({original_message.jump_url})\n\n"
  106. if original_message.content:
  107. embed.description += textwrap.shorten(original_message.content, 1800)
  108. if original_message.attachments:
  109. embed.set_image(url=original_message.attachments[0].url)
  110. embed.set_author(name=str(original_message.author), icon_url=original_message.author.avatar_url)
  111. embed.set_footer(text=str(original_message.created_at))
  112. star = await self.get_star(star_count)
  113. content = f"{star} {star_count} - Channel: {original_message.channel.mention} - ID: {original_message.id}"
  114. return embed, content
  115. @commands.Cog.listener()
  116. async def on_raw_reaction_add(self, raw_reaction):
  117. if str(raw_reaction.emoji) == '⭐':
  118. reaction = await IntermediaryReaction.create(raw_reaction, self.bot)
  119. session = self.bot.db.dbsession()
  120. try:
  121. channel_configured = session.query(StarboardChannel).filter_by(guild_id=reaction.message.guild.id).one()
  122. except:
  123. channel_configured = None
  124. try:
  125. minimum_votes = session.query(VoteConfig).filter_by(guild_id=reaction.message.guild.id).one()
  126. except:
  127. minimum_votes = None
  128. if channel_configured and minimum_votes:
  129. minimum_votes = minimum_votes.minimum_votes
  130. try:
  131. message_db_object = session.query(StarredMessage).filter_by(message_id=reaction.message.id).one()
  132. except:
  133. message_db_object = None
  134. if message_db_object:
  135. message_db_object.reaction_count += 1
  136. if message_db_object.reaction_count >= minimum_votes:
  137. embed, content = await self.generate_starboard_message(message_db_object.reaction_count, reaction.message)
  138. if message_db_object.starboard_message_id:
  139. await self.update_starboard_message(channel_configured.channel_id, embed, content, message_db_object.starboard_message_id)
  140. else:
  141. message_id = await self.update_starboard_message(channel_configured.channel_id, embed, content)
  142. message_db_object.starboard_message_id = message_id
  143. else:
  144. if 1 >= minimum_votes:
  145. embed, content = await self.generate_starboard_message(1, reaction.message)
  146. message_id = await self.update_starboard_message(channel_configured.channel_id, embed, content)
  147. message_db_object = StarredMessage(message_id=reaction.message.id, reaction_count=1, starboard_message_id=message_id)
  148. else:
  149. message_db_object = StarredMessage(message_id=reaction.message.id, reaction_count=1)
  150. session.merge(message_db_object)
  151. session.commit()
  152. session.close()
  153. async def update_starboard_message(self, channel_id, embed, content, message_id=None):
  154. """
  155. Send the starboard message. If message_id is None, create a new message.
  156. Returns the message ID.
  157. """
  158. if not message_id:
  159. message = await self.bot.get_channel(channel_id).send(embed=embed, content=content)
  160. else:
  161. message = await self.bot.get_channel(channel_id).fetch_message(message_id)
  162. if not message:
  163. raise ValueError(f"The message with ID {message_id} is not available.")
  164. await message.edit(content=content, embed=embed)
  165. return message.id
  166. @commands.Cog.listener()
  167. async def on_raw_reaction_remove(self, raw_reaction):
  168. if str(raw_reaction.emoji) == '⭐':
  169. reaction = await IntermediaryReaction.create(raw_reaction, self.bot)
  170. session = self.bot.db.dbsession()
  171. try:
  172. channel_configured = session.query(StarboardChannel).filter_by(guild_id=reaction.message.guild.id).one()
  173. except:
  174. channel_configured = None
  175. try:
  176. minimum_votes = session.query(VoteConfig).filter_by(guild_id=reaction.message.guild.id).one()
  177. minimum_votes = minimum_votes.minimum_votes
  178. except:
  179. minimum_votes = 0
  180. try:
  181. message_db_object = session.query(StarredMessage).filter_by(message_id=reaction.message.id).one()
  182. except:
  183. message_db_object = None
  184. if not message_db_object:
  185. session.commit()
  186. session.close()
  187. return
  188. message_db_object.reaction_count -= 1
  189. embed, content = await self.generate_starboard_message(message_db_object.reaction_count, reaction.message)
  190. await self.update_starboard_message(channel_configured.channel_id, embed, content, message_db_object.starboard_message_id)
  191. session.merge(message_db_object)
  192. session.commit()
  193. session.close()
  194. def setup(bot):
  195. bot.add_cog(Starboard(bot))