Donovan Daniels
b1c702e3cd
poorly tested but it worked well enough, I'm sure I'll be patching bugs over the next few weeks Also remove turbo because it sucks Also changed the way we handle hosts in dev
205 lines
8.5 KiB
Ruby
205 lines
8.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module YiffRest
|
|
class APIV2Controller < APIV2::ApplicationController
|
|
include ::ApplicationController::ReadonlyMethods
|
|
before_action :handle_ratelimit, except: %i[robots]
|
|
before_action :validate_images_access, only: %i[index]
|
|
before_action :validate_images_bulk_access, only: %i[bulk]
|
|
before_action -> { track_usage("images") }, only: %i[index categories category image]
|
|
|
|
def index
|
|
return render_error(YiffyAPIErrorCodes::IMAGES_IMAGE_RESPONSE_DISABLED, error: "Image response has been disabled. Please use the json response.") if params[:category].ends_with?("/image")
|
|
|
|
category = params[:category]&.downcase&.gsub("/", ".") || ""
|
|
category = category[3..] if category.start_with?("v2.")
|
|
return redirect_to("https://yiff.rest") if category.blank?
|
|
|
|
limit = params[:amount]&.to_i || 1
|
|
return render_error(YiffyAPIErrorCodes::IMAGES_AMOUNT_LT_ONE, error: "Amount must be 1 or more.") if limit < 1
|
|
return render_error(YiffyAPIErrorCodes::IMAGES_AMOUNT_LT_ONE, error: "Amount must be 5 or less.") if limit > 5
|
|
|
|
size_limit = params[:sizeLimit].blank? ? nil : Filesize.from(params[:sizeLimit].to_s).to_i
|
|
return render_error(YiffyAPIErrorCodes::IMAGES_CATEGORY_NOT_FOUND, error: "Category not found.") unless api_categories.include?(category)
|
|
|
|
images = APIImage.random(category, limit, size_limit)
|
|
|
|
return render_error(YiffyAPIErrorCodes::IMAGES_NO_RESULTS, error: "No results were found. Try changing your search parameters.") if images.empty?
|
|
|
|
Cache.redis.multi do |r|
|
|
r.incr("yiffy2:stats:images:#{category}")
|
|
r.incr("yiffy2:stats:images:ip:#{request.remote_ip}")
|
|
r.incr("yiffy2:stats:images:ip:#{request.remote_ip}:#{category}")
|
|
r.incr("yiffy2:stats:images:total")
|
|
r.incr("yiffy2:stats:images:total:#{category}")
|
|
if @apikey.present? && !@apikey&.is_anon?
|
|
r.incr("yiffy2:stats:images:key:#{@apikey.id}")
|
|
r.incr("yiffy2:stats:images:key:#{@apikey.id}:#{category}")
|
|
end
|
|
end
|
|
|
|
render(json: {
|
|
"$schema": "https://schema.yiff.rest/V2.json",
|
|
images: images,
|
|
success: true,
|
|
notes: notes_for_request,
|
|
})
|
|
end
|
|
|
|
def robots
|
|
render(plain: <<~ROBOTS)
|
|
User-agent: *
|
|
Disallow: /
|
|
ROBOTS
|
|
end
|
|
|
|
def stats
|
|
render(json: {
|
|
success: true,
|
|
data: APIKey.stats(ip: request.remote_ip, key: @apikey),
|
|
})
|
|
end
|
|
|
|
def categories
|
|
render(json: {
|
|
success: true,
|
|
data: APIImage.categories,
|
|
})
|
|
end
|
|
|
|
def category
|
|
category = params[:category]&.downcase&.gsub("/", ".") || ""
|
|
return render_error(YiffyAPIErrorCodes::IMAGES_CATEGORY_NOT_FOUND, error: "Category not found.") unless api_categories.include?(category)
|
|
count = APIImage.cached_count(category)
|
|
render(json: {
|
|
success: true,
|
|
data: {
|
|
**APIImage.categories.find { |c| c[:db] == category },
|
|
count: count,
|
|
},
|
|
})
|
|
end
|
|
|
|
def image
|
|
image = APIImage.find_by(id: params[:id].gsub("-", ""))
|
|
return render_error(YiffyAPIErrorCodes::IMAGES_NOT_FOUND, error: "Image not found.") unless image
|
|
render(json: {
|
|
success: true,
|
|
data: {
|
|
**image.as_json,
|
|
category: image.category,
|
|
},
|
|
})
|
|
end
|
|
|
|
def bulk
|
|
req = params[:api_v2] || {}
|
|
return render_error(YiffyAPIErrorCodes::BULK_IMAGES_INVALID_BODY, error: "Invalid body, or no categories specified.") if req.blank?
|
|
size_limit = params[:sizeLimit].blank? ? nil : Filesize.from(params[:sizeLimit].to_s).to_i
|
|
total = 0
|
|
valid = true
|
|
req.each do |category, amount|
|
|
total += amount.to_i
|
|
next if api_categories.include?(category)
|
|
|
|
render_error(YiffyAPIErrorCodes::IMAGES_CATEGORY_NOT_FOUND, error: "Invalid category specified: #{category}")
|
|
valid = false
|
|
break
|
|
end
|
|
|
|
return unless valid
|
|
return render_error(YiffyAPIErrorCodes::BULK_IMAGES_NUMBER_GT_MAX, error: "Total amount of images requested is greater than #{@apikey.bulk_limit} (#{total}).") if total > @apikey.bulk_limit
|
|
|
|
images = APIImage.bulk(req, size_limit)
|
|
|
|
return render_error(YiffyAPIErrorCodes::IMAGES_NO_RESULTS, error: "No results were found. Try changing your search parameters.") if images.empty?
|
|
|
|
Cache.redis.multi do |r|
|
|
r.incrby("yiffy2:stats:images:ip:#{request.remote_ip}", total)
|
|
r.incr("yiffy2:stats:images:ip:#{request.remote_ip}:bulk")
|
|
r.incrby("yiffy2:stats:images:total", total)
|
|
r.incr("yiffy2:stats:images:total:bulk")
|
|
r.incrby("yiffy2:stats:images:key:#{@apikey.id}", total)
|
|
r.incr("yiffy2:stats:images:key:#{@apikey.id}:bulk")
|
|
req.each do |category, amount|
|
|
r.incrby("yiffy2:stats:images:#{category}", amount.to_i)
|
|
r.incr("yiffy2:stats:images:#{category}:bulk")
|
|
r.incrby("yiffy2:stats:images:ip:#{request.remote_ip}:#{category}", amount.to_i)
|
|
r.incr("yiffy2:stats:images:ip:#{request.remote_ip}:#{category}:bulk")
|
|
r.incrby("yiffy2:stats:images:total:#{category}", amount.to_i)
|
|
r.incr("yiffy2:stats:images:total:#{category}:bulk")
|
|
r.incrby("yiffy2:stats:images:key:#{@apikey.id}:#{category}", amount.to_i)
|
|
r.incr("yiffy2:stats:images:key:#{@apikey.id}:#{category}:bulk")
|
|
end
|
|
end
|
|
|
|
execute_webhook("bulk", req)
|
|
|
|
render(json: {
|
|
success: true,
|
|
data: images,
|
|
})
|
|
end
|
|
|
|
private
|
|
|
|
def api_categories
|
|
categories = APIImage.categories.map(&:db)
|
|
[*categories, "chris"] # gay little polar cutie
|
|
end
|
|
|
|
def category_info
|
|
[*APIImage.categories, { name: "Gay Polar Cutie", db: "chris", sfw: true }] # gay little polar cutie
|
|
end
|
|
|
|
def notes_for_request
|
|
return [] if params[:notes].to_s.downcase == "disabled"
|
|
notes = []
|
|
# notes << { id: 1, content: "This api host (api.furry.bot) is being removed on June 9th, 2021. Please migrate to https://yiff.rest." } if headers["Host"] == "api.furry.bot"
|
|
notes << { id: 2, content: "We've moved to using subdomains for api versioning! e.g. https://v2.yiff.rest. They have the same functionality, just without the version in the path. The /V2 route will not be removed, but v3 and forward will only use the subdomain." } if request.path.start_with?("/V2")
|
|
notes << { id: 3, content: "Hey, we see you aren't using an api key. They're free! To get one, visit https://yiff.rest/apikeys." } if headers["Authorization"].blank?
|
|
# notes << { id: 4, content: "WARNING! This list is STATIC, it can be inaccurate! We recommended parsing the dot notation in https://v2.yiff.rest/categories instead of this!" }
|
|
# notes << { id: 5, content: "Since images are getting bigger, we're adding a size limit parameter. Add ?sizeLimit=<size> to limit the size of images we provide you." } if params[:sizeLimit].blank?
|
|
notes << { id: 6, content: "You can now hide these notes by setting the notes parameter to disabled. (ex: ?notes=disabled)" }
|
|
notes << { id: 7, content: "We now have proper documentation: https://docs.yiff.rest" }
|
|
notes << { id: 8, content: "We have a new service available, a thumbnailer for e621. You can see its documentation at https://docs.yiff.rest/thumbnails." }
|
|
end
|
|
|
|
def execute_webhook(category, bulk_categories = nil)
|
|
bulk = ""
|
|
color = 0x008000
|
|
if bulk_categories.present?
|
|
bulk = <<~BULK
|
|
|
|
**Categories:**
|
|
#{bulk_categories.permit!.to_h.map { |c, l| "- **#{c}**: #{l}" }.join("\n")}
|
|
BULK
|
|
color = 0xDC143C if bulk_categories.keys.any? { |k| category_info.find { |c| c[:db] == k }[:sfw] }
|
|
elsif !(category_info.find { |c| c[:db] == category }[:sfw])
|
|
color = 0xDC143C
|
|
end
|
|
Websites.config.yiffyapi_usage_webhook.execute(embeds: [
|
|
{
|
|
title: "V2 API Request",
|
|
description: <<~DESC.strip,
|
|
Host: **#{request.host}**
|
|
Path: **#{request.path}**
|
|
Category: `#{category}`
|
|
Auth: #{@apikey.nil? || @apikey.is_anon? ? '**No**' : "**Yes** (##{@apikey.id})"}
|
|
Size Limit: **#{params[:sizeLimit] || 'None'}**
|
|
User Agent: **#{request.user_agent}**
|
|
IP: **#{request.remote_ip}**
|
|
#{bulk}
|
|
DESC
|
|
color: color,
|
|
timestamp: Time.now.iso8601,
|
|
},
|
|
])
|
|
end
|
|
|
|
def allowed_readonly_actions
|
|
super + %w[bulk]
|
|
end
|
|
end
|
|
end
|