Files
danbooru/app/logical/sources/strategies/fanbox.rb
evazion 1d2a8a7898 fanbox: don't raise error on age-restricted posts.
Prevent age-restricted fanbox posts from raising errors when source data
is fetched. This prevents error messages from being shown to users when
switching to the edit tab on a post.

This will cause uploads of age-restricted posts to fail with an
unrelated error because we either can't find the image url (if we were
given only the html page) or we can't download the image (because we're
not logged in to Fanbox).
2020-08-18 15:34:12 -05:00

187 lines
6.5 KiB
Ruby

# Image URLs #############################################################
#
# * OLD DOMAIN
# ** https://fanbox.pixiv.net/images/post/39714/JvjJal8v1yLgc5DPyEI05YpT.png
#
# * NEW DOMAIN
# ** https://downloads.fanbox.cc/images/post/39714/JvjJal8v1yLgc5DPyEI05YpT.png (full res)
# ** https://downloads.fanbox.cc/images/post/39714/c/1200x630/JvjJal8v1yLgc5DPyEI05YpT.jpeg (sample)
# ** https://downloads.fanbox.cc/images/post/39714/w/1200/JvjJal8v1yLgc5DPyEI05YpT.jpeg (sample)
#
# * POST COVERS
# * https://pixiv.pximg.net/c/1200x630_90_a2_g5/fanbox/public/images/post/186919/cover/VCI1Mcs2rbmWPg0mmiTisovn.jpeg
#
# * PROFILE IMAGES
# * https://pixiv.pximg.net/c/400x400_90_a2_g5/fanbox/public/images/creator/1566167/profile/Ix6bnJmTaOAFZhXHLbWyIY1e.jpeg
# * https://pixiv.pximg.net/fanbox/public/images/creator/1566167/profile/Ix6bnJmTaOAFZhXHLbWyIY1e.jpeg (dead URL type)
# * https://pixiv.pximg.net/c/1620x580_90_a2_g5/fanbox/public/images/creator/1566167/cover/WPqKsvKVGRq4qUjKFAMi23Z5.jpeg
# * https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/4635/cover/L6AZNneFuHW6r25CHHlkpHg4.jpeg
#
# Page URLs ##############################################################
#
# * OLD
# ** https://www.pixiv.net/fanbox/creator/1566167/post/39714
#
# * NEW
# ** https://omu001.fanbox.cc/posts/39714
# ** https://www.fanbox.cc/@tsukiori/posts/1080657
# ** https://brllbrll.fanbox.cc/posts/626093 (R-18)
#
#
# Profile URLs ###########################################################
#
# * OLD
# ** https://www.pixiv.net/fanbox/creator/1566167
#
# * NEW
# ** https://omu001.fanbox.cc/
#
module Sources
module Strategies
class Fanbox < Base
PROFILE_OLD = %r{\Ahttps?://(?:www\.)?pixiv\.net/fanbox/creator/(?<artist_id>\d+)}i
PROFILE_NEW = %r{\Ahttps?://(?:(?!www|downloads)(?<artist_name>[\w-]+)\.fanbox\.cc|(?:www\.)?fanbox\.cc/@(?<artist_name>[\w-]+))}i
PAGE_OLD = %r{#{PROFILE_OLD}/post/(?<illust_id>\d+)}i
PAGE_NEW = %r{#{PROFILE_NEW}/posts/(?<illust_id>\d+)}i
IMAGE = %r{\Ahttps?://(?:fanbox\.pixiv\.net|downloads\.fanbox\.cc)/images/post/(?<illust_id>\d+)/(?:\w+/)*\w+\.\w+}i
OTHER_IMAGES = %r{\Ahttps?://pixiv\.pximg\.net/.*/fanbox/.*?/(?:(?:creator|user)/(?<artist_id>\d+)|post/(?<illust_id>\d+))?/(?:.*/)?\w+\.\w+}i
def domains
["fanbox.cc", "pixiv.net", "pximg.net"]
end
def site_name
"Pixiv Fanbox"
end
def image_urls
if url =~ IMAGE || url =~ OTHER_IMAGES
[url]
elsif api_response.present?
# There's two ways pics are returned via api:
# Pics in proper array: https://yanmi0308.fanbox.cc/posts/1141325
# Embedded pics (imageMap): https://www.fanbox.cc/@tsukiori/posts/1080657
images = api_response.dig("body", "images").to_a + api_response.dig("body", "imageMap").to_a.map { |id| id[1] }
images.map { |img| img["originalUrl"] }
else
[url]
end
end
def page_url
if illust_id.present?
"https://#{artist_name}.fanbox.cc/posts/#{illust_id}"
elsif url =~ OTHER_IMAGES && artist_name.present?
# Cover images
"https://#{artist_name}.fanbox.cc"
end
end
def normalize_for_source
if illust_id.present?
if artist_name_from_url.present?
"https://#{artist_name_from_url}.fanbox.cc/posts/#{illust_id}"
elsif artist_id_from_url.present?
"https://www.pixiv.net/fanbox/creator/#{artist_id_from_url}/post/#{illust_id}"
end
elsif artist_id_from_url.present?
# Cover images
"https://www.pixiv.net/fanbox/creator/#{artist_id_from_url}"
end
end
def profile_url
return if artist_name.blank?
"https://#{artist_name}.fanbox.cc"
end
def artist_name
artist_name_from_url || api_response["creatorId"] || artist_api_response["creatorId"]
end
def display_name
api_response.dig("user", "name") || artist_api_response.dig("user", "name")
end
def other_names
[artist_name, display_name].compact.uniq
end
def tags
api_response["tags"].to_a.map { |tag| [tag, "https://fanbox.cc/tags/#{tag}"] }
end
def artist_commentary_title
api_response["title"]
end
def artist_commentary_desc
body = api_response["body"]
return if body.blank?
if body["text"].present?
body["text"]
elsif body["blocks"].present?
# Reference: https://official.fanbox.cc/posts/182757
# Commentary can get pretty complex, but unfortunately it's served in json format so it's a pain to parse it.
# I've left out parsing external embeds because each supported site has its own id mapped to the domain
commentary = body["blocks"].map do |node|
if node["type"] == "image"
body["imageMap"][node["imageId"]]["originalUrl"]
else
node["text"] || "\n"
end
end
commentary.join("\n")
end
end
def illust_id
urls.map { |url| url[PAGE_NEW, :illust_id] || url[IMAGE, :illust_id] || url[PAGE_OLD, :illust_id] || url[OTHER_IMAGES, :illust_id] }.compact.first
end
def artist_id_from_url
urls.map { |url| url[PAGE_OLD, :artist_id] || url[OTHER_IMAGES, :artist_id] }.compact.first
end
def artist_name_from_url
urls.map { |url| url[PROFILE_NEW, :artist_name] }.compact.first
end
def api_response
return {} if illust_id.blank?
resp = client.get("https://api.fanbox.cc/post.info?postId=#{illust_id}")
json_response = JSON.parse(resp)["body"]
# Pixiv Fanbox login is protected by Google Recaptcha, so it's not
# possible for us to extract anything from them (save for the title).
# Other projects like PixivUtils ask the user to periodically extract
# cookies from the browser, but this is not feasible for Danbooru.
return {} if json_response["restrictedFor"] == 2 && json_response["body"].blank?
json_response
rescue JSON::ParserError
{}
end
def artist_api_response
# Needed to fetch artist from cover pages
return {} if artist_id_from_url.blank?
resp = client.get("https://api.fanbox.cc/creator.get?userId=#{artist_id_from_url}")
JSON.parse(resp)["body"]
rescue JSON::ParserError
{}
end
def client
@client ||= http.headers(Origin: "https://fanbox.cc").cache(1.minute)
end
end
end
end