From c9227645d96f21024cbc74a478fd1572ddb5888f Mon Sep 17 00:00:00 2001 From: nonamethanks Date: Mon, 18 Apr 2022 16:45:17 +0200 Subject: [PATCH] Add anifty.jp support --- app/helpers/icon_helper.rb | 2 + app/logical/artist_finder.rb | 2 + app/logical/source/extractor.rb | 1 + app/logical/source/extractor/anifty.rb | 110 +++++++++++++++++++++++++ app/logical/source/url.rb | 1 + app/logical/source/url/anifty.rb | 62 ++++++++++++++ app/models/artist_url.rb | 2 +- public/images/anifty-logo.png | Bin 0 -> 5290 bytes test/unit/sources/anifty_test.rb | 43 ++++++++++ 9 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 app/logical/source/extractor/anifty.rb create mode 100644 app/logical/source/url/anifty.rb create mode 100644 public/images/anifty-logo.png create mode 100644 test/unit/sources/anifty_test.rb 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 0000000000000000000000000000000000000000..3354e5e55b83c92349c98d2c816ca2fd338b97f2 GIT binary patch literal 5290 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 E07d8$@Bjb+ literal 0 HcmV?d00001 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