bot.rb 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. require 'bundler'
  2. require 'erb'
  3. require 'yaml'
  4. require 'json'
  5. require 'terminal-table'
  6. require 'rmagick'
  7. require 'down'
  8. BOT_ENV = ENV.fetch('BOT_ENV') { 'development' }
  9. Bundler.require(:default, BOT_ENV)
  10. require 'active_record'
  11. # Constants: such as roles and colors and regexes
  12. DISCORD = '#36393f'.freeze
  13. ERROR = '#a41e1f'.freeze
  14. UID = /<@\!?(\w+)>/.freeze
  15. URL = %r{https?://[\S]+}.freeze
  16. # ---
  17. Dotenv.load if BOT_ENV != 'production'
  18. db_yml = File.open('config/database.yml') do |erb|
  19. ERB.new(erb.read).result
  20. end
  21. db_config = YAML.safe_load(db_yml)[BOT_ENV]
  22. ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
  23. ActiveRecord::Base.establish_connection(
  24. adapter: 'postgresql',
  25. host: db_config.fetch('host') { 'localhost' },
  26. database: db_config['database'],
  27. user: db_config['user'],
  28. password: db_config['password'],
  29. pool: 5
  30. )
  31. Dir['app/**/*.rb'].each { |f| require File.join(File.expand_path(__dir__), f) }
  32. Dir['./lib/*.rb'].sort.each { |f| require f }
  33. token = ENV['DISCORD_BOT_TOKEN']
  34. bot = Discordrb::Bot.new(token: token)
  35. #--
  36. # This will trigger on every message sent in discord
  37. bot.message do |event|
  38. # Break if not in a server.. for some reason pms trigger here
  39. break if event.server.nil?
  40. # Save the message contents, and author
  41. content = event.message.content
  42. author = event.author
  43. # Attempt to match a command
  44. cmd = /^pkmn-(\w+)/.match(content)
  45. # Check for a standard command
  46. if cmd
  47. # Search for the corresponding command
  48. commands = BaseCommand.descendants.filter { |bc| bc.restricted_to.nil? }
  49. command = commands.detect { |c| c.name == cmd[1].downcase.to_sym }
  50. # Call the command, and reply with its results
  51. reply = command&.call(content, event)
  52. BotController.reply(bot, event, reply)
  53. # Check for a form that needs to be reacted to
  54. elsif author.id == ENV['WEBHOOK'].to_i
  55. # Save the app, and check app if character
  56. app = event.message.embeds.first
  57. if app.author.name == 'Character Application'
  58. case reply = CharacterController.authenticate_char_app(event)
  59. when Embed
  60. BotController.application_react(event)
  61. BotController.reply(bot, event, reply)
  62. when true
  63. BotController.application_react(event)
  64. when false
  65. BotController.unauthorized_char_app(bot, event)
  66. end
  67. else
  68. BotController.application_react(event)
  69. end
  70. # Check for a clear command
  71. elsif ENV['CLEAR_CH'].include?(event.channel.id.to_s) && content.match(/^clear\schat$/i)
  72. msgs = event.channel.history(50).reject { |m| m.author.webhook? }
  73. event.channel.delete_messages(msgs)
  74. # Check for no exp channels
  75. elsif ENV['NO_EXP_CH'].include?(event.channel.id.to_s)
  76. # Do nothing
  77. # Apply experience to non-bot users
  78. elsif !author.bot_account? && !author.webhook?
  79. # Replace any urls with 150 'x' chars
  80. message = URL.match(content) ? content.gsub(URL, 'x' * 100) : content
  81. # Add 40 to the message length if there's a file
  82. msg_length = if event.message.attachments.map(&:filename).positive?
  83. 50 + message.length
  84. else
  85. message.length
  86. end
  87. # Record post lengths and count
  88. user = User.find(author.id)
  89. if msg_length >= 40
  90. # Update User and reply with image, if there is one
  91. reply = user.update_xp(msg_length, author)
  92. bot.send_file(event.channel.id, File.open(reply, 'r')) if reply
  93. else
  94. user.update(
  95. short_post_length: user.short_post_length + msg_length,
  96. short_post_count: user.short_post_count + 1
  97. )
  98. end
  99. end
  100. end
  101. # This will trigger when a dm is sent to the bot from a user
  102. bot.pm do |event|
  103. # Save the message contents
  104. content = event.message.content
  105. # Attempt to match a command
  106. cmd = /^pkmn-(\w+)/.match(content)
  107. # Check for a standard command
  108. if cmd
  109. # Search for the corresponding command
  110. commands = BaseCommand.descendants.filter { |bc| bc.restricted_to == :pm }
  111. command = commands.detect { |c| c.name == cmd[1].downcase.to_sym }
  112. # Call the command, and reply with its results
  113. reply = command&.call(content, event)
  114. BotController.reply(bot, event, reply)
  115. end
  116. end
  117. # This will trigger when any reaction is added in discord
  118. bot.reaction_add do |event|
  119. app_form = event.message.embeds.first
  120. if Team.find_by(channel: event.channel.id)
  121. reactions = event.message.reactions
  122. event.message.pin if reactions[Emoji::PIN]&.count.to_i > 0
  123. end
  124. # Only do logic if this is an embed
  125. break unless app_form
  126. # Find the appropriate app type, and process
  127. app = ApplicationForm.descendants.detect { |af| af.name == app_form.author&.name }
  128. carousel = Carousel.find_by(message_id: event.message.id)
  129. reply = if app then app&.call(event)
  130. elsif carousel then carousel.navigate(event)
  131. end
  132. if reply
  133. BotController.reply(bot, event, reply)
  134. # elsif event.message&.reactions[Emoji::CROSS]&.count
  135. # crosses = event.message.reacted_with(Emoji::CROSS)
  136. # crosses.each do |cross|
  137. # member = event.server.member(cross.id)
  138. # event.message.delete unless member.current_bot?
  139. # end
  140. end
  141. end
  142. # This will trigger when any reaction is removed in discord
  143. bot.reaction_remove do |event|
  144. end
  145. # This will trigger when a member is updated
  146. bot.member_update do |event|
  147. end
  148. # This will trigger when anyone joins the server
  149. bot.member_join do |event|
  150. unless User.find_by(id: event.user.id)
  151. usr = User.create(id: event.user.id)
  152. usr.make_stats
  153. end
  154. end
  155. # This will trigger when anyone leaves the server
  156. bot.member_leave do |event|
  157. updated = []
  158. fields = []
  159. chars = Character.where(user_id: event.user.id)
  160. roles = event.roles
  161. roles = roles.map { |r| "<@#{r}>" } if roles
  162. chars.each do |char|
  163. unless char.active == 'NPC'
  164. char.update(active: 'Deleted')
  165. char.reload
  166. end
  167. ct = CharTeam.find_by(char_id: char.id)
  168. ct&.update(active: false)
  169. t = Team.find(ct.team_id) if ct
  170. bot.send_message(t.channel, "#{char.name} has left the server", false, nil) if t
  171. updated.push("#{char.name}, #{t.name} -- #{char.active}") if t
  172. updated.push("#{char.name}, no team data -- #{char.active}") if t.nil?
  173. end
  174. unless updated.empty?
  175. fields.push({
  176. name: '```Flagging Guild Members......```',
  177. value: updated.join("\n")
  178. })
  179. end
  180. unless roles.empty?
  181. fields.push({
  182. name: "User's Roles",
  183. value: roles.join(', ')
  184. })
  185. end
  186. embed = Embed.new(
  187. title: "I've lost track of a user!",
  188. description: "It seems #{event.member.mention}, (#{event.user.username}) has left the server!",
  189. fields: fields
  190. )
  191. # production channel
  192. bot.send_message(588_464_466_048_581_632, '', false, embed)
  193. # development channel
  194. # bot.send_message(594244240020865035, "", false, embed)
  195. end
  196. # This will trigger when anyone is banned from the server
  197. bot.user_ban do |event|
  198. updated = []
  199. fields = []
  200. chars = Character.where(user_id: event.user.id)
  201. roles = event.roles
  202. roles = roles.map { |r| "<@#{r}>" } if roles
  203. chars.each do |char|
  204. unless char.active == 'NPC'
  205. char.update(active: 'Deleted')
  206. char.reload
  207. end
  208. ct = CharTeam.find_by(char_id: char.id)
  209. ct&.update(active: false)
  210. t = Team.find(ct.team_id) if ct
  211. updated.push("#{char.name}, #{t.name} -- #{char.active}") if t
  212. updated.push("#{char.name}, no team data -- #{char.active}") if t.nil?
  213. end
  214. unless updated.empty?
  215. fields.push({
  216. name: '```Flagging Guild Members......```',
  217. value: updated.join("\n")
  218. })
  219. end
  220. unless roles.empty?
  221. fields.push({
  222. name: "User's Roles",
  223. value: roles.join(', ')
  224. })
  225. end
  226. embed = Embed.new(
  227. title: 'A User was forced to leave!',
  228. description: "It seems #{event.member.mention}, (#{event.user.username}) has been banned from the server!",
  229. fields: fields
  230. )
  231. # production channel
  232. bot.send_message(588_464_466_048_581_632, '', false, embed)
  233. end
  234. # This will trigger when anyone is un-banned from the server
  235. bot.user_unban do |event|
  236. end
  237. bot.run