diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 891cf061a..36a39d593 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -88,7 +88,7 @@ class PostsController < ApplicationController end def random - @post = Post.user_tag_match(params[:tags]).random + @post = Post.user_tag_match(params[:tags]).random(1).first raise ActiveRecord::RecordNotFound if @post.nil? authorize @post respond_with(@post) do |format| diff --git a/app/logical/discord_api_client.rb b/app/logical/discord_api_client.rb index 72af6a822..db8bd7b9c 100644 --- a/app/logical/discord_api_client.rb +++ b/app/logical/discord_api_client.rb @@ -22,16 +22,20 @@ class DiscordApiClient post("/applications/#{application_id}/guilds/#{guild_id}/commands", json) end - def get_channel(channel_id) - get("/channels/#{channel_id}") + def get_channel(channel_id, **options) + get("/channels/#{channel_id}", **options) end - def me - get("/users/@me") + def me(**options) + get("/users/@me", **options) end - def get(url) - client.get("#{BASE_URL}/#{url}").parse + def get(url, cache: nil, **options) + if cache + client.cache(cache).get("#{BASE_URL}/#{url}").parse + else + client.get("#{BASE_URL}/#{url}").parse + end end def post(url, data) diff --git a/app/logical/discord_slash_command.rb b/app/logical/discord_slash_command.rb index 770ebe04e..eaf747728 100644 --- a/app/logical/discord_slash_command.rb +++ b/app/logical/discord_slash_command.rb @@ -4,6 +4,7 @@ class DiscordSlashCommand COMMANDS = { count: DiscordSlashCommand::CountCommand, posts: DiscordSlashCommand::PostsCommand, + random: DiscordSlashCommand::RandomCommand, } # https://discord.com/developers/docs/interactions/slash-commands#interaction-interactiontype @@ -15,6 +16,7 @@ class DiscordSlashCommand # https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptiontype module ApplicationCommandOptionType String = 3 + Integer = 4 end attr_reader :data, :discord @@ -51,14 +53,19 @@ class DiscordSlashCommand concerning :HelperMethods do # The parameters passed to the command. A hash. def params - @params ||= data.dig(:data, :options).map do |opt| + @params ||= data.dig(:data, :options).to_a.map do |opt| [opt[:name], opt[:value]] end.to_h.with_indifferent_access end # https://discord.com/developers/docs/interactions/slash-commands#responding-to-an-interaction # https://discord.com/developers/docs/interactions/slash-commands#interaction-response - def respond_with(content = nil, type: 4, **options) + def respond_with(content = nil, type: 4, posts: [], **options) + if posts.present? + embeds = posts.map { |post| DiscordSlashCommand::PostEmbed.new(post, self).to_h } + options[:embeds] = embeds + end + { type: type, data: { @@ -68,6 +75,10 @@ class DiscordSlashCommand } end + def channel + discord.get_channel(data[:channel_id], cache: 1.minute) + end + # Register the command with the Discord API (replacing it if it already exists). # https://discord.com/developers/docs/interactions/slash-commands#registering-a-command def register_slash_command diff --git a/app/logical/discord_slash_command/post_embed.rb b/app/logical/discord_slash_command/post_embed.rb new file mode 100644 index 000000000..718e074e6 --- /dev/null +++ b/app/logical/discord_slash_command/post_embed.rb @@ -0,0 +1,67 @@ +class DiscordSlashCommand + class PostEmbed + attr_reader :post, :command + + def initialize(post, command) + @post = post + @command = command + end + + def to_h + { + title: post.dtext_shortlink, + url: Routes.url_for(post), + timestamp: post.created_at.iso8601, + color: embed_color, + footer: embed_footer, + image: { + width: post.image_width, + height: post.image_height, + url: embed_image, + }, + } + end + + def embed_image + if is_censored? + nil + elsif post.file_ext.match?(/jpe?g|png|gif/) + post.file_url + else + post.preview_file_url + end + end + + def embed_color + if post.is_flagged? + 0xC41C19 + elsif post.is_pending? + 0x0000FF + elsif post.parent_id.present? + 0xC0C000 + elsif post.has_active_children? + 0x00FF00 + elsif post.is_deleted? + 0xFFFFFF + else + nil + end + end + + def embed_footer + dimensions = "#{post.image_width}x#{post.image_height}" + file_size = post.file_size.to_s(:human_size, precision: 4) + text = "Rating: #{post.rating.upcase} | #{dimensions} (#{file_size} #{post.file_ext})" + + { text: text } + end + + def is_censored? + post.rating != "s" && !is_nsfw_channel? + end + + def is_nsfw_channel? + command.channel.fetch("nsfw") + end + end +end diff --git a/app/logical/discord_slash_command/posts_command.rb b/app/logical/discord_slash_command/posts_command.rb index 135035904..0e59f2a89 100644 --- a/app/logical/discord_slash_command/posts_command.rb +++ b/app/logical/discord_slash_command/posts_command.rb @@ -11,83 +11,26 @@ class DiscordSlashCommand end def options - [{ - name: "tags", - description: "The tags to search", - required: true, - type: ApplicationCommandOptionType::String - }] + [ + { + name: "tags", + description: "The tags to search", + type: ApplicationCommandOptionType::String + }, + { + name: "limit", + description: "The number of posts to show (max 10)", + type: ApplicationCommandOptionType::Integer + } + ] end def call tags = params[:tags] - query = PostQueryBuilder.new(tags, User.anonymous).normalized_query + limit = params.fetch(:limit, 3).clamp(1, 10) + posts = Post.user_tag_match(tags, User.anonymous).limit(limit) - limit = query.find_metatag(:limit) || 3 - limit = limit.to_i.clamp(1, 10) - posts = query.build.paginate(1, limit: limit) - embeds = posts.map { |post| post_embed(post) } - - respond_with(embeds: embeds) + respond_with(posts: posts) end - - def post_embed(post) - { - title: post.dtext_shortlink, - url: Routes.url_for(post), - timestamp: post.created_at.iso8601, - color: post_embed_color(post), - footer: post_embed_footer(post), - image: { - width: post.image_width, - height: post.image_height, - url: post_embed_image(post), - }, - } - end - - def post_embed_image(post, blur: 50) - if is_censored?(post) - nil - elsif post.file_ext.match?(/jpe?g|png|gif/) - post.file_url - else - post.preview_file_url - end - end - - def post_embed_color(post) - if post.is_flagged? - 0xC41C19 - elsif post.is_pending? - 0x0000FF - elsif post.parent_id.present? - 0xC0C000 - elsif post.has_active_children? - 0x00FF00 - elsif post.is_deleted? - 0xFFFFFF - else - nil - end - end - - def post_embed_footer(post) - dimensions = "#{post.image_width}x#{post.image_height}" - file_size = post.file_size.to_s(:human_size, precision: 4) - text = "Rating: #{post.rating.upcase} | #{dimensions} (#{file_size} #{post.file_ext})" - - { text: text } - end - - def is_censored?(post) - post.rating != "s" && !is_nsfw_channel? - end - - def is_nsfw_channel? - discord.get_channel(data[:channel_id]).fetch("nsfw") - end - - memoize :is_nsfw_channel? end end diff --git a/app/logical/discord_slash_command/random_command.rb b/app/logical/discord_slash_command/random_command.rb new file mode 100644 index 000000000..888e8ad15 --- /dev/null +++ b/app/logical/discord_slash_command/random_command.rb @@ -0,0 +1,34 @@ +class DiscordSlashCommand + class RandomCommand < DiscordSlashCommand + def name + "random" + end + + def description + "Show a random post" + end + + def options + [ + { + name: "tags", + description: "The tags to search", + type: ApplicationCommandOptionType::String + }, + { + name: "limit", + description: "The number of posts to show (max 10)", + type: ApplicationCommandOptionType::Integer + } + ] + end + + def call + tags = params[:tags] + limit = params.fetch(:limit, 1).clamp(1, 10) + posts = Post.user_tag_match(tags, User.anonymous).random(limit) + + respond_with(posts: posts) + end + end +end diff --git a/app/logical/post_sets/post.rb b/app/logical/post_sets/post.rb index 6822248fa..adf4eafb0 100644 --- a/app/logical/post_sets/post.rb +++ b/app/logical/post_sets/post.rb @@ -103,9 +103,7 @@ module PostSets end def get_random_posts - per_page.times.inject([]) do |all, _| - all << ::Post.user_tag_match(tag_string).random - end.compact.uniq + ::Post.user_tag_match(tag_string).random(per_page) end def posts diff --git a/app/models/post.rb b/app/models/post.rb index 1bbd402e9..21ed66ec3 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -1144,10 +1144,11 @@ class Post < ApplicationRecord end module SearchMethods - # returns one single post - def random - key = Digest::MD5.hexdigest(Time.now.to_f.to_s) - random_up(key) || random_down(key) + def random(n = 1) + n.times.map do + key = SecureRandom.hex(16) + random_up(key) || random_down(key) + end.compact.uniq end def random_up(key)