diff --git a/app/helpers/icon_helper.rb b/app/helpers/icon_helper.rb index cd3fb0a87..8df8a6555 100644 --- a/app/helpers/icon_helper.rb +++ b/app/helpers/icon_helper.rb @@ -216,6 +216,8 @@ module IconHelper image_icon_tag("amazon-logo.png", **options) when "Ameblo" image_icon_tag("ameblo-logo.png", **options) + when "Anifty" + image_icon_tag("anifty-logo.png", **options) when "ArtStation" image_icon_tag("artstation-logo.png", **options) when "Ask.fm" diff --git a/app/logical/artist_finder.rb b/app/logical/artist_finder.rb index fadc045fc..8a77a56f0 100644 --- a/app/logical/artist_finder.rb +++ b/app/logical/artist_finder.rb @@ -12,6 +12,8 @@ module ArtistFinder "ameblo.jp", # https://ameblo.jp/g8set55679 "ameba.jp", # https://profile.ameba.jp/ameba/kbnr32rbfs "anidb.net", # https://anidb.net/creator/65313 + "anifty.jp", # https://anifty.jp/@unagi189 + %r{anifty.jp/(?:ja|zh|en)}, # https://anifty.jp/ja/@unagi189 "animenewsnetwork.com", # http://www.animenewsnetwork.com/encyclopedia/people.php?id=46869 "artstation.com/artist", # http://www.artstation.com/artist/serafleur/ "www.artstation.com", # http://www.artstation.com/serafleur/ diff --git a/app/logical/source/extractor.rb b/app/logical/source/extractor.rb index f758404a0..b7b1237e6 100644 --- a/app/logical/source/extractor.rb +++ b/app/logical/source/extractor.rb @@ -52,6 +52,7 @@ module Source Source::Extractor::Tinami, Source::Extractor::Fantia, Source::Extractor::Booth, + Source::Extractor::Anifty, ] # Should return true if the extractor is configured correctly. Return false diff --git a/app/logical/source/extractor/anifty.rb b/app/logical/source/extractor/anifty.rb new file mode 100644 index 000000000..d8af77f2f --- /dev/null +++ b/app/logical/source/extractor/anifty.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +# @see Source::URL::Anifty +class Source::Extractor + class Anifty < Source::Extractor + def match? + Source::URL::Anifty === parsed_url + end + + def image_urls + if parsed_url.image_url? + [parsed_url.full_image_url].compact + else + [api_response["imageURL"]].compact + end + end + + def profile_url + if artist_name.present? + "https://anifty.jp/@#{username}" + else + parsed_url.profile_url || parsed_referer&.profile_url + end + end + + def username + api_response.dig("creator", "userName") || artist_api_response["userName"] + end + + def artist_name + api_response.dig("creator", "displayName") || artist_api_response.dig("createdTokens", 0, "creatorProfile", "displayNameEN") + end + + def other_names + other_names = [username] + if api_response.present? + other_names << api_response.dig("creator", "displayNameJA") + elsif artist_api_response + other_names << artist_api_response.dig("createdTokens", 0, "creatorProfile", "displayNameJP") + end + other_names.compact.uniq + end + + def artist_commentary_title + api_response["title"] || api_response["titleJA"] + end + + def artist_commentary_desc + api_response["description"] || api_response["descriptionJA"] + end + + def tags + # anifty marketplace uses XHR requests to filter by tags, so there's no url to get + api_response["tags"].to_a.map do |tag| + [tag["name"], "https://anifty.jp/marketplace"] + end + end + + def page_url + if page_url_from_parsed_urls.present? + page_url_from_parsed_urls + elsif work_id.present? + "https://anifty.jp/creations/#{work_id}" + end + end + + def page_url_from_parsed_urls + parsed_url.page_url || parsed_referer&.page_url + end + + def work_id + parsed_url.work_id || parsed_referer&.work_id || work_id_from_artist_api + end + + def work_id_from_artist_api + # Try to get the work ID from the artist's list of tokens + return nil unless parsed_url.file.present? && parsed_url.work_type == "creation" + artist_api_response["createdTokens"].to_a.map do |token| + if Source::URL.parse(token["imageURL"])&.file == parsed_url.file + return token["creationID"] + end + end + nil + end + + def artist_hash + parsed_url.artist_hash || parsed_referer&.artist_hash + end + + def api_response + return {} if work_id.blank? + + resp = http.cache(1.minute).get("https://asia-northeast1-anifty-59655.cloudfunctions.net/api/v2/creations/#{work_id}") + return {} if resp.code != 200 + + resp.parse.with_indifferent_access + end + memoize :api_response + + def artist_api_response + return {} if artist_hash.blank? + + resp = http.cache(1.minute).get("https://asia-northeast1-anifty-59655.cloudfunctions.net/api/users/#{artist_hash}") + return {} if resp.code != 200 + + resp.parse.with_indifferent_access + end + memoize :artist_api_response + end +end diff --git a/app/logical/source/url.rb b/app/logical/source/url.rb index e1f561c65..ccac2582f 100644 --- a/app/logical/source/url.rb +++ b/app/logical/source/url.rb @@ -47,6 +47,7 @@ module Source Source::URL::Tumblr, Source::URL::TwitPic, Source::URL::Weibo, + Source::URL::Anifty, ] # Parse a URL into a subclass of Source::URL, or raise an exception if the URL is not a valid HTTP or HTTPS URL. diff --git a/app/logical/source/url/anifty.rb b/app/logical/source/url/anifty.rb new file mode 100644 index 000000000..4d96c2da9 --- /dev/null +++ b/app/logical/source/url/anifty.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +class Source::URL::Anifty < Source::URL + attr_reader :username, :artist_hash, :work_id, :file, :work_type + + def self.match?(url) + url.domain == "anifty.jp" || url.host == "anifty.imgix.net" || (url.host == "storage.googleapis.com" && url.path.include?("/anifty-media/")) + end + + def site_name + "Anifty" + end + + def parse + case [host, *path_segments] + + # https://anifty.imgix.net/creation/0x961d09077b4a9f7a27f6b7ee78cb4c26f0e72c18/20d5ce5b5163a71258e1d0ee152a0347bf40c7da.png?w=660&h=660&fit=crop&crop=focalpoint&fp-x=0.76&fp-y=0.5&fp-z=1&auto=compress + # https://anifty.imgix.net/creation/0x961d09077b4a9f7a27f6b7ee78cb4c26f0e72c18/48b1409838cf7271413480b8533372844b9f2437.png?w=3840&q=undefined&auto=compress + in "anifty.imgix.net", work_type, /0x\w+/ => artist_hash, file + @artist_hash = artist_hash + @file = file + @work_type = work_type + + # https://storage.googleapis.com/anifty-media/creation/0x961d09077b4a9f7a27f6b7ee78cb4c26f0e72c18/20d5ce5b5163a71258e1d0ee152a0347bf40c7da.png + # https://storage.googleapis.com/anifty-media/profile/0x961d09077b4a9f7a27f6b7ee78cb4c26f0e72c18/a6d2c366a3e876ddbf04fc269b63124be18af424.png + in "storage.googleapis.com", "anifty-media", work_type, /0x\w+/ => artist_hash, file + @artist_hash = artist_hash + @file = file + @work_type = work_type + + # https://anifty.jp/creations/373 + # https://anifty.jp/ja/creations/373 + # https://anifty.jp/zh/creations/373 + # https://anifty.jp/zh-Hant/creations/373 + in ("anifty.jp" | "www.anifty.jp"), *, "creations", /\d+/ => work_id + @work_id = work_id + + # https://anifty.jp/@hightree + # https://anifty.jp/ja/@hightree + in ("anifty.jp" | "www.anifty.jp"), *, /@(\w+)/ + @username = $1 + + else + end + end + + def image_url? + file.present? && artist_hash.present? + end + + def full_image_url + "https://storage.googleapis.com/anifty-media/#{work_type}/#{artist_hash}/#{file}" if image_url? + end + + def page_url + "https://anifty.jp/creations/#{work_id}" if work_id.present? + end + + def profile_url + "https://anifty.jp/@#{username}" if username.present? + end +end diff --git a/app/models/artist_url.rb b/app/models/artist_url.rb index b1301f633..036ba482f 100644 --- a/app/models/artist_url.rb +++ b/app/models/artist_url.rb @@ -95,7 +95,7 @@ class ArtistURL < ApplicationRecord def priority sites = %w[ Pixiv Twitter - ArtStation Baraag BCY Booth Deviant\ Art Hentai\ Foundry Fantia Foundation Lofter Nico\ Seiga Nijie Pawoo Fanbox Pixiv\ Sketch Plurk Tinami Tumblr Weibo + Anifty ArtStation Baraag BCY Booth Deviant\ Art Hentai\ Foundry Fantia Foundation Lofter Nico\ Seiga Nijie Pawoo Fanbox Pixiv\ Sketch Plurk Tinami Tumblr Weibo Ask.fm Facebook FC2 Gumroad Instagram Ko-fi Livedoor Mihuashi Mixi.jp Patreon Piapro.jp Picarto Privatter Sakura.ne.jp Stickam Skeb Twitch Youtube Amazon Circle.ms DLSite Doujinshi.org Erogamescape Mangaupdates Melonbooks Toranoana Wikipedia ] diff --git a/public/images/anifty-logo.png b/public/images/anifty-logo.png new file mode 100644 index 000000000..3354e5e55 Binary files /dev/null and b/public/images/anifty-logo.png differ diff --git a/test/unit/sources/anifty_test.rb b/test/unit/sources/anifty_test.rb new file mode 100644 index 000000000..454a6411e --- /dev/null +++ b/test/unit/sources/anifty_test.rb @@ -0,0 +1,43 @@ +require "test_helper" + +module Sources + class AniftyTest < ActiveSupport::TestCase + strategy_should_work( + "https://anifty.jp/ja/creations/1500", + image_urls: ["https://storage.googleapis.com/anifty-media/creation/0x0913be22dd08f7e092e00d4f8c2f61778dc6df94/a5bb2c63b8a602aba6cfd93d2147bef23b6b9bc2.jpg"], + profile_url: "https://anifty.jp/@inamihatoko", + page_url: "https://anifty.jp/creations/1500", + artist_name: "inami hatoko", + other_names: ["inamihatoko", "井波ハトコ"], + tags: ["background", "girl"], + artist_commentary_title: "Escape", + artist_commentary_desc: "Let's get out of there." + ) + + strategy_should_work( + "https://anifty.imgix.net/creation/0x9942a21fdc78fe2c3973d219a1d705a4efd056b4/22f4c9694dd2f1f32b610d1d75a18621c5c2d6d8.jpg?w=3840&q=undefined&auto=compress", + image_urls: ["https://storage.googleapis.com/anifty-media/creation/0x9942a21fdc78fe2c3973d219a1d705a4efd056b4/22f4c9694dd2f1f32b610d1d75a18621c5c2d6d8.jpg"], + profile_url: "https://anifty.jp/@unagi189", + page_url: "https://anifty.jp/creations/1585", + artist_name: "yunagi", + other_names: ["unagi189", "夕凪"], + tags: ["background", "girl", "uniform"], + artist_commentary_title: "Sound!", + artist_commentary_desc: "This work was created in 2017 and partially modified for exhibition.I created this work with the image of after-school for the girls in the brass band." + ) + + strategy_should_work( + "https://storage.googleapis.com/anifty-media/profile/0x961d09077b4a9f7a27f6b7ee78cb4c26f0e72c18/a6d2c366a3e876ddbf04fc269b63124be18af424.png", + image_urls: ["https://storage.googleapis.com/anifty-media/profile/0x961d09077b4a9f7a27f6b7ee78cb4c26f0e72c18/a6d2c366a3e876ddbf04fc269b63124be18af424.png"], + profile_url: "https://anifty.jp/@hightree", + page_url: nil, + artist_name: "Knoy Konome", + other_names: ["hightree", "木芽のい"], + tags: [], + artist_commentary_title: nil, + artist_commentary_desc: nil + ) + + strategy_should_work("https://anifty.jp/zh/creations/373123123", deleted: true, profile_url: nil) + end +end