Procházet zdrojové kódy

Merge branch 'member' of PMD/rotom_bot into master

kjswis před 6 roky
rodič
revize
b617741c94

+ 1 - 0
README.md

@@ -31,6 +31,7 @@ $ pg_dump -O -f db/schma.sql [db_name]  # Exports schema
   * Displays help information for the available commands
   * Allows modular polls in any channel
   * Allows users to add, view, and delete images for their characters
+  * Display all kinds of information about guild members
 
 ## Setup
 This application runs using Ruby and Postgres. In order to run the bot locally

+ 17 - 1
app/controllers/image_controller.rb

@@ -2,7 +2,7 @@ class ImageController
   def self.default_image(content, char_id)
     img_url =
       /\*\*URL to the Character\'s Appearance\*\*\:\s(.*)/.match(content)
-    img = CharImage.where(char_id: char_id),find_by(keyword: 'Default')
+    img = CharImage.where(char_id: char_id).find_by(keyword: 'Default')
 
     case
     when img_url && img
@@ -36,4 +36,20 @@ class ImageController
 
     img
   end
+
+  def self.img_scroll(char_id: , nsfw: false, img: nil, dir: nil)
+    imgs = nsfw ? CharImage.where(char_id: char_id) :
+      CharImage.where(char_id: char_id, category: 'SFW' )
+
+    cur_i = img ? imgs.index { |i| i[:id] == img } : imgs.length - 1
+
+    case dir
+    when :left
+      nex_i = cur_i == 0 ? imgs.length - 1 : cur_i - 1
+    else
+      nex_i = cur_i == imgs.length - 1 ? 0 : cur_i + 1
+    end
+
+    imgs[nex_i]
+  end
 end

+ 3 - 0
app/models/carousels.rb

@@ -0,0 +1,3 @@
+class Carousel < ActiveRecord::Base
+  validates :message_id, presence: true
+end

+ 28 - 0
app/models/char_carousel.rb

@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+require_relative '../../lib/emoji.rb'
+
+module CharCarousel
+  BIO = "Bio"
+  STATUS = "Status"
+  TYPE = "Type"
+  RUMORS = "Rumors"
+  IMAGE = "Images"
+  BAGS = "Inventory"
+  FAMILY = "Family Tree"
+  ALL = "All"
+  DEFAULT = "This Key"
+  CROSS = "Delete Message"
+
+  REACTIONS = {
+    Emoji::NOTEBOOK => BIO,
+    Emoji::QUESTION => STATUS,
+    Emoji::PALLET => TYPE,
+    Emoji::EAR => RUMORS,
+    Emoji::PICTURE => IMAGE,
+    Emoji::BAGS => BAGS,
+    Emoji::FAMILY => FAMILY,
+    Emoji::EYES => ALL,
+    Emoji::KEY => DEFAULT,
+    Emoji::CROSS => CROSS
+  }
+end

+ 1 - 1
app/models/regex.rb

@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 module Regex
-  UID = /<@([0-9]+)>/
+  UID = /<@\!?([0-9]+)>/
   EDIT_URL = /Edit\sKey\s\(ignore\):\s([\s\S]*)/
   CHAR_APP = /\_New\sCharacter\sApplication\_:\s(.*)/
 end

+ 21 - 0
app/responses/carousel.rb

@@ -0,0 +1,21 @@
+require_relative '../../lib/emoji.rb'
+
+def section_react(message)
+  Emoji::CAROUSEL.each do |emote|
+    message.react(emote)
+  end
+end
+
+def arrow_react(message)
+  message.react(Emoji::UNDO)
+  message.react(Emoji::LEFT)
+  message.react(Emoji::RIGHT)
+  message.react(Emoji::CROSS)
+end
+
+def option_react(message, opts)
+  opts.each.with_index do |_, i|
+    message.react(Emoji::NUMBERS[i])
+  end
+  message.react(Emoji::CROSS)
+end

+ 214 - 49
app/responses/character.rb

@@ -1,79 +1,244 @@
-def character_embed(character, image, user, color)
+def character_embed(char:, img: nil, user:, color:, section: :all)
   fields = []
-  footer_text = "#{user.name}##{user.tag} | #{character.active}"
-  footer_text += " | #{character.rating}" if character.rating
+  footer_text = "#{user.name}##{user.tag} | #{char.active}"
+  footer_text += " | #{char.rating}" if char.rating
+  footer_text += " | #{img.category} " if section == :image
 
