6c451d260f
Reviewed-on: #402 Co-authored-by: miteruzo <miteruzo@naver.com> Co-committed-by: miteruzo <miteruzo@naver.com>
96 行
3.5 KiB
Ruby
96 行
3.5 KiB
Ruby
module Preview
|
|
class ThumbnailFetcher
|
|
class GenerationFailed < StandardError; end
|
|
ALLOWED_IMAGE_CONTENT_TYPES = [
|
|
'image/jpeg', 'image/png', 'image/gif', 'image/webp'
|
|
].freeze
|
|
HTML_MAX_BYTES = 1.megabyte
|
|
NICONICO_XML_MAX_BYTES = 256.kilobytes
|
|
|
|
def self.fetch(raw_url)
|
|
uri, = UrlSafety.validate(raw_url)
|
|
|
|
known_url = KnownSiteExtractor.thumbnail_url(uri)
|
|
image = fetch_image_or_nil(known_url) if known_url
|
|
return image if image
|
|
|
|
niconico_url = niconico_thumbnail_url(uri)
|
|
image = fetch_image_or_nil(niconico_url) if niconico_url
|
|
return image if image
|
|
|
|
page = HttpFetcher.fetch(uri.to_s, max_bytes: HTML_MAX_BYTES)
|
|
metadata = HtmlMetadataExtractor.extract(page)
|
|
raise GenerationFailed, 'サムネール画像が見つかりませんでした.' if metadata[:image_url].blank?
|
|
|
|
fetch_image!(metadata[:image_url])
|
|
end
|
|
|
|
def self.title(raw_url)
|
|
uri, = UrlSafety.validate(raw_url)
|
|
HtmlMetadataExtractor.extract(
|
|
HttpFetcher.fetch(uri.to_s, max_bytes: HTML_MAX_BYTES))[:title]
|
|
end
|
|
|
|
def self.fetch_image_or_nil(url)
|
|
return nil if url.blank?
|
|
|
|
response = HttpFetcher.fetch(url)
|
|
return nil unless allowed_image_content_type?(response.content_type)
|
|
|
|
response.body
|
|
rescue HttpFetcher::FetchTimeout
|
|
raise
|
|
rescue HttpFetcher::FetchFailed
|
|
nil
|
|
end
|
|
|
|
def self.fetch_image!(url)
|
|
response = HttpFetcher.fetch(url)
|
|
unless allowed_image_content_type?(response.content_type)
|
|
raise GenerationFailed, 'サムネール画像が見つかりませんでした.'
|
|
end
|
|
|
|
response.body
|
|
rescue HttpFetcher::FetchTimeout
|
|
raise
|
|
rescue HttpFetcher::ResponseTooLarge
|
|
raise
|
|
rescue HttpFetcher::FetchFailed
|
|
raise GenerationFailed, 'サムネール画像を取得できませんでした.'
|
|
end
|
|
|
|
def self.niconico_thumbnail_url(uri)
|
|
video_id = KnownSiteExtractor.niconico_video_id(uri)
|
|
return nil if video_id.blank?
|
|
|
|
response = HttpFetcher.fetch("https://ext.nicovideo.jp/api/getthumbinfo/#{ video_id }",
|
|
max_bytes: NICONICO_XML_MAX_BYTES)
|
|
xml = Nokogiri::XML(response.body)
|
|
return nil unless xml.at_xpath('/nicovideo_thumb_response/@status')&.value == 'ok'
|
|
|
|
xml.at_xpath('//thumbnail_url')&.text&.strip.presence
|
|
rescue HttpFetcher::FetchFailed, HttpFetcher::FetchTimeout => e
|
|
Rails.logger.info("preview_niconico_getthumbinfo_fallback #{ { url: uri.to_s,
|
|
video_id:,
|
|
error: e.class.name,
|
|
message: e.message }.to_json }")
|
|
nil
|
|
rescue Nokogiri::XML::SyntaxError => e
|
|
Rails.logger.info("preview_niconico_getthumbinfo_fallback #{ { url: uri.to_s,
|
|
video_id:,
|
|
error: e.class.name,
|
|
message: e.message }.to_json }")
|
|
nil
|
|
end
|
|
|
|
def self.allowed_image_content_type?(content_type)
|
|
mime_type = content_type.to_s.split(';', 2).first.downcase.strip
|
|
ALLOWED_IMAGE_CONTENT_TYPES.include?(mime_type)
|
|
end
|
|
|
|
private_class_method :fetch_image_or_nil, :fetch_image!,
|
|
:niconico_thumbnail_url,
|
|
:allowed_image_content_type?
|
|
end
|
|
end
|