2024-05-03 03:04:43 +00:00
# frozen_string_literal: true
module YiffRest
2024-05-06 07:47:53 +00:00
class APIV2Controller < APIV2 :: ApplicationController
2024-05-03 03:04:43 +00:00
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 ( " / " , " . " ) || " "
2024-05-03 06:39:05 +00:00
category = category [ 3 .. ] if category . start_with? ( " v2. " )
2024-05-03 03:04:43 +00:00
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
2024-05-06 07:47:53 +00:00
categories = APIImage . categories . map ( & :db )
2024-05-03 03:04:43 +00:00
[ * 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
2024-05-09 02:25:36 +00:00
Total : ** #{bulk_categories.values.sum}**
2024-05-03 03:04:43 +00:00
** Categories : **
#{bulk_categories.permit!.to_h.map { |c, l| "- **#{c}**: #{l}" }.join("\n")}
BULK
2024-05-09 02:19:08 +00:00
color = 0xDC143C if bulk_categories . keys . any? { | k | category_info . find { | c | c . db == k } . sfw }
2024-05-09 02:25:36 +00:00
elsif ! category_info . find { | c | c . db == category } . sfw
2024-05-03 03:04:43 +00:00
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