+  navigate = "React to Navigate"
+  footer_text += " | #{navigate}"
+
+  embed = Embed.new(
+    footer: {
+      icon_url: user.avatar_url,
+      text: footer_text
+    },
+    title: char.name,
+    color: color,
+  )
+
+  case section
+  when :all
+    embed.description = char.personality if char.personality
+    fields = char_type(char, fields)
+    fields = char_status(char, fields)
+    fields = char_bio(char, fields)
+    fields = char_rumors(char, fields)
+  when :default
+    embed.description = navigate
+    fields = char_sections(fields)
+  when :bio
+    embed.description = char.personality if char.personality
+    fields = char_bio(char, fields)
+  when :type
+    fields = char_type(char, fields)
+  when :status
+    fields = char_status(char, fields)
+  when :rumors
+    fields = char_rumors(char, fields)
+  when :image
+    if img
+      embed.title =
+        "#{char.name} | #{img.keyword}" unless img.keyword == 'Default'
+      embed.image = { url: img.url }
+    else
+      embed.description = "No character images found!"
+    end
+  end
+
+
+  embed.thumbnail = { url: img.url } if img && section != :image
+  embed.fields = fields
+
+  embed
+end
+
+def char_bio(char, fields)
   fields.push(
-    { name: 'Species', value: character.species, inline: true }
-  )if character.species
-  fields.push(
-    { name: 'Type', value: character.types, inline: true }
-  )if character.types
-  fields.push(
-    { name: 'Age', value: character.age, inline: true }
-  )if character.age
+    { name: 'Hometown', value: char.hometown, inline: true }
+  )if char.hometown
   fields.push(
-    { name: 'Weight', value: character.weight, inline: true }
-  )if character.weight
+    { name: 'Location', value: char.location, inline: true }
+  )if char.location
   fields.push(
-    { name: 'Height', value: character.height, inline: true }
-  )if character.height
+    { name: 'Likes', value: char.likes }
+  )if char.likes
   fields.push(
-    { name: 'Gender', value: character.gender, inline: true }
-  )if character.gender
+    { name: 'Dislikes', value: char.dislikes }
+  )if char.dislikes
   fields.push(
-    { name: 'Sexual Orientation', value: character.orientation, inline: true }
-  )if character.orientation
+    { name: 'Backstory', value: char.backstory }
+  )if char.backstory
   fields.push(
-    { name: 'Relationship Status', value: character.relationship, inline: true }
-  )if character.relationship
+    { name: 'Other', value: char.other }
+  )if char.other
   fields.push(
-    { name: 'Hometown', value: character.hometown, inline: true }
-  )if character.hometown
+    { name: 'DM Notes', value: char.dm_notes }
+  )if char.dm_notes
+
+  fields
+end
+
+def char_type(char, fields)
   fields.push(
-    { name: 'Location', value: character.location, inline: true }
-  )if character.location
+    { name: 'Species', value: char.species, inline: true }
+  )if char.species
   fields.push(
-    { name: 'Attacks', value: character.attacks }
-  )if character.attacks
+    { name: 'Type', value: char.types, inline: true }
+  )if char.types
+
+  if char.attacks
+    attacks = char.attacks
+    attacks = attacks.gsub(/\s?\|\s?/, "\n")
+
+    fields.push({ name: 'Attacks', value: attacks })
+  end
+
+  fields
+end
+
+def char_rumors(char, fields)
   fields.push(
-    { name: 'Likes', value: character.likes }
-  )if character.likes
+    { name: 'Warnings', value: char.warnings }
+  )if char.warnings
+
+  if char.rumors
+    rumors = char.rumors.split(/\s?\|\s?/)
+    rumors = rumors.shuffle
+    rumors = rumors.join("\n")
+
+    fields.push({ name: 'Rumors', value: rumors })
+  end
+
+  fields
+end
+
+def char_status(char, fields)
   fields.push(
-    { name: 'Dislikes', value: character.dislikes }
-  )if character.dislikes
+    { name: 'Age', value: char.age, inline: true }
+  )if char.age
   fields.push(
-    { name: 'Warnings', value: character.warnings }
-  )if character.warnings
+    { name: 'Gender', value: char.gender, inline: true }
+  )if char.gender
   fields.push(
-    { name: 'Rumors', value: character.rumors }
-  )if character.rumors
+    { name: 'Weight', value: char.weight, inline: true }
+  )if char.weight
   fields.push(
-    { name: 'Backstory', value: character.backstory }
-  )if character.backstory
+    { name: 'Height', value: char.height, inline: true }
+  )if char.height
   fields.push(
-    { name: 'Other', value: character.other }
-  )if character.other
+    { name: 'Sexual Orientation', value: char.orientation, inline: true }
+  )if char.orientation
   fields.push(
-    { name: 'DM Notes', value: character.dm_notes }
-  )if character.dm_notes
+    { name: 'Relationship Status', value: char.relationship, inline: true }
+  )if char.relationship
+
+  fields
+end
+
+def char_sections(fields)
+  CharCarousel::REACTIONS.map do |emoji, message|
+    fields.push({
+      name: emoji,
+      value: message,
+      inline: true
+    })
+  end
+
+  fields
+end
+
+def char_list_embed(chars, user = nil)
+  fields = []
+  active = []
+  npcs = []
+
+  chars.each do |char|
+    case char.active
+    when 'Active'
+      active.push char.name
+    when 'NPC'
+      npcs.push char.name
+    end
+  end
+
+  fields.push({
+    name: 'Active Characters',
+    value: active.join(", ")
+  })if active.length > 0
+
+  fields.push({
+    name: 'NPCs',
+    value: npcs.join(", ")
+  })if npcs.length > 0
 
   embed = Embed.new(
-    footer: {
-      icon_url: user.avatar_url,
-      text: footer_text
-    },
-    title: character.name,
-    color: color,
+    title: 'Registered Characters',
     fields: fields
   )
 
