class ArtistUrl < ApplicationRecord before_validation :initialize_normalized_url, on: :create before_validation :normalize validates :url, presence: true, uniqueness: { scope: :artist_id } validate :validate_url_format belongs_to :artist, :touch => true scope :url_matches, ->(url) { url_attribute_matches(:url, url) } scope :normalized_url_matches, ->(url) { url_attribute_matches(:normalized_url, url) } scope :active, -> { where(is_active: true) } def self.parse_prefix(url) prefix, url = url.match(/\A(-)?(.*)/)[1, 2] is_active = prefix.nil? [is_active, url] end def self.normalize(url) if url.nil? nil else url = url.sub(%r!^https://!, "http://") url = url.sub(%r!^http://blog-imgs-\d+\.fc2!, "http://blog.fc2") url = url.sub(%r!^http://blog-imgs-\d+-\w+\.fc2!, "http://blog.fc2") url = url.sub(%r!^http://blog\d*\.fc2\.com/(?:\w/){,3}(\w+)!, "http://\\1.blog.fc2.com") url = url.sub(%r!^http://pictures.hentai-foundry.com//!, "http://pictures.hentai-foundry.com/") # the strategy won't always work for twitter because it looks for a status url = url.downcase if url =~ %r!^https?://(?:mobile\.)?twitter\.com! url = Sources::Strategies.find(url).normalize_for_artist_finder # XXX the Pixiv strategy should implement normalize_for_artist_finder and return the correct url directly. url = url.sub(%r!\Ahttps?://www\.pixiv\.net/(?:en/)?users/(\d+)\z!i, 'https://www.pixiv.net/member.php?id=\1') url = url.gsub(/\/+\Z/, "") url = url.gsub(%r!^https://!, "http://") url + "/" end end def self.search(params = {}) q = search_attributes(params, :id, :created_at, :updated_at, :url, :normalized_url, :is_active, :artist) q = q.url_matches(params[:url_matches]) q = q.normalized_url_matches(params[:normalized_url_matches]) case params[:order] when /\A(id|artist_id|url|normalized_url|is_active|created_at|updated_at)(?:_(asc|desc))?\z/i dir = $2 || :desc q = q.order($1 => dir).order(id: :desc) else q = q.apply_default_order(params) end q end def self.url_attribute_matches(attr, url) if url.blank? all elsif url =~ %r!\A/(.*)/\z! where_regex(attr, $1) elsif url.include?("*") where_ilike(attr, url) else where(attr => normalize(url)) end end def domain uri = Addressable::URI.parse(normalized_url) uri.domain end def site_name source = Sources::Strategies.find(normalized_url) source.site_name end # A secondary URL is an artist URL that we don't normally want to display, # usually because it's redundant with the primary profile URL. def secondary_url? case url when %r!pixiv\.net/stacc!i true when %r!pixiv\.net/fanbox!i true when %r!twitter\.com/intent!i true when %r!lohas\.nicoseiga\.jp!i true when %r!(?:www|com|dic)\.nicovideo\.jp!i true when %r!pawoo\.net/web/accounts!i true when %r!www\.artstation\.com!i true else false end end # The sort order of sites in artist URL lists. def priority sites = %w[ Pixiv Twitter ArtStation Deviant\ Art Nico\ Seiga Nijie pawoo.net Pixiv\ Fanbox Pixiv\ Sketch Tinami Tumblr Booth.pm Facebook Fantia FC2 Gumroad Instagram Lofter Patreon Privatter Skeb Weibo Youtube Circle.ms DLSite Melonbooks Toranoana ] sites.index(site_name) || 1000 end def normalize # Perform some normalization with Addressable on the URL itself # - Converts scheme and hostname to downcase # - Converts unicode hostname to Punycode uri = Addressable::URI.parse(url) uri.site = uri.normalized_site self.url = uri.to_s self.normalized_url = self.class.normalize(url) rescue Addressable::URI::InvalidURIError # Don't bother normalizing the URL if there is errors end def initialize_normalized_url self.normalized_url = url end def to_s if is_active? url else "-#{url}" end end def validate_scheme(uri) errors.add(:url, "'#{uri}' must begin with http:// or https:// ") unless uri.scheme.in?(%w[http https]) end def validate_hostname(uri) errors.add(:url, "'#{uri}' has a hostname '#{uri.host}' that does not contain a dot") unless uri.host&.include?('.') end def validate_url_format uri = Addressable::URI.parse(url) validate_scheme(uri) validate_hostname(uri) rescue Addressable::URI::InvalidURIError => error errors.add(:url, "'#{uri}' is malformed: #{error}") end def self.available_includes [:artist] end end