2024-02-25 12:15:55 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-06-14 22:27:53 -04:00
|
|
|
class Artist < ApplicationRecord
|
2016-10-10 06:24:49 -04:00
|
|
|
class RevertError < Exception ; end
|
|
|
|
|
2018-10-04 20:37:18 -04:00
|
|
|
attr_accessor :url_string_changed
|
2018-11-14 16:54:51 -05:00
|
|
|
array_attribute :other_names
|
2018-10-04 18:38:44 -04:00
|
|
|
|
2019-09-04 08:30:37 -04:00
|
|
|
belongs_to_creator
|
2024-08-03 17:15:26 -04:00
|
|
|
before_validation :normalize_name, unless: :destroyed?
|
|
|
|
before_validation :normalize_other_names, unless: :destroyed?
|
|
|
|
before_validation :validate_protected_properties_not_changed, if: :dnp_restricted?
|
2023-08-01 13:32:24 -04:00
|
|
|
validate :validate_user_can_edit
|
2024-01-14 15:01:01 -05:00
|
|
|
validate :wiki_page_not_locked
|
2019-06-27 11:33:02 -04:00
|
|
|
validate :user_not_limited
|
2023-06-17 09:18:16 -04:00
|
|
|
validates :name, tag_name: true, uniqueness: true, if: :name_changed?
|
2022-10-30 13:16:29 -04:00
|
|
|
validates :name, :group_name, length: { maximum: 100 }
|
2024-07-20 14:45:36 -04:00
|
|
|
before_destroy :log_destroy
|
2022-03-21 15:21:55 -04:00
|
|
|
after_save :log_changes
|
2010-02-15 13:59:58 -05:00
|
|
|
after_save :create_version
|
2013-10-26 00:13:09 -04:00
|
|
|
after_save :categorize_tag
|
2017-04-18 18:57:20 -04:00
|
|
|
after_save :update_wiki
|
2021-01-17 14:33:21 -05:00
|
|
|
after_save :propagate_locked, if: :should_propagate_locked
|
2018-10-04 20:37:18 -04:00
|
|
|
after_save :clear_url_string_changed
|
2024-09-07 16:34:25 -04:00
|
|
|
after_save :update_posts_index, if: :saved_change_to_linked_user_id?
|
2019-09-04 08:30:37 -04:00
|
|
|
|
2024-07-20 14:45:36 -04:00
|
|
|
has_many :members, class_name: "Artist", foreign_key: "group_name", primary_key: "name"
|
|
|
|
has_many :urls, dependent: :destroy, class_name: "ArtistUrl", autosave: true
|
|
|
|
has_many :versions, -> {order("artist_versions.id ASC")}, class_name: "ArtistVersion"
|
|
|
|
has_one :wiki_page, foreign_key: "title", primary_key: "name"
|
|
|
|
has_one :tag_alias, foreign_key: "antecedent_name", primary_key: "name"
|
|
|
|
has_one :tag, foreign_key: "name", primary_key: "name"
|
2024-08-03 17:15:26 -04:00
|
|
|
has_one :avoid_posting, -> { active }
|
|
|
|
has_one :inactive_dnp, -> { deleted }, class_name: "AvoidPosting"
|
2020-03-02 18:34:33 -05:00
|
|
|
belongs_to :linked_user, class_name: "User", optional: true
|
2018-04-02 13:51:26 -04:00
|
|
|
attribute :notes, :string
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2021-01-17 14:33:21 -05:00
|
|
|
def log_changes
|
2022-03-21 15:21:55 -04:00
|
|
|
if saved_change_to_name? && !previously_new_record?
|
2023-03-23 15:14:17 -04:00
|
|
|
ModAction.log(:artist_page_rename, { new_name: name, old_name: name_before_last_save })
|
2021-01-17 14:33:21 -05:00
|
|
|
end
|
2022-03-21 15:21:55 -04:00
|
|
|
if saved_change_to_is_locked?
|
2022-03-06 09:38:08 -05:00
|
|
|
ModAction.log(is_locked ? :artist_page_lock : :artist_page_unlock, { artist_page: id })
|
|
|
|
end
|
2022-03-21 15:21:55 -04:00
|
|
|
if saved_change_to_linked_user_id?
|
2022-03-06 09:38:08 -05:00
|
|
|
# FIXME: This should also go in artist_versions
|
|
|
|
if linked_user_id.present?
|
|
|
|
ModAction.log(:artist_user_linked, { artist_page: id, user_id: linked_user_id })
|
|
|
|
else
|
2023-03-23 15:14:17 -04:00
|
|
|
ModAction.log(:artist_user_unlinked, { artist_page: id, user_id: linked_user_id_before_last_save })
|
2022-03-06 09:38:08 -05:00
|
|
|
end
|
2021-01-17 14:33:21 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-02-15 13:59:58 -05:00
|
|
|
module UrlMethods
|
2012-05-11 17:53:11 -04:00
|
|
|
extend ActiveSupport::Concern
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2019-09-05 03:26:18 -04:00
|
|
|
MAX_URLS_PER_ARTIST = 25
|
2010-02-15 13:59:58 -05:00
|
|
|
module ClassMethods
|
2017-12-05 21:59:53 -05:00
|
|
|
# Subdomains are automatically included. e.g., "twitter.com" matches "www.twitter.com",
|
|
|
|
# "mobile.twitter.com" and any other subdomain of "twitter.com".
|
|
|
|
SITE_BLACKLIST = [
|
2017-12-05 23:50:47 -05:00
|
|
|
"artstation.com/artist", # http://www.artstation.com/artist/serafleur/
|
|
|
|
"www.artstation.com", # http://www.artstation.com/serafleur/
|
2017-12-26 15:05:41 -05:00
|
|
|
%r!cdn[ab]?\.artstation\.com/p/assets/images/images!i, # https://cdna.artstation.com/p/assets/images/images/001/658/068/large/yang-waterkuma-b402.jpg?1450269769
|
2017-12-05 23:50:47 -05:00
|
|
|
"ask.fm", # http://ask.fm/mikuroko_396
|
|
|
|
"bcyimg.com",
|
|
|
|
"bcyimg.com/drawer", # https://img9.bcyimg.com/drawer/32360/post/178vu/46229ec06e8111e79558c1b725ebc9e6.jpg
|
|
|
|
"bcy.net",
|
|
|
|
"bcy.net/illust/detail", # https://bcy.net/illust/detail/32360/1374683
|
|
|
|
"bcy.net/u", # http://bcy.net/u/1390261
|
|
|
|
"behance.net", # "https://www.behance.net/webang111
|
|
|
|
"booru.org",
|
|
|
|
"booru.org/drawfriends", # http://img.booru.org/drawfriends//images/36/de65da5f588b76bc1d9de8af976b540e2dff17e2.jpg
|
|
|
|
"donmai.us",
|
|
|
|
"donmai.us/users", # http://danbooru.donmai.us/users/507162/
|
|
|
|
"derpibooru.org",
|
|
|
|
"derpibooru.org/tags", # https://derpibooru.org/tags/artist-colon-checkerboardazn
|
2018-07-27 19:35:23 -04:00
|
|
|
"deviantart.com",
|
2017-12-05 21:59:53 -05:00
|
|
|
"deviantart.net",
|
2017-12-05 23:50:47 -05:00
|
|
|
"dlsite.com",
|
|
|
|
"doujinshi.org",
|
|
|
|
"doujinshi.org/browse/circle", # http://www.doujinshi.org/browse/circle/65368/
|
|
|
|
"doujinshi.org/browse/author", # http://www.doujinshi.org/browse/author/979/23/
|
|
|
|
"doujinshi.mugimugi.org",
|
|
|
|
"doujinshi.mugimugi.org/browse/author", # http://doujinshi.mugimugi.org/browse/author/3029/
|
|
|
|
"doujinshi.mugimugi.org/browse/circle", # http://doujinshi.mugimugi.org/browse/circle/7210/
|
|
|
|
"drawcrowd.net", # https://drawcrowd.com/agussw
|
|
|
|
"drawr.net", # http://drawr.net/matsu310
|
|
|
|
"dropbox.com",
|
|
|
|
"dropbox.com/sh", # https://www.dropbox.com/sh/gz9okupqycr2vj2/GHt_oHDKsR
|
|
|
|
"dropbox.com/u", # http://dl.dropbox.com/u/76682289/daitoHP-WP/pict/
|
|
|
|
"e-hentai.org", # https://e-hentai.org/tag/artist:spirale
|
|
|
|
"e621.net",
|
|
|
|
"e621.net/post/index/1", # https://e621.net/post/index/1/spirale
|
|
|
|
"enty.jp", # https://enty.jp/aizawachihiro888
|
|
|
|
"enty.jp/users", # https://enty.jp/users/3766
|
|
|
|
"facebook.com", # https://www.facebook.com/LuutenantsLoot
|
|
|
|
"fantia.jp", # http://fantia.jp/no100
|
|
|
|
"fantia.jp/fanclubs", # https://fantia.jp/fanclubs/1711
|
|
|
|
"fav.me", # http://fav.me/d9y1njg
|
|
|
|
/blog-imgs-\d+(?:-origin)?\.fc2\.com/i,
|
|
|
|
"furaffinity.net",
|
|
|
|
"furaffinity.net/user", # http://www.furaffinity.net/user/achthenuts
|
|
|
|
"gelbooru.com", # http://gelbooru.com/index.php?page=account&s=profile&uname=junou
|
|
|
|
"inkbunny.net", # https://inkbunny.net/achthenuts
|
|
|
|
"plus.google.com", # https://plus.google.com/111509637967078773143/posts
|
|
|
|
"hentai-foundry.com",
|
|
|
|
"hentai-foundry.com/pictures/user", # http://www.hentai-foundry.com/pictures/user/aaaninja/
|
|
|
|
"hentai-foundry.com/user", # http://www.hentai-foundry.com/user/aaaninja/profile
|
|
|
|
%r!pictures\.hentai-foundry\.com(?:/\w)?!i, # http://pictures.hentai-foundry.com/a/aaaninja/
|
|
|
|
"i.imgur.com", # http://i.imgur.com/Ic9q3.jpg
|
|
|
|
"instagram.com", # http://www.instagram.com/serafleur.art/
|
|
|
|
"iwara.tv",
|
|
|
|
"iwara.tv/users", # http://ecchi.iwara.tv/users/marumega
|
|
|
|
"kym-cdn.com",
|
|
|
|
"livedoor.blogimg.jp",
|
|
|
|
"monappy.jp",
|
|
|
|
"monappy.jp/u", # https://monappy.jp/u/abara_bone
|
|
|
|
"mstdn.jp", # https://mstdn.jp/@oneb
|
2017-12-05 21:59:53 -05:00
|
|
|
"nicoseiga.jp",
|
2017-12-05 23:50:47 -05:00
|
|
|
"nicoseiga.jp/priv", # http://lohas.nicoseiga.jp/priv/2017365fb6cfbdf47ad26c7b6039feb218c5e2d4/1498430264/6820259
|
|
|
|
"nicovideo.jp",
|
2017-12-26 15:31:42 -05:00
|
|
|
"nicovideo.jp/user", # http://www.nicovideo.jp/user/317609
|
2017-12-05 23:50:47 -05:00
|
|
|
"nicovideo.jp/user/illust", # http://seiga.nicovideo.jp/user/illust/29075429
|
|
|
|
"nijie.info", # http://nijie.info/members.php?id=15235
|
2018-03-03 12:26:15 -05:00
|
|
|
%r!nijie\.info/nijie_picture!i, # http://pic03.nijie.info/nijie_picture/32243_20150609224803_0.png
|
2017-12-05 23:50:47 -05:00
|
|
|
"patreon.com", # http://patreon.com/serafleur
|
|
|
|
"pawoo.net", # https://pawoo.net/@148nasuka
|
|
|
|
"pawoo.net/web/accounts", # https://pawoo.net/web/accounts/228341
|
|
|
|
"picarto.tv", # https://picarto.tv/CheckerBoardAZN
|
|
|
|
"picarto.tv/live", # https://www.picarto.tv/live/channel.php?watch=aaaninja
|
|
|
|
"pictaram.com", # http://www.pictaram.com/user/5ish/3048385011/1350040096769940245_3048385011
|
|
|
|
"pinterest.com", # http://www.pinterest.com/alexandernanitc/
|
|
|
|
"pixiv.cc", # http://pixiv.cc/0123456789/
|
|
|
|
"pixiv.net", # https://www.pixiv.net/member.php?id=10442390
|
|
|
|
"pixiv.net/stacc", # https://www.pixiv.net/stacc/aaaninja2013
|
|
|
|
"i.pximg.net",
|
|
|
|
"plurk.com", # http://www.plurk.com/a1amorea1a1
|
|
|
|
"privatter.net",
|
|
|
|
"privatter.net/u", # http://privatter.net/u/saaaatonaaaa
|
|
|
|
"rule34.paheal.net",
|
|
|
|
"rule34.paheal.net/post/list", # http://rule34.paheal.net/post/list/Reach025/
|
|
|
|
"sankakucomplex.com", # https://chan.sankakucomplex.com/?tags=user%3ASubridet
|
|
|
|
"society6.com", # http://society6.com/serafleur/
|
|
|
|
"tinami.com",
|
|
|
|
"tinami.com/creator/profile", # http://www.tinami.com/creator/profile/29024
|
2017-12-05 21:59:53 -05:00
|
|
|
"data.tumblr.com",
|
|
|
|
/\d+\.media\.tumblr\.com/i,
|
2017-12-05 23:50:47 -05:00
|
|
|
"twipple.jp",
|
|
|
|
"twipple.jp/user", # http://p.twipple.jp/user/Type10TK
|
|
|
|
"twitch.tv", # https://www.twitch.tv/5ish
|
|
|
|
"twitpic.com",
|
|
|
|
"twitpic.com/photos", # http://twitpic.com/photos/Type10TK
|
|
|
|
"twitter.com", # https://twitter.com/akkij0358
|
2018-01-24 00:07:21 -05:00
|
|
|
"twitter.com/i/web/status", # https://twitter.com/i/web/status/943446161586733056
|
2018-01-26 19:30:18 -05:00
|
|
|
"twimg.com/media", # https://pbs.twimg.com/media/DUUUdD5VMAEuURz.jpg:orig
|
2017-12-05 23:50:47 -05:00
|
|
|
"ustream.tv",
|
|
|
|
"ustream.tv/channel", # http://www.ustream.tv/channel/633b
|
|
|
|
"ustream.tv/user", # http://www.ustream.tv/user/kazaputi
|
|
|
|
"vk.com", # https://vk.com/id425850679
|
|
|
|
"weibo.com", # http://www.weibo.com/5536681649
|
|
|
|
"wp.com",
|
|
|
|
"yande.re",
|
|
|
|
"youtube.com",
|
|
|
|
"youtube.com/c", # https://www.youtube.com/c/serafleurArt
|
|
|
|
"youtube.com/channel", # https://www.youtube.com/channel/UCfrCa2Y6VulwHD3eNd3HBRA
|
|
|
|
"youtube.com/user", # https://www.youtube.com/user/148nasuka
|
|
|
|
"youtu.be", # http://youtu.be/gibeLKKRT-0
|
2017-12-05 21:59:53 -05:00
|
|
|
]
|
|
|
|
|
|
|
|
SITE_BLACKLIST_REGEXP = Regexp.union(SITE_BLACKLIST.map do |domain|
|
|
|
|
domain = Regexp.escape(domain) if domain.is_a?(String)
|
|
|
|
%r!\Ahttps?://(?:[a-zA-Z0-9_-]+\.)*#{domain}/\z!i
|
|
|
|
end)
|
|
|
|
|
2021-06-15 07:48:40 -04:00
|
|
|
# Looks at the url and goes one directory down if no results are found.
|
|
|
|
# Should the domain of the url match one of the domains in the site blacklist stop immediately
|
|
|
|
# http://www.explame.com/cool/page/ => http://www.explame.com/cool/ => http://www.explame.com/
|
|
|
|
# This was presumably made so you only get specific matches for user pages and not some unrelated
|
|
|
|
# results when that specific user doesn't exist
|
2018-09-21 22:03:24 -04:00
|
|
|
def find_artists(url)
|
2010-02-15 13:59:58 -05:00
|
|
|
url = ArtistUrl.normalize(url)
|
|
|
|
artists = []
|
2021-06-15 07:48:40 -04:00
|
|
|
while artists.empty? && url.length > 10
|
2010-02-15 13:59:58 -05:00
|
|
|
u = url.sub(/\/+$/, "") + "/"
|
2023-08-01 13:32:24 -04:00
|
|
|
u = u.to_escaped_for_sql_like.gsub("*", "%") + "%"
|
2024-07-20 14:45:36 -04:00
|
|
|
artists += Artist.joins(:urls).where(["artist_urls.normalized_url ILIKE ? ESCAPE E'\\\\'", u]).limit(10).order("artists.name").all
|
2010-02-15 13:59:58 -05:00
|
|
|
url = File.dirname(url) + "/"
|
2017-12-05 21:59:53 -05:00
|
|
|
|
|
|
|
break if url =~ SITE_BLACKLIST_REGEXP
|
2010-02-15 13:59:58 -05:00
|
|
|
end
|
|
|
|
|
2018-09-08 17:03:10 -04:00
|
|
|
where(id: artists.uniq(&:name).take(20))
|
2010-02-15 13:59:58 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-14 14:37:42 -04:00
|
|
|
def sorted_urls
|
2021-06-15 09:12:44 -04:00
|
|
|
urls.sort {|a, b| b.priority <=> a.priority}
|
2017-09-14 14:37:42 -04:00
|
|
|
end
|
|
|
|
|
2016-08-28 14:12:35 -04:00
|
|
|
def url_array
|
2018-11-14 15:22:06 -05:00
|
|
|
urls.map(&:to_s).sort
|
2016-08-28 14:12:35 -04:00
|
|
|
end
|
|
|
|
|
2018-10-04 18:38:44 -04:00
|
|
|
def url_string
|
2018-11-14 15:22:06 -05:00
|
|
|
url_array.join("\n")
|
2018-10-04 18:38:44 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def url_string=(string)
|
2023-06-18 10:13:08 -04:00
|
|
|
# FIXME: This is a hack. Setting an association directly immediatly updates without regard for the parents validity.
|
|
|
|
# As a consequence, removing urls always works. This does not create a new ArtistVersion.
|
|
|
|
# This fix isn't great but it's the best I came up with without rather large changes.
|
|
|
|
return unless valid?
|
|
|
|
|
2018-10-04 20:37:18 -04:00
|
|
|
url_string_was = url_string
|
2018-10-04 18:38:44 -04:00
|
|
|
|
|
|
|
self.urls = string.to_s.scan(/[^[:space:]]+/).map do |url|
|
|
|
|
is_active, url = ArtistUrl.parse_prefix(url)
|
|
|
|
self.urls.find_or_initialize_by(url: url, is_active: is_active)
|
2022-12-21 11:20:16 -05:00
|
|
|
end.uniq(&:url).first(MAX_URLS_PER_ARTIST)
|
2018-10-04 20:37:18 -04:00
|
|
|
|
|
|
|
self.url_string_changed = (url_string_was != url_string)
|
2018-10-04 18:38:44 -04:00
|
|
|
end
|
|
|
|
|
2018-10-04 20:37:18 -04:00
|
|
|
def clear_url_string_changed
|
|
|
|
self.url_string_changed = false
|
2010-02-15 13:59:58 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2021-10-24 07:31:58 -04:00
|
|
|
# Some sites do not include the file extension directly in their path, or it's not useable for the regex
|
|
|
|
DOMAINS_COUNT_BLACKLIST = [
|
|
|
|
"twimg.com", # https://pbs.twimg.com/media/E627JTbVcAI94NW?format=jpg&name=orig
|
|
|
|
"wixmp.com", # https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/885a6dec-35b8-456f-a409-43b214729c22/desps0r-87920cf6-c246-4b04-8144-06f0ed108aaa.jpg/v1/fill/w_980,h_735,q_75,strp/3136__tortie_cat_by_cryptid_creations_desps0r-fullview.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9NzM1IiwicGF0aCI6IlwvZlwvODg1YTZkZWMtMzViOC00NTZmLWE0MDktNDNiMjE0NzI5YzIyXC9kZXNwczByLTg3OTIwY2Y2LWMyNDYtNGIwNC04MTQ0LTA2ZjBlZDEwOGFhYS5qcGciLCJ3aWR0aCI6Ijw9OTgwIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.qOCiaQZhVwya39kW1hxEY6ufK-0fYw-cJgtBG8wOpLo
|
|
|
|
"ngfiles.com", # https://art.ngfiles.com/images/224000/224977_signhereplease_razeal-s-trick.jpg?f1353472836
|
|
|
|
]
|
|
|
|
|
2021-08-14 09:11:28 -04:00
|
|
|
# Returns a count of sourced domains for the artist.
|
|
|
|
# A domain only gets counted once per post, direct image urls are filtered out.
|
2017-05-01 18:47:48 -04:00
|
|
|
def domains
|
2023-05-19 16:53:20 -04:00
|
|
|
Cache.fetch("artist-domains-#{id}", expires_in: 1.day) do
|
2021-08-13 15:59:48 -04:00
|
|
|
re = /\.(png|jpeg|jpg|webm|mp4)$/m
|
2021-08-14 09:11:28 -04:00
|
|
|
counted = Hash.new(0)
|
2023-09-16 09:30:28 -04:00
|
|
|
sources = Post.tag_match(name, resolve_aliases: false).limit(100).pluck(:source).each do |source_string|
|
2021-08-14 09:11:28 -04:00
|
|
|
sources = source_string.split("\n")
|
|
|
|
# try to filter out direct file urls
|
|
|
|
domains = sources.filter {|s| !re.match?(s) }.map do |x|
|
|
|
|
Addressable::URI.parse(x).domain
|
2017-05-12 17:50:37 -04:00
|
|
|
rescue Addressable::URI::InvalidURIError
|
|
|
|
nil
|
2021-08-14 09:11:28 -04:00
|
|
|
end.compact.uniq
|
2021-10-24 07:31:58 -04:00
|
|
|
domains = domains.filter { |d| DOMAINS_COUNT_BLACKLIST.exclude?(d) }
|
2021-08-14 09:11:28 -04:00
|
|
|
domains.each {|domain| counted[domain] += 1}
|
2021-08-13 15:59:48 -04:00
|
|
|
end
|
|
|
|
counted.sort {|a, b| b[1] <=> a[1]}
|
2017-05-12 17:50:37 -04:00
|
|
|
end
|
2017-05-01 18:47:48 -04:00
|
|
|
end
|
2010-02-15 13:59:58 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
module NameMethods
|
2012-05-11 17:53:11 -04:00
|
|
|
extend ActiveSupport::Concern
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2022-12-21 11:20:16 -05:00
|
|
|
MAX_OTHER_NAMES_PER_ARTIST = 25
|
2010-02-15 13:59:58 -05:00
|
|
|
module ClassMethods
|
|
|
|
def normalize_name(name)
|
2022-04-09 08:23:12 -04:00
|
|
|
name.to_s.downcase.strip.gsub(/ /, '_').to_s
|
2010-02-15 13:59:58 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def normalize_name
|
|
|
|
self.name = Artist.normalize_name(name)
|
2013-02-20 13:58:37 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2014-10-13 17:44:40 -04:00
|
|
|
def pretty_name
|
|
|
|
name.tr("_", " ")
|
|
|
|
end
|
|
|
|
|
2018-11-14 16:54:51 -05:00
|
|
|
def normalize_other_names
|
2018-12-27 16:03:11 -05:00
|
|
|
self.other_names = other_names.map { |x| Artist.normalize_name(x) }.uniq
|
|
|
|
self.other_names -= [name]
|
2022-12-21 11:20:16 -05:00
|
|
|
self.other_names = other_names.first(MAX_OTHER_NAMES_PER_ARTIST).map { |other_name| other_name.first(100) }
|
2010-02-15 13:59:58 -05:00
|
|
|
end
|
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-02-15 13:59:58 -05:00
|
|
|
module GroupMethods
|
|
|
|
def member_names
|
2020-04-25 08:24:25 -04:00
|
|
|
members.limit(25).map(&:name).join(", ")
|
2010-02-15 13:59:58 -05:00
|
|
|
end
|
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-02-15 13:59:58 -05:00
|
|
|
module VersionMethods
|
2013-12-16 13:45:47 -05:00
|
|
|
def create_version(force=false)
|
2025-01-23 17:24:39 -05:00
|
|
|
if saved_change_to_name? || url_string_changed || saved_change_to_other_names? || saved_change_to_group_name? || saved_change_to_banner_id? || saved_change_to_avatar_id? || saved_change_to_notes? || force
|
2020-03-02 18:34:33 -05:00
|
|
|
create_new_version
|
2013-12-16 13:45:47 -05:00
|
|
|
end
|
2010-02-15 13:59:58 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2014-03-18 16:03:38 -04:00
|
|
|
def create_new_version
|
|
|
|
ArtistVersion.create(
|
2024-07-20 14:45:36 -04:00
|
|
|
artist_id: id,
|
|
|
|
name: name,
|
|
|
|
updater_id: CurrentUser.id,
|
|
|
|
updater_ip_addr: CurrentUser.ip_addr,
|
|
|
|
urls: url_array,
|
|
|
|
is_active: is_active,
|
|
|
|
other_names: other_names,
|
|
|
|
group_name: group_name,
|
2025-01-23 17:24:39 -05:00
|
|
|
banner_id: banner_id,
|
|
|
|
avatar_id: avatar_id,
|
2024-07-20 14:45:36 -04:00
|
|
|
notes_changed: saved_change_to_notes?
|
2014-03-18 16:03:38 -04:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2010-10-08 18:42:26 -04:00
|
|
|
def revert_to!(version)
|
2016-10-10 06:24:49 -04:00
|
|
|
if id != version.artist_id
|
|
|
|
raise RevertError.new("You cannot revert to a previous version of another artist.")
|
|
|
|
end
|
|
|
|
|
2010-02-15 13:59:58 -05:00
|
|
|
self.name = version.name
|
2018-11-14 15:22:06 -05:00
|
|
|
self.url_string = version.urls.join("\n")
|
2018-11-14 16:54:51 -05:00
|
|
|
self.other_names = version.other_names
|
2010-02-15 13:59:58 -05:00
|
|
|
self.group_name = version.group_name
|
2025-01-23 17:24:39 -05:00
|
|
|
self.banner_id = version.banner_id
|
|
|
|
self.avatar_id = version.avatar_id
|
2013-03-19 08:10:10 -04:00
|
|
|
save
|
2010-02-15 13:59:58 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-10-08 18:42:26 -04:00
|
|
|
module NoteMethods
|
2017-04-18 18:57:20 -04:00
|
|
|
extend ActiveSupport::Concern
|
|
|
|
|
2010-10-08 18:42:26 -04:00
|
|
|
def notes
|
2017-04-18 18:57:20 -04:00
|
|
|
@notes || wiki_page.try(:body)
|
|
|
|
end
|
|
|
|
|
|
|
|
def notes=(text)
|
2021-08-06 12:59:00 -04:00
|
|
|
return if wiki_page.blank? && text.empty?
|
|
|
|
return if notes == text
|
|
|
|
|
|
|
|
notes_will_change!
|
|
|
|
@notes = text
|
2010-10-08 18:42:26 -04:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2017-04-18 18:57:20 -04:00
|
|
|
def reload(options = nil)
|
|
|
|
if instance_variable_defined?(:@notes)
|
|
|
|
remove_instance_variable(:@notes)
|
2013-10-26 00:08:37 -04:00
|
|
|
end
|
2017-04-18 18:57:20 -04:00
|
|
|
|
|
|
|
super
|
|
|
|
end
|
|
|
|
|
|
|
|
def notes_changed?
|
|
|
|
attribute_changed?("notes")
|
|
|
|
end
|
|
|
|
|
|
|
|
def notes_will_change!
|
|
|
|
attribute_will_change!("notes")
|
|
|
|
end
|
|
|
|
|
|
|
|
def update_wiki
|
2024-04-09 13:50:33 -04:00
|
|
|
if persisted? && saved_change_to_name? && attribute_before_last_save("name").present? && WikiPage.titled(attribute_before_last_save("name"))
|
2017-04-18 18:57:20 -04:00
|
|
|
# we're renaming the artist, so rename the corresponding wiki page
|
2024-04-09 13:50:33 -04:00
|
|
|
old_page = WikiPage.titled(name_before_last_save)
|
2023-02-24 18:03:32 -05:00
|
|
|
if wiki_page.nil?
|
2017-04-18 18:57:20 -04:00
|
|
|
# a wiki page doesn't already exist for the new name, so rename the old one
|
2023-02-24 18:03:32 -05:00
|
|
|
old_page.update(title: name, body: @notes || old_page.body)
|
2013-10-26 00:08:37 -04:00
|
|
|
end
|
2017-04-18 18:57:20 -04:00
|
|
|
elsif wiki_page.nil?
|
|
|
|
# if there are any notes, we need to create a new wiki page
|
|
|
|
if @notes.present?
|
2023-02-24 18:03:32 -05:00
|
|
|
create_wiki_page(body: @notes, title: name)
|
2013-12-16 13:45:47 -05:00
|
|
|
end
|
2018-01-14 23:10:44 -05:00
|
|
|
elsif (!@notes.nil? && (wiki_page.body != @notes)) || wiki_page.title != name
|
2017-04-18 18:57:20 -04:00
|
|
|
# if anything changed, we need to update the wiki page
|
|
|
|
wiki_page.body = @notes unless @notes.nil?
|
|
|
|
wiki_page.title = name
|
|
|
|
wiki_page.save
|
2010-10-08 18:42:26 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-10-08 18:42:26 -04:00
|
|
|
module TagMethods
|
2023-02-03 12:28:17 -05:00
|
|
|
def category_id
|
2014-10-13 17:44:40 -04:00
|
|
|
Tag.category_for(name)
|
|
|
|
end
|
|
|
|
|
2013-08-07 16:00:25 -04:00
|
|
|
def categorize_tag
|
2018-04-02 13:51:26 -04:00
|
|
|
if new_record? || saved_change_to_name?
|
2013-10-26 00:13:09 -04:00
|
|
|
Tag.find_or_create_by_name("artist:#{name}")
|
|
|
|
end
|
2013-08-07 16:00:25 -04:00
|
|
|
end
|
2010-10-08 18:42:26 -04:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2021-01-17 14:33:21 -05:00
|
|
|
module LockMethods
|
|
|
|
def propagate_locked
|
|
|
|
if wiki_page.present?
|
|
|
|
wiki_page.update_column(:is_locked, is_locked?)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def should_propagate_locked
|
|
|
|
saved_change_to_is_locked?
|
|
|
|
end
|
|
|
|
|
2023-08-01 13:32:24 -04:00
|
|
|
def validate_user_can_edit
|
2021-01-17 14:33:21 -05:00
|
|
|
return if CurrentUser.is_janitor?
|
|
|
|
|
|
|
|
if is_locked?
|
|
|
|
errors.add(:base, "Artist is locked")
|
|
|
|
throw :abort
|
|
|
|
end
|
|
|
|
end
|
2024-01-14 15:01:01 -05:00
|
|
|
|
|
|
|
def wiki_page_not_locked
|
|
|
|
return if CurrentUser.is_janitor?
|
|
|
|
|
|
|
|
if @notes.present? && is_note_locked? && wiki_page&.body != @notes
|
|
|
|
errors.add(:base, "Wiki page is locked")
|
|
|
|
throw :abort
|
|
|
|
end
|
|
|
|
end
|
2021-01-17 14:33:21 -05:00
|
|
|
end
|
|
|
|
|
2013-01-08 15:53:21 -05:00
|
|
|
module SearchMethods
|
2024-08-02 10:42:32 -04:00
|
|
|
def named(name)
|
|
|
|
find_by(name: normalize_name(name))
|
|
|
|
end
|
|
|
|
|
2018-11-14 16:54:51 -05:00
|
|
|
def any_other_name_matches(regex)
|
|
|
|
where(id: Artist.from("unnest(other_names) AS other_name").where("other_name ~ ?", regex))
|
|
|
|
end
|
|
|
|
|
|
|
|
def any_other_name_like(name)
|
|
|
|
where(id: Artist.from("unnest(other_names) AS other_name").where("other_name LIKE ?", name.to_escaped_for_sql_like))
|
|
|
|
end
|
|
|
|
|
2018-09-21 19:34:39 -04:00
|
|
|
def any_name_matches(query)
|
2022-10-13 12:22:07 -04:00
|
|
|
normalized_name = normalize_name(query)
|
|
|
|
normalized_name = "*#{normalized_name}*" unless normalized_name.include?("*")
|
|
|
|
where_like(:name, normalized_name).or(any_other_name_like(normalized_name)).or(where_like(:group_name, normalized_name))
|
2013-01-10 17:45:52 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2018-09-21 22:03:24 -04:00
|
|
|
def url_matches(query)
|
2022-10-13 12:22:07 -04:00
|
|
|
if query =~ %r!\Ahttps?://!i
|
2018-09-21 22:03:24 -04:00
|
|
|
find_artists(query)
|
|
|
|
else
|
2022-10-13 12:22:07 -04:00
|
|
|
where(id: ArtistUrl.search(url_matches: query).select(:artist_id))
|
2018-09-21 22:03:24 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-12-16 13:05:35 -05:00
|
|
|
def any_name_or_url_matches(query)
|
|
|
|
if query =~ %r!\Ahttps?://!i
|
|
|
|
url_matches(query)
|
|
|
|
else
|
|
|
|
any_name_matches(query)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-01-10 17:45:52 -05:00
|
|
|
def search(params)
|
2017-12-17 17:58:34 -05:00
|
|
|
q = super
|
2013-01-08 15:53:21 -05:00
|
|
|
|
2023-06-01 15:45:51 -04:00
|
|
|
q = q.attribute_matches(:name, params[:name])
|
|
|
|
q = q.attribute_matches(:group_name, params[:group_name])
|
2017-04-25 11:55:16 -04:00
|
|
|
|
2018-11-14 16:54:51 -05:00
|
|
|
if params[:any_other_name_like]
|
|
|
|
q = q.any_other_name_like(params[:any_other_name_like])
|
|
|
|
end
|
|
|
|
|
2017-04-25 11:55:16 -04:00
|
|
|
if params[:any_name_matches].present?
|
|
|
|
q = q.any_name_matches(params[:any_name_matches])
|
|
|
|
end
|
|
|
|
|
2018-12-16 13:05:35 -05:00
|
|
|
if params[:any_name_or_url_matches].present?
|
|
|
|
q = q.any_name_or_url_matches(params[:any_name_or_url_matches])
|
|
|
|
end
|
|
|
|
|
2017-04-25 11:55:16 -04:00
|
|
|
if params[:url_matches].present?
|
|
|
|
q = q.url_matches(params[:url_matches])
|
|
|
|
end
|
|
|
|
|
2023-08-03 16:01:53 -04:00
|
|
|
q = q.where_user(:creator_id, :creator, params)
|
2013-04-20 10:40:58 -04:00
|
|
|
|
2018-05-03 19:53:35 -04:00
|
|
|
if params[:has_tag].to_s.truthy?
|
2017-04-25 11:55:16 -04:00
|
|
|
q = q.joins(:tag).where("tags.post_count > 0")
|
2018-05-03 19:53:35 -04:00
|
|
|
elsif params[:has_tag].to_s.falsy?
|
2017-04-25 11:55:16 -04:00
|
|
|
q = q.includes(:tag).where("tags.name IS NULL OR tags.post_count <= 0").references(:tags)
|
2014-04-28 14:24:24 -04:00
|
|
|
end
|
|
|
|
|
2023-04-05 03:34:00 -04:00
|
|
|
if params[:is_linked].to_s.truthy?
|
2022-03-30 11:43:28 -04:00
|
|
|
q = q.where("linked_user_id IS NOT NULL")
|
2025-02-19 10:24:15 -05:00
|
|
|
elsif params[:is_linked].to_s.falsy?
|
|
|
|
q = q.where("linked_user_id IS NULL")
|
2022-03-30 11:43:28 -04:00
|
|
|
end
|
|
|
|
|
2018-01-28 23:46:18 -05:00
|
|
|
case params[:order]
|
|
|
|
when "name"
|
|
|
|
q = q.order("artists.name")
|
|
|
|
when "updated_at"
|
|
|
|
q = q.order("artists.updated_at desc")
|
|
|
|
when "post_count"
|
|
|
|
q = q.includes(:tag).order("tags.post_count desc nulls last").order("artists.name").references(:tags)
|
|
|
|
else
|
2023-07-07 08:32:57 -04:00
|
|
|
q = q.apply_basic_order(params)
|
2018-01-28 23:46:18 -05:00
|
|
|
end
|
|
|
|
|
2013-01-10 17:45:52 -05:00
|
|
|
q
|
2013-01-08 15:53:21 -05:00
|
|
|
end
|
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2024-08-03 17:15:26 -04:00
|
|
|
module AvoidPostingMethods
|
|
|
|
def validate_protected_properties_not_changed
|
|
|
|
errors.add(:name, "cannot be changed while the artist is on the avoid posting list") if will_save_change_to_name?
|
|
|
|
errors.add(:group_name, "cannot be changed while the artist is on the avoid posting list") if will_save_change_to_group_name?
|
|
|
|
errors.add(:other_names, "cannot be changed while the artist is on the avoid posting list") if will_save_change_to_other_names?
|
|
|
|
throw(:abort) if errors.any?
|
|
|
|
end
|
|
|
|
|
|
|
|
def is_dnp?
|
|
|
|
avoid_posting.present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def has_any_dnp?
|
|
|
|
is_dnp? || inactive_dnp.present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def dnp_restricted?
|
|
|
|
is_dnp? && !CurrentUser.can_edit_avoid_posting_entries?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
include AvoidPostingMethods
|
|
|
|
|
2010-02-15 13:59:58 -05:00
|
|
|
include UrlMethods
|
|
|
|
include NameMethods
|
|
|
|
include GroupMethods
|
|
|
|
include VersionMethods
|
2010-10-08 18:42:26 -04:00
|
|
|
include NoteMethods
|
|
|
|
include TagMethods
|
2021-01-17 14:33:21 -05:00
|
|
|
include LockMethods
|
2013-01-10 17:45:52 -05:00
|
|
|
extend SearchMethods
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2024-08-03 17:15:26 -04:00
|
|
|
# due to technical limitations (foreign keys), artists with any
|
|
|
|
# dnp entry (active or inactive) cannot be deleted
|
2013-06-05 13:09:14 -04:00
|
|
|
def deletable_by?(user)
|
2024-08-03 17:15:26 -04:00
|
|
|
!has_any_dnp? && user.is_admin?
|
2013-06-05 13:09:14 -04:00
|
|
|
end
|
2014-04-24 19:15:59 -04:00
|
|
|
|
2016-09-26 15:02:35 -04:00
|
|
|
def editable_by?(user)
|
2024-07-29 19:12:09 -04:00
|
|
|
return true if user.is_janitor?
|
|
|
|
!is_locked?
|
2016-09-26 15:02:35 -04:00
|
|
|
end
|
|
|
|
|
2019-07-19 08:01:12 -04:00
|
|
|
def user_not_limited
|
|
|
|
allowed = CurrentUser.can_artist_edit_with_reason
|
|
|
|
if allowed != true
|
|
|
|
errors.add(:base, "User #{User.throttle_reason(allowed)}.")
|
|
|
|
false
|
|
|
|
end
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2014-04-24 19:15:59 -04:00
|
|
|
def visible?
|
2022-02-19 10:54:26 -05:00
|
|
|
true
|
2014-04-24 19:15:59 -04:00
|
|
|
end
|
2024-01-14 15:01:01 -05:00
|
|
|
|
|
|
|
def is_note_locked?
|
|
|
|
return false if CurrentUser.is_janitor?
|
|
|
|
wiki_page&.is_locked? || false
|
|
|
|
end
|
2024-07-20 14:45:36 -04:00
|
|
|
|
|
|
|
def log_destroy
|
|
|
|
ModAction.log(:artist_delete, { artist_id: id, artist_name: name })
|
|
|
|
end
|
2024-09-07 16:34:25 -04:00
|
|
|
|
|
|
|
def update_posts_index
|
|
|
|
Post.tag_match_system(name).each(&:update_index)
|
|
|
|
end
|
2010-02-15 13:59:58 -05:00
|
|
|
end
|