-  embed.description = character.personality if character.personality
-  embed.thumbnail = { url: image } if image
+  if user
+    user_name = user.nickname || user.name
+
+    embed.color = user.color.combined
+    embed.title = "#{user_name}'s Characters"
+  end
+
+  embed
+end
+
+def user_char_embed(chars, user)
+  fields = []
+  active = []
+  npcs = []
+  user_name = user.nickname || user.name
+
+  chars.each do |char|
+    case char.active
+    when 'Active'
+      active.push char
+    when 'NPC'
+      npcs.push char.name
+    end
+  end
+
+  active.each.with_index do |char, i|
+    fields.push({
+      name: "#{i+1} #{char.name}",
+      value: "#{char.species} -- #{char.types}"
+    })
+  end
+
+  unless npcs.empty?
+    fields.push({ name: "#{user_name}'s NPCs", value: npcs.join(", ") })
+  end
+
+  embed = Embed.new(
+    title: "#{user_name}'s Characters",
+    description: "Click on the corresponding reaction to view the character",
+    fields: fields
+  )
 
+  embed.color = user.color.combined if user.color
   embed
 end
 
+def dup_char_embed(chars, name)
+  fields = []
+
+  chars.each.with_index do |char, i|
+    fields.push({
+      name: "#{Emoji::NUMBERS[i]}: #{char.species}",
+      value: "Created by <@#{char.user_id}>"
+    })
+  end
+
+  Embed.new(
+    title: "Which #{name}?",
+    description: "Click on the corresponding reaction to pick",
+    fields: fields
+  )
+end
+
 def char_image_embed(char, image, user, color)
   footer = "#{user.name}##{user.tag} | #{char.active}" +
     " | #{image.category}"

+ 341 - 4
bot.rb

@@ -250,6 +250,137 @@ rescue ActiveRecord::RecordNotFound
   )
 end
 
+opts = {
+  "" => "List all guild members",
+  "@user" => "List all characters belonging to the user",
+  "name " => "Display the given character",
+  "name | section" => "Display the given section for the character",
+  "name | image | keword" => "Display the given image"
+}
+desc = "Display info about the guild members"
+member = Command.new(:member, desc, opts) do |event, name, section, keyword|
+  sections = [:all, :default, :bio, :type, :status, :rumors, :image]
+
+  case name
+  when Regex::UID
+    user_id = Regex::UID.match(name)
+  when String
+    chars = Character.where(name: name)
+    char = chars.first if chars.length == 1
+
+    if char
+      img = CharImage.where(char_id: char.id).find_by(keyword: 'Default')
+      user = event.server.member(char.user_id)
+      color = CharacterController.type_color(char)
+    end
+  end
+
+  case
+  when !name
+    chars = Character.all
+    char_list_embed(chars)
+  when name && user_id
+    chars = Character.where(user_id: user_id[1])
+    user = event.server.member(user_id[1])
+    chars_id = []
+
+    chars.each do |char|
+      chars_id.push char.id if char.active == 'Active'
+    end
+
+    embed = user_char_embed(chars, user)
+    msg = event.send_embed("", embed)
+
+    Carousel.create(message_id: msg.id, options: chars_id)
+    option_react(msg, chars_id)
+  when name && chars && !char
+    embed = dup_char_embed(chars, name)
+    chars_id = chars.map(&:id)
+
+    msg = event.send_embed("", embed)
+    Carousel.create(message_id: msg.id, options: chars_id)
+
+    option_react(msg, chars_id)
+  when name && char && !section
+    embed = character_embed(
+      char: char,
+      img: img,
+      section: :default,
+      user: user,
+      color: color
+    )
+
+    msg = event.send_embed("", embed)
+    Carousel.create(message_id: msg.id, char_id: char.id)
+
+    section_react(msg)
+  when char && section && keyword
+    embed = command_error_embed(
+      "Invalid Arguments",
+      member
+    )unless /image/i.match(section)
+
+    unless embed
+      img = CharImage.where(char_id: char.id).find_by!(keyword: keyword)
+
+      embed = error_embed(
+        "Wrong Channel!",
+        "The requested image is NSFW"
+      )if img.category == 'NSFW' && !event.channel.nsfw?
+    end
+
+    unless embed
+      embed = character_embed(
+        char: char,
+        img: img,
+        section: :image,
+        user: user,
+        color: color
+      )
+
+      msg = event.send_embed("", embed)
+      Carousel.create(message_id: msg.id, char_id: char.id, image_id: img.id)
+
+      arrow_react(msg)
+    end
+
+    embed
+  when name && char && section
+    sect = section.downcase.to_sym
+    nsfw = event.channel.nsfw?
+
+    img = ImageController.img_scroll(
+      char_id: char.id,
+      nsfw: nsfw
+    )if section == :image
+
+    if sections.detect{ |s| s == sect }
+      embed = character_embed(
+        char: char,
+        img: img,
+        section: sect,
+        user: user,
+        color: color,
+      )
+
+      msg = event.send_embed("", embed)
+      Carousel.create(message_id: msg.id, char_id: char.id, image_id: img.id)
+
+      if sect == :image
+        arrow_react(msg)
+      else
+        section_react(msg)
+      end
+    else
+      error_embed("Invalid Section!")
+    end
+  end
+
+
+rescue ActiveRecord::RecordNotFound => e
+  error_embed("Record Not Found!", e.message)
+end
+
 # ---
 
 commands = [
@@ -258,7 +389,8 @@ commands = [
   app,
   help,
   poll,
-  raffle
+  raffle,
+  member
 ]
 
 # This will trigger on every message sent in discord
@@ -325,6 +457,8 @@ bot.reaction_add do |event|
       :pm
     when event.message.from_bot? && content.match(/\_New\sCharacter\sImage\_:/)
       :image_application
+    when carousel = Carousel.find_by(message_id: event.message.id)
+      :carousel
     end
 
   vote =
@@ -334,9 +468,20 @@ bot.reaction_add do |event|
     when reactions[Emoji::CHECK]&.count.to_i > 1 then :check
     when reactions[Emoji::CROSS]&.count.to_i > 1 then :cross
     when reactions[Emoji::CRAYON]&.count.to_i > 1 then :crayon
+    when reactions[Emoji::NOTEBOOK]&.count.to_i > 1 then :notebook
+    when reactions[Emoji::QUESTION]&.count.to_i > 1 then :question
+    when reactions[Emoji::PALLET]&.count.to_i > 1 then :pallet
+    when reactions[Emoji::EAR]&.count.to_i > 1 then :ear
+    when reactions[Emoji::PICTURE]&.count.to_i > 1 then :picture
+    when reactions[Emoji::BAGS]&.count.to_i > 1 then :bags
+    when reactions[Emoji::FAMILY]&.count.to_i > 1 then :family
+    when reactions[Emoji::EYES]&.count.to_i > 1 then :eyes
+    when reactions[Emoji::KEY]&.count.to_i > 1 then :key
     when reactions[Emoji::PHONE]&.count.to_i > 1 then :phone
     when reactions[Emoji::LEFT]&.count.to_i > 1 then :left
     when reactions[Emoji::RIGHT]&.count.to_i > 1 then :right
+    when reactions[Emoji::UNDO]&.count.to_i > 1 then :back
+    when reactions.any? { |k,v| Emoji::NUMBERS.include? k } then :number
     end
 
   case [form, vote]
@@ -346,11 +491,15 @@ bot.reaction_add do |event|
     user = event.server.member(uid[1])
 
     char = CharacterController.edit_character(params)
-    image_url = ImageController.default_image(content, char.id)
+    img = ImageController.default_image(content, char.id)
     color = CharacterController.type_color(char)
 
-
-    embed = character_embed(char, image_url, user, color) if char
+    embed = character_embed(
+      char: char,
+      img: img,
+      user: user,
+      color: color
+    )if char
 
     if embed
       bot.send_message(
@@ -447,6 +596,194 @@ bot.reaction_add do |event|
   when [:image_application, :cross]
     event.message.delete
 
+  when [:carousel, :notebook]
+    emoji = Emoji::NOTEBOOK
+    users = event.message.reacted_with(emoji)
+
+    users.each do |user|
+      event.message.delete_reaction(user.id, emoji) unless user.current_bot?
+    end
+
+    char = Character.find(carousel.char_id)
+    embed = character_embed(
+      char: char,
+      img: CharImage.where(char_id: char.id).find_by(keyword: 'Default'),
+      user: event.server.member(char.user_id),
+      color: CharacterController.type_color(char),
+      section: :bio
+    )
+    event.message.edit("", embed)
+  when [:carousel, :question]
+    emoji = Emoji::QUESTION
+    users = event.message.reacted_with(emoji)
+
+    users.each do |user|
+      event.message.delete_reaction(user.id, emoji) unless user.current_bot?
+    end
+
+    char = Character.find(carousel.char_id)
+    embed = character_embed(
+      char: char,
+      img: CharImage.where(char_id: char.id).find_by(keyword: 'Default'),
+      user: event.server.member(char.user_id),
+      color: CharacterController.type_color(char),
+      section: :status
+    )
+    event.message.edit("", embed)
+  when [:carousel, :pallet]
+    emoji = Emoji::PALLET
+    users = event.message.reacted_with(emoji)
+
+    users.each do |user|
+      event.message.delete_reaction(user.id, emoji) unless user.current_bot?
+    end
+
+    char = Character.find(carousel.char_id)
+    embed = character_embed(
+      char: char,
+      img: CharImage.where(char_id: char.id).find_by(keyword: 'Default'),
+      user: event.server.member(char.user_id),
+      color: CharacterController.type_color(char),
+      section: :type
+    )
+    event.message.edit("", embed)
+  when [:carousel, :ear]
+    emoji = Emoji::EAR
+    users = event.message.reacted_with(emoji)
+
+    users.each do |user|
+      event.message.delete_reaction(user.id, emoji) unless user.current_bot?
+    end
+
+    char = Character.find(carousel.char_id)
+    embed = character_embed(
+      char: char,
+      img: CharImage.where(char_id: char.id).find_by(keyword: 'Default'),
+      user: event.server.member(char.user_id),
+      color: CharacterController.type_color(char),
+      section: :rumors
+    )
+    event.message.edit("", embed)
+  when [:carousel, :picture]
+    event.message.delete_all_reactions
+
+    char = Character.find(carousel.char_id)
+    img = ImageController.img_scroll(
+      char_id: char.id,
+      nsfw: event.channel.nsfw?,
+    )
+    carousel.update(id: carousel.id, image_id: img.id)
+
+    embed = character_embed(
+      char: char,
+      img: img,
+      user: event.server.member(char.user_id),
+      color: CharacterController.type_color(char),
+      section: :image
+    )
+    event.message.edit("", embed)
+    arrow_react(event.message)
+  when [:carousel, :bags]
+  when [:carousel, :family]
+  when [:carousel, :eyes]
+    emoji = Emoji::EYES
+    users = event.message.reacted_with(emoji)
+
+    users.each do |user|
+      event.message.delete_reaction(user.id, emoji) unless user.current_bot?
+    end
+
+    char = Character.find(carousel.char_id)
+    embed = character_embed(
+      char: char,
+      img: CharImage.where(char_id: char.id).find_by(keyword: 'Default'),
+      user: event.server.member(char.user_id),
+      color: CharacterController.type_color(char),
+      section: :all
+    )
+    event.message.edit("", embed)
+  when [:carousel, :key]
+    emoji = Emoji::KEY
+    users = event.message.reacted_with(emoji)
+
+    users.each do |user|
+      event.message.delete_reaction(user.id, emoji) unless user.current_bot?
+    end
+
+    char = Character.find(carousel.char_id)
+    embed = character_embed(
+      char: char,
+      img: CharImage.where(char_id: char.id).find_by(keyword: 'Default'),
+      user: event.server.member(char.user_id),
+      color: CharacterController.type_color(char),
+      section: :default
+    )
+    event.message.edit("", embed)
+  when [:carousel, :back]
+    event.message.delete_all_reactions
+
+    char = Character.find(carousel.char_id)
+    embed = character_embed(
+      char: char,
+      img: CharImage.where(char_id: char.id).find_by(keyword: 'Default'),
+      user: event.server.member(char.user_id),
+      color: CharacterController.type_color(char),
+      section: :default
+    )
+    event.message.edit("", embed)
+    section_react(event.message)
+  when [:carousel, :left], [:carousel, :right]
+    emoji = vote == :left ? Emoji::LEFT : Emoji::RIGHT
+    users = event.message.reacted_with(emoji)
+
+    users.each do |user|
+      event.message.delete_reaction(user.id, emoji) unless user.current_bot?
+    end
+
+    char = Character.find(carousel.char_id)
+    img = ImageController.img_scroll(
+      char_id: char.id,
+      nsfw: event.channel.nsfw?,
+      img: carousel.image_id,
+      dir: vote
+    )
+
+    carousel.update(id: carousel.id, image_id: img.id)
+
+    embed = character_embed(
+      char: char,
+      img: img,
+      user: event.server.member(char.user_id),
+      color: CharacterController.type_color(char),
+      section: :image
+    )
+    event.message.edit("", embed)
+
+  when [:carousel, :number]
+    char_index = nil
+    Emoji::NUMBERS.each.with_index do |emoji, i|
+      char_index = i if reactions[emoji]&.count.to_i > 1
+    end
+
+    if char_index
+      event.message.delete_all_reactions
+
+      char = Character.find(carousel.options[char_index])
+      carousel.update(id: carousel.id, char_id: char.id)
+
+      embed = character_embed(
+        char: char,
+        img: CharImage.where(char_id: char.id).find_by(keyword: 'Default'),
+        user: event.server.member(char.user_id),
+        color: CharacterController.type_color(char),
+        section: :default
+      )
+      event.message.edit("", embed)
+      section_react(event.message)
+    end
+  when [:carousel, :cross]
+    event.message.delete
+    carousel.delete
   end
 end
 

+ 90 - 2
db/schema.sql

@@ -20,6 +20,39 @@ SET default_tablespace = '';
 
 SET default_with_oids = false;
 
+--
+-- Name: carousels; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.carousels (
+    id integer NOT NULL,
+    message_id character varying(50) NOT NULL,
+    char_id integer,
+    options integer[],
+    image_id integer
+);
+
+
+--
+-- Name: carousels_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.carousels_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: carousels_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.carousels_id_seq OWNED BY public.carousels.id;
+
+
 --
 -- Name: char_images; Type: TABLE; Schema: public; Owner: -
 --
@@ -157,6 +190,13 @@ CREATE TABLE public.users (
 );
 
 
+--
+-- Name: carousels id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.carousels ALTER COLUMN id SET DEFAULT nextval('public.carousels_id_seq'::regclass);
+
+
 --
 -- Name: char_images id; Type: DEFAULT; Schema: public; Owner: -
 --
@@ -178,6 +218,14 @@ ALTER TABLE ONLY public.characters ALTER COLUMN id SET DEFAULT nextval('public.c
 ALTER TABLE ONLY public.types ALTER COLUMN id SET DEFAULT nextval('public.types_id_seq'::regclass);
 
 
+--
+-- Data for Name: carousels; Type: TABLE DATA; Schema: public; Owner: -
+--
+
+COPY public.carousels (id, message_id, char_id, options, image_id) FROM stdin;
+\.
+
+
 --
 -- Data for Name: char_images; Type: TABLE DATA; Schema: public; Owner: -
 --
@@ -186,6 +234,19 @@ COPY public.char_images (id, char_id, url, category, keyword) FROM stdin;
 2	1	https://i.pinimg.com/originals/e1/af/36/e1af3607885c7bbd3812706bfe9cbafc.jpg	SFW	Neiro's Nightmare
 4	1	https://i.pinimg.com/originals/53/0a/32/530a3267c68dcf5711855d4329e3bbee.png	SFW	Hero
 3	1	https://i.imgur.com/CqtkxMr.png	SFW	Default
+8	2	http://d.facdn.net/art/fishinabarrrel/1568640777/1568640773.fishinabarrrel_neiro_ref_sfw.png	SFW	Default
+9	3	http://static.pokemonpets.com/images/monsters-images-800-800/8715-Mega-Noivern.png	SFW	Default
+10	4	https://i.imgur.com/GZY4QN7.png	SFW	Default
+11	1	https://i.kym-cdn.com/photos/images/original/001/194/272/043.png	SFW	Cutesy
+12	1	https://i.kym-cdn.com/photos/images/original/001/183/270/c38.png	NSFW	Salty
+13	1	https://cdnb.artstation.com/p/assets/images/images/003/772/763/large/mauricio-cid-mimikkyu.jpg?1477345311	SFW	Spooky
+14	1	https://66.media.tumblr.com/536300ac3be54c647394e959b20efce4/tumblr_prxhywHjGa1qagaoco1_400.png	SFW	Detective
+15	4	https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcQAqq4k9ffG_WLCbHLXD23T4RXxMy0VIwcBS-ErrArZxgViEnFP	SFW	Sweating
+16	4	https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcSIBDTycnrDuTc1BiUiA5BJVd1l5AblA5u9FkL66DcsjzP1KuEY	SFW	Attacking
+17	4	https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcQjEu8qtYF6WR3fbCCCCyQB8t6Y8qcZe40tuBKbSGD6vpvHLps5	SFW	Scheming
+18	4	https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcRGttdLkAF5trFBFeh6DziEy7uD8vkmOgZQcmkB44WaTQQYDiHf	SFW	Playful
+19	4	https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcS6J-el9WuMFQ_g11r57Y1UaGqcwBYCjuGu7jRSVIYIozTl2Bzl	SFW	Mega
+20	4	https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcRG65IsiuZr1HPn4kINl6ECHNcfz6nXWu7hlkdmQjy5H9nujuv-	SFW	Hypnosis
 \.
 
 
@@ -195,6 +256,10 @@ COPY public.char_images (id, char_id, url, category, keyword) FROM stdin;
 
 COPY public.characters (id, user_id, name, species, types, age, weight, height, gender, orientation, relationship, attacks, likes, dislikes, personality, backstory, other, edit_url, active, dm_notes, location, rumors, hometown, warnings, rating) FROM stdin;
 1	215240568245190656	Mizukyu	Mimikyu	Ghost/Fairy	Old	1.5 lbs	0'8"	Female	Pansexual	Married	Shadow Claw | Play Rough | Psychic | Shadow Sneak	Cuddles, soft things, spooky stories, horror	Bullies, rejection, being exposed	I am shy and a bit recluse, but warm when you take the time to get to know me. I don't like when people try to see under my disguise, I've lost many friends that way (to death) including my spouse. Really, really really likes to dress up as other pokemon	Has existed more years than she cares to count. She is immortal, due to being a ghost. She has learned to carefully hide her appearance as to make sure not to accidentally kill more friends. Took up tailoring to make multiple disguises, because pretending to be a Pikachu forever is boring.	Switches disguising with moods. Has made them for all pokemon. Also a master shadow bender	?edit2=2_ABaOnuc3HZEyhI9EeApXcJtsBmDzqtzDGH5De46CfuRBxwVavQKAfTT_LZy_kMH0sz5H7gk	Active	\N	\N	Loves children | Hates washing and tailoring disguises | Surprisingly soft | Steals children | Wishes to be admired	\N	Horrific Eldritch Abomination	\N
+2	271741998321369088	Neiro	Vaporeon	Water	33	125 lbs	3'03"	Male	Bisexual	Single	Water Gun|Hydro Pump|Ice Beam|Synchronoise	Likes to overeat, being in the rain, and playing with fat	Hates that his body absorbs any liquids	Carefree and a little bit lazy. Always up for fun things.	He was spoiled from the very beginning of his life. This resulted in him being a bit fat and lazy. He loved it tho but decided to make something more with his life by joining a rescue team. He also wanted to work on his skills as being taken care of his whole life made him pretty useless in battle.	He has the special ability Water Absorb. Neiro's body can bloat up to 250 times his normal size before his body stopped expanding. His tail can also bloat equal to that, making his total potential size 500 times	?edit2=2_ABaOnufidKuBLj5ecAtAjVd0CTTFoJhB0CqE9rM-WW0yeYSfnxeKjblep_Nru3f8jpOzkS4	Active	\N	\N	Uses Water Gun every time he sneezes.|His Water Absorb ability absorbs any liquids into his body.|Too much water makes him bloat like a sponge.|He can piss for an hour.|He is outstanding with his move execution and aim	The Village	Dont share a shower with him... 	\N
+3	215240568245190656	Zumi	Noivern	Dragon/Flying	Young	120 lbs	3'0"	Male	\N	\N	Dragon Pulse | Screech | Bite | Wing Attack	Zumi loves games and all his friends. He also loves sweets and sour candy	Zumi doesn't like bullies or mean kids that constantly make fun of other kids or exclude them from the fun	Zumi is a fun loving Derg that likes to play with anyone and everyone! He also likes to play fun jokes on people or do a bit of spooking with his ghosty parents -- but he tries not to take it too far. He tries to protect his friends as much as he can, but theres only so much a little dragon can do	Zumi is a young dragon, and doesn't really remember much before coming to the guild house. He doesn't remember his birth parents, or when he met his ghost parents, but he isn't really bothered about it. All he knows, is that he loves his ghost parents and wants to make as many friends as he can!	\N	?edit2=2_ABaOnuf86ZAxBbQfeKlKe40wSsMNOKbAg8yb9OIS7GsZ1Qs8dtVRX13RLf4uJnuRawMf5io	Active	\N	\N	He was abandoned as a baby | He wishes he could fly faster | He was rescued by a ghostly couple | He doesn't scare easy | He's just a dumb kid	\N	Has a spooky set of parents that will ruin your day	\N
+4	215240568245190656	Ozi	Gengar	Ghost/Poison	???	90 lbs	5'0"	Male	Straight	Married	Shadow Ball | Sludge Bomb | Psychic | Dazzling Gleam	Jokes and Spooks	Kill joys and Arrogance	Theres never a dull moment with Ozi! He likes to pull pranks and make people either laugh or scream. He loves his wife (Mizukyu) and son (Zumi) and will spook anyone who harms them to death. So long as you play nice, he's a great ghosty guy!	No one knows where Ozi came from, or why he came here, but he and his wife Mizukyu have caused a lot of mayhem in their many years together. He's been known to suddenly appear or disappear and even caused some unfortunate souls to go mad with insanity. Over time he's learned to dial back the spooks a bit, and even tried to make some new friends	Ozi sometimes causes trouble with his ghost powers, behaving a bit like the Cheshire Cat. He can be a bit slippery, but tries not to cause too much trouble	?edit2=2_ABaOnueS5nTN63soX4wQ0PVKa_tGFghbIpiYlW3kwGw4Cj09Fnio0qNLD47iyOC5H3f4oN4	Active	\N	\N	He taught Zumi how to fly | He's a surprisingly good father | Sometimes he doesn't know when to stop the jokes | His humor is not for everyone | I'm not totally convinced he's real	\N	Spooks ahead!	\N
+5	215240568245190656	Neiro	Vaporeon	Water	\N	???	varies	Male	\N	\N	Duplicate | Pokemon | Attack	\N	\N	\N	\N	This is test entry	?edit2=2_ABaOnuc441lxr76HNkZnq7BJq0f_VFhK9OM8YMUGkIbQGKSU8apSzv6re4BevYVYi6QzmI8	Active	\N	\N	\N	\N	\N	\N
 \.
 
 
@@ -236,18 +301,25 @@ COPY public.users (id, level, next_level, boosted_xp, unboosted_xp, evs, hp, att
 \.
 
 
+--
+-- Name: carousels_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
+--
+
+SELECT pg_catalog.setval('public.carousels_id_seq', 29, true);
+
+
 --
 -- Name: char_images_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
 --
 
-SELECT pg_catalog.setval('public.char_images_id_seq', 6, true);
+SELECT pg_catalog.setval('public.char_images_id_seq', 20, true);
 
 
 --
 -- Name: characters_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
 --
 
-SELECT pg_catalog.setval('public.characters_id_seq', 1, true);
+SELECT pg_catalog.setval('public.characters_id_seq', 5, true);
 
 
 --
@@ -257,6 +329,14 @@ SELECT pg_catalog.setval('public.characters_id_seq', 1, true);
 SELECT pg_catalog.setval('public.types_id_seq', 19, true);
 
 
+--
+-- Name: carousels carousels_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.carousels
+    ADD CONSTRAINT carousels_pkey PRIMARY KEY (id);
+
+
 --
 -- Name: char_images char_images_pkey; Type: CONSTRAINT; Schema: public; Owner: -
 --
@@ -297,6 +377,14 @@ ALTER TABLE ONLY public.users
     ADD CONSTRAINT users_pkey PRIMARY KEY (id);
 
 
+--
+-- Name: carousels carousel_char_id; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.carousels
+    ADD CONSTRAINT carousel_char_id FOREIGN KEY (char_id) REFERENCES public.characters(id);
+
+
 --
 -- Name: characters character_user_id; Type: FK CONSTRAINT; Schema: public; Owner: -
 --

+ 28 - 2
lib/emoji.rb

@@ -27,10 +27,26 @@ module Emoji
   X = "🇽"
   Y = "🇾"
   Z = "🇿"
-  LEFT = "⬅"
-  RIGHT = "➡"
+
+  ONE = "1⃣"
+  TWO = "2⃣"
+  THREE = "3⃣"
+  FOUR = "4⃣"
+  FIVE = "5⃣"
+  SIX = "6⃣"
+  SEVEN = "7⃣"
+  EIGHT = "8⃣"
+  NINE = "9⃣"
+  TEN = "🔟"
+  NUMS = "🔢"
+
+  LEFT = "◀"
+  RIGHT = "▶"
+  UNDO = "⏫"
+
   CHECK = "✅"
   CROSS = "❌"
+
   SPEECH_BUBBLE = "💬"
   SCALE = "⚖"
   PICTURE = "🖼"
@@ -45,8 +61,18 @@ module Emoji
   KEY = "🔑"
   PAGE = "📄"
   FLAG = "🚩"
+  NOTEBOOK = "📔"
+  PALLET = "🎨"
+  EAR = "👂"
+  QUESTION = "❔"
+  BAGS = "🛍"
+  FAMILY = "👪"
+  EYES = "👀"
+
 
   LETTERS = [A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
+  NUMBERS = [ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN]
   CHAR_APP = [SPEECH_BUBBLE, SCALE, PICTURE, BOOKS, BABY, SKULL, VULGAR, NOTE]
   IMG_APP = [DOG, KEY, FLAG, PAGE, BOOKS, VULGAR]
+  CAROUSEL = [NOTEBOOK, QUESTION, PALLET, EAR, PICTURE, BAGS, FAMILY, EYES, KEY, CROSS]
 end