From c21af0c8539d4687bbe00909b7fce04aafbe4209 Mon Sep 17 00:00:00 2001 From: BrokenEagle Date: Fri, 29 May 2020 22:33:46 +0000 Subject: [PATCH 001/173] Fix invalid artist URLs being allowed The problem was that the Addressable parser does not catch all invalid URL cases, so some extra checks were added in. - hostname must contain a dot This accounts for URLs of the following type: http://http://something.com which has a hostname of http. The artist URL tests were also updated with cases which test all validation errors. --- app/models/artist_url.rb | 11 ++++++++++- test/unit/artist_url_test.rb | 14 +++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/models/artist_url.rb b/app/models/artist_url.rb index e39d3db65..0f7854d5f 100644 --- a/app/models/artist_url.rb +++ b/app/models/artist_url.rb @@ -120,9 +120,18 @@ class ArtistUrl < ApplicationRecord end end + def validate_scheme(uri) + errors[:url] << "'#{uri}' must begin with http:// or https:// " unless uri.scheme.in?(%w[http https]) + end + + def validate_hostname(uri) + errors[: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) - errors[:url] << "'#{uri}' must begin with http:// or https:// " if !uri.scheme.in?(%w[http https]) + validate_scheme(uri) + validate_hostname(uri) rescue Addressable::URI::InvalidURIError => error errors[:url] << "'#{uri}' is malformed: #{error}" end diff --git a/test/unit/artist_url_test.rb b/test/unit/artist_url_test.rb index b17dc7cef..76eca5459 100644 --- a/test/unit/artist_url_test.rb +++ b/test/unit/artist_url_test.rb @@ -24,10 +24,18 @@ class ArtistUrlTest < ActiveSupport::TestCase end should "disallow invalid urls" do - url = FactoryBot.build(:artist_url, url: "www.example.com") + urls = [ + FactoryBot.build(:artist_url, url: "www.example.com"), + FactoryBot.build(:artist_url, url: ":www.example.com"), + FactoryBot.build(:artist_url, url: "http://http://www.example.com"), + ] - assert_equal(false, url.valid?) - assert_match(/must begin with http/, url.errors.full_messages.join) + assert_equal(false, urls[0].valid?) + assert_match(/must begin with http/, urls[0].errors.full_messages.join) + assert_equal(false, urls[1].valid?) + assert_match(/is malformed/, urls[1].errors.full_messages.join) + assert_equal(false, urls[2].valid?) + assert_match(/that does not contain a dot/, urls[2].errors.full_messages.join) end should "always add a trailing slash when normalized" do From ed9135bcf3b8c715a9a56f61b6b3a4c4e5a3d88c Mon Sep 17 00:00:00 2001 From: BrokenEagle Date: Fri, 29 May 2020 22:34:32 +0000 Subject: [PATCH 002/173] Perform some scheme and hostname normalization on the URL itself - Converts scheme and hostname to lowercase - Converts unicode hostnames into Punycode This all gets done before the normalized URL gets assigned. Additionally, this removes the dead commented out line for Nicoseiga. --- app/models/artist_url.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/models/artist_url.rb b/app/models/artist_url.rb index 0f7854d5f..54c743bbe 100644 --- a/app/models/artist_url.rb +++ b/app/models/artist_url.rb @@ -20,11 +20,9 @@ class ArtistUrl < ApplicationRecord nil else url = url.sub(%r!^https://!, "http://") - url = url.sub(%r!^http://([^/]+)!i) { |domain| domain.downcase } url = url.sub(%r!^http://blog\d+\.fc2!, "http://blog.fc2") 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://seiga.nicovideo.jp/user/illust/\d+)\?.+!, '\1/') url = url.sub(%r!^http://pictures.hentai-foundry.com//!, "http://pictures.hentai-foundry.com/") # XXX should be handled by pixiv strategy. @@ -105,7 +103,15 @@ class ArtistUrl < ApplicationRecord 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 From e83d07ea7baa4cd03990d2d701a3cd95f1494500 Mon Sep 17 00:00:00 2001 From: nonamethanks Date: Fri, 12 Jun 2020 04:13:22 +0200 Subject: [PATCH 003/173] Pixiv: don't blacklist digital tools anymore --- app/logical/pixiv_api_client.rb | 17 +---------------- test/unit/sources/pixiv_test.rb | 6 +----- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/app/logical/pixiv_api_client.rb b/app/logical/pixiv_api_client.rb index f1d55c981..c742e250e 100644 --- a/app/logical/pixiv_api_client.rb +++ b/app/logical/pixiv_api_client.rb @@ -8,21 +8,6 @@ class PixivApiClient CLIENT_SECRET = "HP3RmkgAmEGro0gn1x9ioawQE8WMfvLXDz3ZqxpK" CLIENT_HASH_SALT = "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c" - # Tools to not include in the tags list. We don't tag digital media, so - # including these results in bad translated tags suggestions. - TOOLS_BLACKLIST = %w[ - Photoshop Illustrator Fireworks Flash Painter PaintShopPro pixiv\ Sketch - CLIP\ STUDIO\ PAINT IllustStudio ComicStudio RETAS\ STUDIO SAI PhotoStudio - Pixia NekoPaint PictBear openCanvas ArtRage Expression Inkscape GIMP - CGillust COMICWORKS MS_Paint EDGE AzPainter AzPainter2 AzDrawing - PicturePublisher SketchBookPro Processing 4thPaint GraphicsGale mdiapp - Paintgraphic AfterEffects drawr CLIP\ PAINT\ Lab FireAlpaca Pixelmator - AzDrawing2 MediBang\ Paint Krita ibisPaint Procreate Live2D - Lightwave3D Shade Poser STRATA AnimationMaster XSI CARRARA CINEMA4D Maya - 3dsMax Blender ZBrush Metasequoia Sunny3D Bryce Vue Hexagon\ King SketchUp - VistaPro Sculptris Comi\ Po! modo DAZ\ Studio 3D-Coat - ] - class Error < StandardError; end class BadIDError < Error; end @@ -39,7 +24,7 @@ class PixivApiClient @artist_commentary_title = json["title"].to_s @artist_commentary_desc = json["caption"].to_s @tags = json["tags"].reject {|x| x =~ /^http:/} - @tags += json["tools"] - TOOLS_BLACKLIST + @tags += json["tools"] if json["metadata"] if json["metadata"]["zip_urls"] diff --git a/test/unit/sources/pixiv_test.rb b/test/unit/sources/pixiv_test.rb index 4ba9738e9..0103ffb53 100644 --- a/test/unit/sources/pixiv_test.rb +++ b/test/unit/sources/pixiv_test.rb @@ -259,10 +259,6 @@ module Sources assert_includes(@translated_tags, "foo") end - should "not translate tags for digital media" do - assert_equal(false, @tags.include?("Photoshop")) - end - should "normalize 10users入り tags" do assert_includes(@tags, "風景10users入り") assert_includes(@translated_tags, "scenery") @@ -294,7 +290,7 @@ module Sources should "not translate '1000users入り' to '1'" do FactoryBot.create(:tag, name: "1", post_count: 1) source = get_source("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=60665428") - tags = %w[1000users入り Fate/GrandOrder アルジュナ(Fate) アルトリア・ペンドラゴン イシュタル(Fate) グランブルーファンタジー マシュ・キリエライト マーリン(Fate) 両儀式 手袋] + tags = %w[1000users入り Fate/GrandOrder アルジュナ(Fate) アルトリア・ペンドラゴン イシュタル(Fate) グランブルーファンタジー マシュ・キリエライト マーリン(Fate) 両儀式 手袋 CLIP\ STUDIO\ PAINT Photoshop] assert_equal(tags.sort, source.tags.map(&:first).sort) assert_equal(["fate/grand_order"], source.translated_tags.map(&:name)) From 1846133cd6aeacc4b4e3a98767ed505a4af85585 Mon Sep 17 00:00:00 2001 From: evazion Date: Sat, 13 Jun 2020 21:50:06 -0500 Subject: [PATCH 004/173] post view count service: switch to Danbooru::Http. --- app/logical/post_view_count_service.rb | 39 ++++------ test/unit/post_view_count_service_test.rb | 88 ++++------------------- 2 files changed, 25 insertions(+), 102 deletions(-) diff --git a/app/logical/post_view_count_service.rb b/app/logical/post_view_count_service.rb index 8564a027f..7e1bbd8c9 100644 --- a/app/logical/post_view_count_service.rb +++ b/app/logical/post_view_count_service.rb @@ -1,38 +1,25 @@ class PostViewCountService - def self.enabled? - Danbooru.config.reportbooru_server.present? + attr_reader :http, :reportbooru_server + + def initialize(http: Danbooru::Http.new, reportbooru_server: Danbooru.config.reportbooru_server) + @reportbooru_server = reportbooru_server + @http = http end - def initialize - if !PostViewCountService.enabled? - raise NotImplementedError.new("the Reportbooru service isn't configured. Post views are not available.") - end - end - - def fetch_count(post_id) - url = URI.parse("#{Danbooru.config.reportbooru_server}/post_views/#{post_id}") - response = HTTParty.get(url, Danbooru.config.httparty_options.reverse_merge(timeout: 6)) - if response.success? - return JSON.parse(response.body) - else - return nil - end + def enabled? + reportbooru_server.present? end def fetch_rank(date = Date.today) - url = URI.parse("#{Danbooru.config.reportbooru_server}/post_views/rank?date=#{date}") - response = HTTParty.get(url, Danbooru.config.httparty_options.reverse_merge(timeout: 6)) - if response.success? - return JSON.parse(response.body) - else - return nil - end - rescue JSON::ParserError - nil + raise NotImplementedError, "Reportbooru not configured, post views not available." unless enabled? + + response = http.get("#{reportbooru_server}/post_views/rank?date=#{date}") + return [] if response.status != 200 + JSON.parse(response.to_s) end def popular_posts(date = Date.today) - ranking = fetch_rank(date) || [] + ranking = fetch_rank(date) ranking.slice(0, 50).map {|x| Post.find(x[0])} end end diff --git a/test/unit/post_view_count_service_test.rb b/test/unit/post_view_count_service_test.rb index ce9b856ac..d134ecc5a 100644 --- a/test/unit/post_view_count_service_test.rb +++ b/test/unit/post_view_count_service_test.rb @@ -2,88 +2,24 @@ require 'test_helper' class PostViewCountServiceTest < ActiveSupport::TestCase def setup - super - - CurrentUser.user = FactoryBot.create(:user) - CurrentUser.ip_addr = "127.0.0.1" - - PostViewCountService.stubs(:enabled?).returns(true) - Danbooru.config.stubs(:reportbooru_server).returns("http://localhost:1234") - @post = FactoryBot.create(:post) + @service = PostViewCountService.new(reportbooru_server: "http://localhost:1234") + @post = create(:post) + @date = "2000-01-01" end - def teardown - super - CurrentUser.user = nil - CurrentUser.ip_addr = nil - end - - subject { PostViewCountService.new } - context "#popular_posts" do - setup do - subject.stubs(:fetch_rank).returns([[@post.id, 1]]) + should "return the list of popular posts on success" do + body = "[[#{@post.id},100.0]]" + @service.http.expects(:get).with("http://localhost:1234/post_views/rank?date=#{@date}").returns(HTTP::Response.new(status: 200, body: body, version: "1.1")) + + posts = @service.popular_posts(@date) + assert_equal([@post], posts) end - should "return the posts" do - posts = subject.popular_posts - assert_equal(@post.id, posts[0].id) - end - end + should "return nothing on failure" do + @service.http.expects(:get).with("http://localhost:1234/post_views/rank?date=#{@date}").returns(HTTP::Response.new(status: 500, body: "", version: "1.1")) - context "#fetch_rank" do - context "success" do - setup do - @date = "2000-01-01" - @body = "[[1,1.0],[2,2.0]]" - stub_request(:get, "localhost:1234/post_views/rank").with(query: {"date" => @date}).to_return(body: @body) - end - - should "return a list" do - json = subject.fetch_rank(@date) - assert(json.is_a?(Array)) - assert_equal(1, json[0][0]) - assert_equal(2, json[1][0]) - end - end - - context "failure" do - setup do - @date = "2000-01-01" - stub_request(:get, "localhost:1234/post_views/rank").with(query: {"date" => @date}).to_return(body: "", status: 400) - end - - should "return nil" do - json = subject.fetch_rank(@date) - assert_nil(json) - end - end - end - - context "#fetch_count" do - context "success" do - setup do - @body = "[[1,5],[2,20]]" - stub_request(:get, "localhost:1234/post_views/#{@post.id}").to_return(body: @body) - end - - should "return a list" do - json = subject.fetch_count(@post.id) - assert(json.is_a?(Array)) - assert_equal(1, json[0][0]) - assert_equal(2, json[1][0]) - end - end - - context "failure" do - setup do - stub_request(:get, "localhost:1234/post_views/#{@post.id}").to_return(body: "", status: 400) - end - - should "return nil" do - json = subject.fetch_count(@post.id) - assert_nil(json) - end + assert_equal([], @service.popular_posts(@date)) end end end From a4df18e650de5204ea057b213b90b00548de0e83 Mon Sep 17 00:00:00 2001 From: evazion Date: Sun, 14 Jun 2020 00:24:15 -0500 Subject: [PATCH 005/173] Refactor Reportbooru API clients. * Combine MissedSearchService, PostViewCountService, and PopularSearchService into single ReportbooruService class. * Use Danbooru::Http for these services instead of HTTParty. --- app/controllers/explore/posts_controller.rb | 8 +-- app/controllers/static_controller.rb | 2 +- app/logical/missed_search_service.rb | 29 --------- app/logical/popular_search_service.rb | 61 ------------------- app/logical/post_sets/post.rb | 8 ++- app/logical/post_view_count_service.rb | 25 -------- app/logical/reportbooru_service.rb | 49 +++++++++++++++ .../explore/posts/missed_searches.html.erb | 2 +- app/views/explore/posts/searches.html.erb | 4 +- .../posts/partials/index/_related.html.erb | 4 +- app/views/static/sitemap.xml.erb | 4 +- .../explore/posts_controller_test.rb | 10 +++ test/functional/posts_controller_test.rb | 6 +- test/functional/static_controller_test.rb | 1 + test/test_helper.rb | 2 - test/test_helpers/reportbooru_helper.rb | 26 +++++--- ...ce_test.rb => reportbooru_service_test.rb} | 4 +- 17 files changed, 102 insertions(+), 143 deletions(-) delete mode 100644 app/logical/missed_search_service.rb delete mode 100644 app/logical/popular_search_service.rb delete mode 100644 app/logical/post_view_count_service.rb create mode 100644 app/logical/reportbooru_service.rb rename test/unit/{post_view_count_service_test.rb => reportbooru_service_test.rb} (84%) diff --git a/app/controllers/explore/posts_controller.rb b/app/controllers/explore/posts_controller.rb index c2d7e25c3..e40d6c5b3 100644 --- a/app/controllers/explore/posts_controller.rb +++ b/app/controllers/explore/posts_controller.rb @@ -22,23 +22,23 @@ module Explore def viewed @date, @scale, @min_date, @max_date = parse_date(params) - @posts = PostViewCountService.new.popular_posts(@date) + @posts = ReportbooruService.new.popular_posts(@date) respond_with(@posts) end def searches @date, @scale, @min_date, @max_date = parse_date(params) - @search_service = PopularSearchService.new(@date) + @search_service = ReportbooruService.new end def missed_searches - @search_service = MissedSearchService.new + @search_service = ReportbooruService.new end private def parse_date(params) - date = params[:date].present? ? Date.parse(params[:date]) : Time.zone.today + date = params[:date].present? ? Date.parse(params[:date]) : Date.today scale = params[:scale].in?(["day", "week", "month"]) ? params[:scale] : "day" min_date = date.send("beginning_of_#{scale}") max_date = date.send("next_#{scale}").send("beginning_of_#{scale}") diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb index fc151ebd7..8dff5678f 100644 --- a/app/controllers/static_controller.rb +++ b/app/controllers/static_controller.rb @@ -17,7 +17,7 @@ class StaticController < ApplicationController end def sitemap - @popular_search_service = PopularSearchService.new(Date.yesterday) + @reportbooru_service = ReportbooruService.new @posts = Post.where("created_at > ?", 1.week.ago).order(score: :desc).limit(200) @posts = @posts.select(&:visible?) render layout: false diff --git a/app/logical/missed_search_service.rb b/app/logical/missed_search_service.rb deleted file mode 100644 index f98e3d395..000000000 --- a/app/logical/missed_search_service.rb +++ /dev/null @@ -1,29 +0,0 @@ -# queries reportbooru to find missed post searches -class MissedSearchService - def self.enabled? - Danbooru.config.reportbooru_server.present? - end - - def initialize - if !MissedSearchService.enabled? - raise NotImplementedError.new("the Reportbooru service isn't configured. Missed searches are not available.") - end - end - - def each_search(&block) - fetch_data.scan(/(.+?) (\d+)\.0\n/).each(&block) - end - - def fetch_data - Cache.get("ms", 1.minute) do - url = URI.parse("#{Danbooru.config.reportbooru_server}/missed_searches") - response = HTTParty.get(url, Danbooru.config.httparty_options.reverse_merge(timeout: 6)) - if response.success? - response = response.body - else - response = "" - end - response.force_encoding("utf-8") - end - end -end diff --git a/app/logical/popular_search_service.rb b/app/logical/popular_search_service.rb deleted file mode 100644 index 026684f3c..000000000 --- a/app/logical/popular_search_service.rb +++ /dev/null @@ -1,61 +0,0 @@ -# queries reportbooru to find popular post searches -class PopularSearchService - attr_reader :date - - def self.enabled? - Danbooru.config.reportbooru_server.present? - end - - def initialize(date) - if !PopularSearchService.enabled? - raise NotImplementedError.new("the Reportbooru service isn't configured. Popular searches are not available.") - end - - @date = date - end - - def each_search(limit = 100, &block) - JSON.parse(fetch_data.to_s).slice(0, limit).each(&block) - end - - def tags - JSON.parse(fetch_data.to_s).map {|x| x[0]} - end - - def fetch_data - return [] unless self.class.enabled? - - dates = date.strftime("%Y-%m-%d") - - data = Cache.get("ps-day-#{dates}", 1.minute) do - url = "#{Danbooru.config.reportbooru_server}/post_searches/rank?date=#{dates}" - response = HTTParty.get(url, Danbooru.config.httparty_options.reverse_merge(timeout: 3)) - if response.success? - response = response.body - else - response = "[]" - end - response - end.to_s.force_encoding("utf-8") - - if data.blank? || data == "[]" - dates = date.yesterday.strftime("%Y-%m-%d") - - data = Cache.get("ps-day-#{dates}", 1.minute) do - url = "#{Danbooru.config.reportbooru_server}/post_searches/rank?date=#{dates}" - response = HTTParty.get(url, Danbooru.config.httparty_options.reverse_merge(timeout: 3)) - if response.success? - response = response.body - else - response = "[]" - end - response - end.to_s.force_encoding("utf-8") - end - - data - rescue StandardError => e - DanbooruLogger.log(e) - return [] - end -end diff --git a/app/logical/post_sets/post.rb b/app/logical/post_sets/post.rb index e12315873..88c415c13 100644 --- a/app/logical/post_sets/post.rb +++ b/app/logical/post_sets/post.rb @@ -169,8 +169,8 @@ module PostSets end def popular_tags - if PopularSearchService.enabled? - PopularSearchService.new(Date.today).tags + if reportbooru_service.enabled? + reportbooru_service.popular_searches(Date.today, limit: MAX_SIDEBAR_TAGS).map(&:first) else frequent_tags end @@ -199,6 +199,10 @@ module PostSets def tag_list_html(**options) tag_set_presenter.tag_list_html(name_only: query.is_metatag?(:search), **options) end + + def reportbooru_service + @reportbooru_service ||= ReportbooruService.new + end end end end diff --git a/app/logical/post_view_count_service.rb b/app/logical/post_view_count_service.rb deleted file mode 100644 index 7e1bbd8c9..000000000 --- a/app/logical/post_view_count_service.rb +++ /dev/null @@ -1,25 +0,0 @@ -class PostViewCountService - attr_reader :http, :reportbooru_server - - def initialize(http: Danbooru::Http.new, reportbooru_server: Danbooru.config.reportbooru_server) - @reportbooru_server = reportbooru_server - @http = http - end - - def enabled? - reportbooru_server.present? - end - - def fetch_rank(date = Date.today) - raise NotImplementedError, "Reportbooru not configured, post views not available." unless enabled? - - response = http.get("#{reportbooru_server}/post_views/rank?date=#{date}") - return [] if response.status != 200 - JSON.parse(response.to_s) - end - - def popular_posts(date = Date.today) - ranking = fetch_rank(date) - ranking.slice(0, 50).map {|x| Post.find(x[0])} - end -end diff --git a/app/logical/reportbooru_service.rb b/app/logical/reportbooru_service.rb new file mode 100644 index 000000000..83c8bc895 --- /dev/null +++ b/app/logical/reportbooru_service.rb @@ -0,0 +1,49 @@ +class ReportbooruService + attr_reader :http, :reportbooru_server + + def initialize(http: Danbooru::Http.new, reportbooru_server: Danbooru.config.reportbooru_server) + @reportbooru_server = reportbooru_server + @http = http + end + + def enabled? + reportbooru_server.present? + end + + def missed_search_rankings(expires_in: 1.minutes) + raise NotImplementedError, "Reportbooru not configured, missed searches not available." unless enabled? + + response = http.cache(expires_in).get("#{reportbooru_server}/missed_searches") + return [] if response.status != 200 + + body = response.to_s.force_encoding("utf-8") + body.lines.map(&:split).map { [_1, _2.to_i] } + end + + def post_search_rankings(date = Date.today, expires_in: 1.minutes) + raise NotImplementedError, "Reportbooru not configured, popular searches not available." unless enabled? + + response = http.cache(expires_in).get("#{reportbooru_server}/post_searches/rank?date=#{date}") + return [] if response.status != 200 + JSON.parse(response.to_s.force_encoding("utf-8")) + end + + def post_view_rankings(date = Date.today, expires_in: 1.minutes) + raise NotImplementedError, "Reportbooru not configured, post views not available." unless enabled? + + response = http.get("#{reportbooru_server}/post_views/rank?date=#{date}") + return [] if response.status != 200 + JSON.parse(response.to_s.force_encoding("utf-8")) + end + + def popular_searches(date = Date.today, limit: 100) + ranking = post_search_rankings(date) + ranking = post_search_rankings(date.yesterday) if ranking.blank? + ranking.take(limit).map(&:first) + end + + def popular_posts(date = Date.today, limit: 100) + ranking = post_view_rankings(date) + ranking.take(limit).map { |x| Post.find(x[0]) } + end +end diff --git a/app/views/explore/posts/missed_searches.html.erb b/app/views/explore/posts/missed_searches.html.erb index 2575d1694..df5e730bd 100644 --- a/app/views/explore/posts/missed_searches.html.erb +++ b/app/views/explore/posts/missed_searches.html.erb @@ -15,7 +15,7 @@ - <% @search_service.each_search do |tags, count| %> + <% @search_service.missed_search_rankings do |tags, count| %> <%= link_to tags, posts_path(:tags => tags) %> diff --git a/app/views/explore/posts/searches.html.erb b/app/views/explore/posts/searches.html.erb index 42770ad5a..18d02f8bf 100644 --- a/app/views/explore/posts/searches.html.erb +++ b/app/views/explore/posts/searches.html.erb @@ -3,7 +3,7 @@
-

Popular Searches - <%= @search_service.date %>

+

Popular Searches - <%= @date %>

@@ -13,7 +13,7 @@ - <% @search_service.each_search do |tags, count| %> + <% @search_service.post_search_rankings(@date) do |tags, count| %> diff --git a/app/views/posts/partials/index/_related.html.erb b/app/views/posts/partials/index/_related.html.erb index d23a094fc..c979869b8 100644 --- a/app/views/posts/partials/index/_related.html.erb +++ b/app/views/posts/partials/index/_related.html.erb @@ -5,10 +5,8 @@ - <% if PopularSearchService.enabled? %> + <% if ReportbooruService.enabled? %>
  • <%= link_to "Searches", searches_explore_posts_path %>
  • - <% end %> - <% if PostViewCountService.enabled? %>
  • <%= link_to "Viewed", viewed_explore_posts_path %>
  • <% end %> <% end %> diff --git a/app/views/static/sitemap.xml.erb b/app/views/static/sitemap.xml.erb index 4c8e1ece5..dc44779c3 100644 --- a/app/views/static/sitemap.xml.erb +++ b/app/views/static/sitemap.xml.erb @@ -16,7 +16,7 @@ <% cache("sitemap", :expires_in => 24.hours) do %> - <% @popular_search_service.each_search do |tags, count| %> + <% @reportbooru_service.post_search_rankings(Date.yesterday) do |tags, count| %> <%= posts_url(tags: tags) %> <%= Date.today %> @@ -38,4 +38,4 @@ <% end %> <% end %> - \ No newline at end of file + diff --git a/test/functional/explore/posts_controller_test.rb b/test/functional/explore/posts_controller_test.rb index 171235f32..5fd79d5e1 100644 --- a/test/functional/explore/posts_controller_test.rb +++ b/test/functional/explore/posts_controller_test.rb @@ -28,8 +28,17 @@ module Explore end end + context "#viewed" do + should "render" do + mock_post_view_rankings(Date.today, [[@post.id, 100]]) + get viewed_explore_posts_path + assert_response :success + end + end + context "#searches" do should "render" do + mock_post_search_rankings(Date.today, [["1girl", 100], ["original", 50]]) get searches_explore_posts_path assert_response :success end @@ -37,6 +46,7 @@ module Explore context "#missed_searches" do should "render" do + mock_missed_search_rankings([["1girl", 100], ["original", 50]]) get missed_searches_explore_posts_path assert_response :success end diff --git a/test/functional/posts_controller_test.rb b/test/functional/posts_controller_test.rb index d488062f0..548ecacab 100644 --- a/test/functional/posts_controller_test.rb +++ b/test/functional/posts_controller_test.rb @@ -3,13 +3,15 @@ require "test_helper" class PostsControllerTest < ActionDispatch::IntegrationTest context "The posts controller" do setup do - PopularSearchService.stubs(:enabled?).returns(false) - @user = travel_to(1.month.ago) {create(:user)} @post = as(@user) { create(:post, tag_string: "aaaa") } end context "index action" do + setup do + mock_post_search_rankings(Date.today, [["1girl", 100], ["original", 50]]) + end + should "render" do get posts_path assert_response :success diff --git a/test/functional/static_controller_test.rb b/test/functional/static_controller_test.rb index 0fd2bd57b..ee8dca354 100644 --- a/test/functional/static_controller_test.rb +++ b/test/functional/static_controller_test.rb @@ -16,6 +16,7 @@ class StaticControllerTest < ActionDispatch::IntegrationTest context "sitemap action" do should "work" do create_list(:post, 3) + mock_post_search_rankings(Time.zone.yesterday, [["1girl", 100.0], ["2girls", 50.0]]) get sitemap_path, as: :xml assert_response :success end diff --git a/test/test_helper.rb b/test/test_helper.rb index e395a6195..700bf1e6d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -43,8 +43,6 @@ class ActiveSupport::TestCase setup do Socket.stubs(:gethostname).returns("www.example.com") - mock_popular_search_service! - mock_missed_search_service! WebMock.allow_net_connect! storage_manager = StorageManager::Local.new(base_dir: Dir.mktmpdir("uploads-test-storage-")) diff --git a/test/test_helpers/reportbooru_helper.rb b/test/test_helpers/reportbooru_helper.rb index 441df8216..051c33395 100644 --- a/test/test_helpers/reportbooru_helper.rb +++ b/test/test_helpers/reportbooru_helper.rb @@ -1,12 +1,24 @@ module ReportbooruHelper - def mock_popular_search_service! - Danbooru.config.stubs(:reportbooru_server).returns("http://localhost:3003") - stub_request(:get, "http://localhost:3003/post_searches/month?date=#{Date.today}").to_return(body: "kantai_collection 1000.0\ntouhou 500.0") - stub_request(:get, "http://localhost:3003/post_searches/day?date=#{Date.today}").to_return(body: "kantai_collection 1000.0\ntouhou 500.0") + def mock_request(url, method: :get, status: 200, body: nil, http: Danbooru::Http.any_instance) + response = HTTP::Response.new(status: status, body: body, version: "1.1") + http.stubs(method).with(url).returns(response) end - def mock_missed_search_service! - Danbooru.config.stubs(:reportbooru_server).returns("http://localhost:3003") - stub_request(:get, "http://localhost:3003/missed_searches").to_return(body: "kantai_collection 1000.0\ntouhou 500.0") + def mock_post_search_rankings(date = Date.today, rankings) + Danbooru.config.stubs(:reportbooru_server).returns("http://localhost:1234") + url = "http://localhost:1234/post_searches/rank?date=#{date}" + mock_request(url, body: rankings.to_json) + end + + def mock_missed_search_rankings(date = Date.today, rankings) + Danbooru.config.stubs(:reportbooru_server).returns("http://localhost:1234") + url = "http://localhost:1234/missed_searches" + mock_request(url, body: rankings.to_json) + end + + def mock_post_view_rankings(date = Date.today, rankings) + Danbooru.config.stubs(:reportbooru_server).returns("http://localhost:1234") + url = "http://localhost:1234/post_views/rank?date=#{date}" + mock_request(url, body: rankings.to_json) end end diff --git a/test/unit/post_view_count_service_test.rb b/test/unit/reportbooru_service_test.rb similarity index 84% rename from test/unit/post_view_count_service_test.rb rename to test/unit/reportbooru_service_test.rb index d134ecc5a..3d795d130 100644 --- a/test/unit/post_view_count_service_test.rb +++ b/test/unit/reportbooru_service_test.rb @@ -1,8 +1,8 @@ require 'test_helper' -class PostViewCountServiceTest < ActiveSupport::TestCase +class ReportbooruServiceTest < ActiveSupport::TestCase def setup - @service = PostViewCountService.new(reportbooru_server: "http://localhost:1234") + @service = ReportbooruService.new(reportbooru_server: "http://localhost:1234") @post = create(:post) @date = "2000-01-01" end From cd501fe27beab2ec6a9cee5342135b475eafa9f4 Mon Sep 17 00:00:00 2001 From: evazion Date: Sun, 14 Jun 2020 01:06:51 -0500 Subject: [PATCH 006/173] iqdb: switch to Danbooru::Http. --- app/controllers/iqdb_queries_controller.rb | 2 +- app/logical/iqdb_proxy.rb | 33 +++++++----- .../iqdb_queries_controller_test.rb | 52 +++++-------------- test/test_helpers/iqdb_test_helper.rb | 10 ++-- 4 files changed, 37 insertions(+), 60 deletions(-) diff --git a/app/controllers/iqdb_queries_controller.rb b/app/controllers/iqdb_queries_controller.rb index 21a063838..659c22950 100644 --- a/app/controllers/iqdb_queries_controller.rb +++ b/app/controllers/iqdb_queries_controller.rb @@ -5,7 +5,7 @@ class IqdbQueriesController < ApplicationController # XXX allow bare search params for backwards compatibility. search_params.merge!(params.slice(:url, :image_url, :file_url, :post_id, :limit, :similarity, :high_similarity).permit!) - @high_similarity_matches, @low_similarity_matches, @matches = IqdbProxy.search(search_params) + @high_similarity_matches, @low_similarity_matches, @matches = IqdbProxy.new.search(search_params) respond_with(@matches, template: "iqdb_queries/show") end diff --git a/app/logical/iqdb_proxy.rb b/app/logical/iqdb_proxy.rb index a812596ff..e1b1f195f 100644 --- a/app/logical/iqdb_proxy.rb +++ b/app/logical/iqdb_proxy.rb @@ -1,19 +1,23 @@ class IqdbProxy class Error < StandardError; end + attr_reader :http, :iqdbs_server - def self.enabled? - Danbooru.config.iqdbs_server.present? + def initialize(http: Danbooru::Http.new, iqdbs_server: Danbooru.config.iqdbs_server) + @iqdbs_server = iqdbs_server + @http = http end - def self.download(url, type) + def enabled? + iqdbs_server.present? + end + + def download(url, type) download = Downloads::File.new(url) file, strategy = download.download!(url: download.send(type)) file end - def self.search(params) - raise NotImplementedError, "the IQDBs service isn't configured" unless enabled? - + def search(params) limit = params[:limit]&.to_i&.clamp(1, 1000) || 20 similarity = params[:similarity]&.to_f&.clamp(0.0, 100.0) || 0.0 high_similarity = params[:high_similarity]&.to_f&.clamp(0.0, 100.0) || 65.0 @@ -46,15 +50,18 @@ class IqdbProxy file.try(:close) end - def self.query(params) - response = HTTParty.post("#{Danbooru.config.iqdbs_server}/similar", body: params, **Danbooru.config.httparty_options) - raise Error, "IQDB error: #{response.code} #{response.message}" unless response.success? - raise Error, "IQDB error: #{response.parsed_response["error"]}" if response.parsed_response.is_a?(Hash) - raise Error, "IQDB error: #{response.parsed_response.first}" if response.parsed_response.try(:first).is_a?(String) - response.parsed_response + def query(params) + raise NotImplementedError, "the IQDBs service isn't configured" unless enabled? + response = http.post("#{iqdbs_server}/similar", body: params) + + raise Error, "IQDB error: #{response.status}" if response.status != 200 + raise Error, "IQDB error: #{response.parse["error"]}" if response.parse.is_a?(Hash) + raise Error, "IQDB error: #{response.parse.first}" if response.parse.try(:first).is_a?(String) + + response.parse end - def self.decorate_posts(json) + def decorate_posts(json) post_ids = json.map { |match| match["post_id"] } posts = Post.where(id: post_ids).group_by(&:id).transform_values(&:first) diff --git a/test/functional/iqdb_queries_controller_test.rb b/test/functional/iqdb_queries_controller_test.rb index eb0f3d206..e9af6770d 100644 --- a/test/functional/iqdb_queries_controller_test.rb +++ b/test/functional/iqdb_queries_controller_test.rb @@ -3,61 +3,33 @@ require 'test_helper' class IqdbQueriesControllerTest < ActionDispatch::IntegrationTest context "The iqdb controller" do setup do - Danbooru.config.stubs(:iqdbs_server).returns("https://karasuma.donmai.us") @user = create(:user) - @posts = as(@user) { create_list(:post, 2) } + @post = as(@user) { create(:post) } end context "show action" do context "with a url parameter" do - setup do - @url = "https://google.com" - @params = { url: @url } - @mocked_response = [{ - "post" => @posts[0], - "post_id" => @posts[0].id, - "score" => 1 - }] - end - should "render a response" do - IqdbProxy.expects(:query).returns(@mocked_response) - get_auth iqdb_queries_path, @user, as: :javascript, params: @params + @url = "https://google.com" + @matches = [{ "post_id" => @post.id, "width" => 128, "height" => 128, "score" => 95.0 }] + mock_iqdb_matches(@matches) + + get_auth iqdb_queries_path, @user, as: :javascript, params: { url: @url } assert_response :success - assert_select("#post_#{@posts[0].id}") + assert_select("#post_#{@post.id}") end end context "with a post_id parameter" do - setup do - @params = { post_id: @posts[0].id } - @url = @posts[0].preview_file_url - @mocked_response = [{ - "post" => @posts[0], - "post_id" => @posts[0].id, - "score" => 1 - }] - end - should "redirect to iqdbs" do - IqdbProxy.expects(:query).returns(@mocked_response) - get_auth iqdb_queries_path, @user, params: @params + @matches = [{ "post_id" => @post.id, "width" => 128, "height" => 128, "score" => 95.0 }] + mock_iqdb_matches(@matches) + + get_auth iqdb_queries_path, @user, params: { post_id: @post.id } assert_response :success - assert_select("#post_#{@posts[0].id}") - end - end - - context "with matches" do - setup do - json = @posts.map {|x| {"post_id" => x.id, "score" => 1}}.to_json - @params = { matches: json } - end - - should "render with matches" do - get_auth iqdb_queries_path, @user, params: @params - assert_response :success + assert_select("#post_#{@post.id}") end end end diff --git a/test/test_helpers/iqdb_test_helper.rb b/test/test_helpers/iqdb_test_helper.rb index 1feeffabc..4b52eade7 100644 --- a/test/test_helpers/iqdb_test_helper.rb +++ b/test/test_helpers/iqdb_test_helper.rb @@ -21,11 +21,9 @@ module IqdbTestHelper Danbooru.config.stubs(:iqdbs_server).returns("http://localhost:3004") end - def mock_iqdb_matches!(post_or_source, matches) - source = post_or_source.is_a?(Post) ? post_or_source.preview_file_url : post_or_source - url = "http://localhost:3004/similar?key=hunter2&url=#{CGI.escape source}&ref" - body = matches.map { |post| { post_id: post.id } }.to_json - - stub_request(:get, url).to_return(body: body) + def mock_iqdb_matches(matches) + Danbooru.config.stubs(:iqdbs_server).returns("http://localhost:3004") + response = HTTP::Response.new(status: 200, body: matches.to_json, headers: { "Content-Type": "application/json" }, version: "1.1") + HTTP::Client.any_instance.stubs(:post).returns(response) end end From 3cdf679202e377b5e5797760ca34973909aedd34 Mon Sep 17 00:00:00 2001 From: evazion Date: Sun, 14 Jun 2020 01:14:53 -0500 Subject: [PATCH 007/173] http: automatically follow redirects. * Automatically follow redirects (up to 5, return a synthetic 598 error after that). * Add `put` and `timeout` methods. * Add tests. --- app/logical/danbooru/http.rb | 22 +++++++++++++-- test/unit/danbooru_http_test.rb | 49 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 test/unit/danbooru_http_test.rb diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index 6e0b21711..d80c869c9 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -1,17 +1,22 @@ module Danbooru class Http DEFAULT_TIMEOUT = 3 + MAX_REDIRECTS = 5 attr_writer :cache, :http class << self - delegate :get, :post, :delete, :cache, :auth, :basic_auth, :headers, to: :new + delegate :get, :put, :post, :delete, :cache, :timeout, :auth, :basic_auth, :headers, to: :new end def get(url, **options) request(:get, url, **options) end + def put(url, **options) + request(:get, url, **options) + end + def post(url, **options) request(:post, url, **options) end @@ -24,6 +29,10 @@ module Danbooru dup.tap { |o| o.cache = expiry.to_i } end + def timeout(*args) + dup.tap { |o| o.http = o.http.timeout(*args) } + end + def auth(*args) dup.tap { |o| o.http = o.http.auth(*args) } end @@ -44,9 +53,11 @@ module Danbooru else raw_request(method, url, **options) end + rescue HTTP::Redirector::TooManyRedirectsError + ::HTTP::Response.new(status: 598, body: "", version: "1.1") rescue HTTP::TimeoutError # return a synthetic http error on connection timeouts - ::HTTP::Response.new(status: 522, body: "", version: "1.1") + ::HTTP::Response.new(status: 599, body: "", version: "1.1") end def cached_request(method, url, **options) @@ -65,7 +76,12 @@ module Danbooru end def http - @http ||= ::HTTP.timeout(DEFAULT_TIMEOUT).use(:auto_inflate).headers(Danbooru.config.http_headers).headers("Accept-Encoding" => "gzip") + @http ||= ::HTTP. + follow(max_hops: MAX_REDIRECTS). + timeout(DEFAULT_TIMEOUT). + use(:auto_inflate). + headers(Danbooru.config.http_headers). + headers("Accept-Encoding" => "gzip") end end end diff --git a/test/unit/danbooru_http_test.rb b/test/unit/danbooru_http_test.rb new file mode 100644 index 000000000..42e585bef --- /dev/null +++ b/test/unit/danbooru_http_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' + +class DanbooruHttpTest < ActiveSupport::TestCase + context "Danbooru::Http" do + context "#get method" do + should "work for all basic methods" do + %i[get put post delete].each do |method| + response = Danbooru::Http.send(method, "https://httpbin.org/status/200") + assert_equal(200, response.status) + end + end + + should "follow redirects" do + response = Danbooru::Http.get("https://httpbin.org/absolute-redirect/3") + assert_equal(200, response.status) + end + + should "fail if redirected too many times" do + response = Danbooru::Http.get("https://httpbin.org/absolute-redirect/10") + assert_equal(598, response.status) + end + + should "fail if the request takes too long to connect" do + response = Danbooru::Http.timeout(1).get("https://httpbin.org/delay/5") + assert_equal(599, response.status) + end + + should "fail if the request takes too long to download" do + response = Danbooru::Http.timeout(1).get("https://httpbin.org/drip?duration=5&numbytes=5") + assert_equal(599, response.status) + end + + should "automatically decompress gzipped responses" do + response = Danbooru::Http.get("https://httpbin.org/gzip") + assert_equal(200, response.status) + assert_equal(true, response.parse["gzipped"]) + end + + should "cache requests" do + response1 = Danbooru::Http.cache(1.minute).get("https://httpbin.org/uuid") + assert_equal(200, response1.status) + + response2 = Danbooru::Http.cache(1.minute).get("https://httpbin.org/uuid") + assert_equal(200, response2.status) + assert_equal(response2.body, response1.body) + end + end + end +end From 9f0e85e1b5af8b364f3612aa63f97663050fcdcb Mon Sep 17 00:00:00 2001 From: nonamethanks Date: Tue, 2 Jun 2020 12:29:04 +0200 Subject: [PATCH 008/173] Refactor nicoseiga strategy * Get rid of mechanize, fully switch to Danbooru::Http * Switch to mobile api, improving speed * Merge main and manga clients * Add full support for manga pages * Add support for anonymous and r-15 images * Don't fail when attempting to upload oekaki direct links * Various misc fixes --- app/logical/nico_seiga_api_client.rb | 136 ++++++----- app/logical/nico_seiga_manga_api_client.rb | 60 ----- app/logical/sources/strategies/nico_seiga.rb | 239 +++++++++---------- test/unit/sources/nico_seiga_manga_test.rb | 23 -- test/unit/sources/nico_seiga_test.rb | 105 ++++++-- 5 files changed, 278 insertions(+), 285 deletions(-) delete mode 100644 app/logical/nico_seiga_manga_api_client.rb delete mode 100644 test/unit/sources/nico_seiga_manga_test.rb diff --git a/app/logical/nico_seiga_api_client.rb b/app/logical/nico_seiga_api_client.rb index cb596a34e..4290dae96 100644 --- a/app/logical/nico_seiga_api_client.rb +++ b/app/logical/nico_seiga_api_client.rb @@ -1,83 +1,101 @@ class NicoSeigaApiClient extend Memoist - BASE_URL = "http://seiga.nicovideo.jp/api" - attr_reader :illust_id + XML_API = "https://seiga.nicovideo.jp/api" - def self.agent - mech = Mechanize.new - mech.redirect_ok = false - mech.keep_alive = false + def initialize(work_id:, type:) + @work_id = work_id + @work_type = type + end - session = Cache.get("nico-seiga-session") - if session - cookie = Mechanize::Cookie.new("user_session", session) - cookie.domain = ".nicovideo.jp" - cookie.path = "/" - mech.cookie_jar.add(cookie) - else - mech.get("https://account.nicovideo.jp/login") do |page| - page.form_with(:id => "login_form") do |form| - form["mail_tel"] = Danbooru.config.nico_seiga_login - form["password"] = Danbooru.config.nico_seiga_password - end.click_button - end - session = mech.cookie_jar.cookies.select {|c| c.name == "user_session"}.first - if session - Cache.put("nico-seiga-session", session.value, 1.week) - else - raise "Session not found" + def image_ids + if @work_type == "illust" + [api_response["id"]] + elsif @work_type == "manga" + manga_api_response.map do |x| + x["meta"]["source_url"].match(%r{/thumb/(\d+)\w}i).captures[0] end end - - # This cookie needs to be set to allow viewing of adult works - cookie = Mechanize::Cookie.new("skip_fetish_warning", "1") - cookie.domain = "seiga.nicovideo.jp" - cookie.path = "/" - mech.cookie_jar.add(cookie) - - mech.redirect_ok = true - mech - end - - def initialize(illust_id:, user_id: nil) - @illust_id = illust_id - @user_id = user_id - end - - def image_id - illust_xml["response"]["image"]["id"].to_i - end - - def user_id - @user_id || illust_xml["response"]["image"]["user_id"].to_i end def title - illust_xml["response"]["image"]["title"] + api_response["title"] end - def desc - illust_xml["response"]["image"]["description"] + def description + api_response["description"] end - def moniker - artist_xml["response"]["user"]["nickname"] + def tags + api_response.dig("tag_list", "tag").to_a.map { |t| t["name"] }.compact end - def illust_xml - get("#{BASE_URL}/illust/info?id=#{illust_id}") + def user_id + api_response["user_id"] end - def artist_xml - get("#{BASE_URL}/user/info?id=#{user_id}") + def user_name + if @work_type == "illust" + api_response["nickname"] + elsif @work_type == "manga" + user_api_response(user_id)["nickname"] + end + end + + def api_response + if @work_type == "illust" + resp = get("https://sp.seiga.nicovideo.jp/ajax/seiga/im#{@work_id}") + return {} if resp.blank? || resp.code.to_i == 404 + api_response = JSON.parse(resp)["target_image"] + + elsif @work_type == "manga" + resp = Danbooru::Http.cache(1.minute).get("#{XML_API}/theme/info?id=#{@work_id}") + return {} if resp.blank? || resp.code.to_i == 404 + api_response = Hash.from_xml(resp.to_s)["response"]["theme"] + end + + api_response || {} + rescue JSON::ParserError + {} + end + + def manga_api_response + resp = get("https://ssl.seiga.nicovideo.jp/api/v1/app/manga/episodes/#{@work_id}/frames") + return {} if resp.blank? || resp.code.to_i == 404 + JSON.parse(resp)["data"]["result"] + rescue JSON::ParserError + {} + end + + def user_api_response(user_id) + resp = Danbooru::Http.cache(1.minute).get("#{XML_API}/user/info?id=#{user_id}") + return {} if resp.blank? || resp.code.to_i == 404 + Hash.from_xml(resp.to_s)["response"]["user"] end def get(url) - response = Danbooru::Http.cache(1.minute).get(url) - raise "nico seiga api call failed (code=#{response.code}, body=#{response.body})" if response.code != 200 + cookie_header = Cache.get("nicoseiga-cookie-header") || regenerate_cookie_header - Hash.from_xml(response.to_s) + resp = Danbooru::Http.headers({Cookie: cookie_header}).cache(1.minute).get(url) + + if resp.headers["Location"] =~ %r{seiga\.nicovideo\.jp/login/}i + cookie_header = regenerate_cookie_header + resp = Danbooru::Http.headers({Cookie: cookie_header}).cache(1.minute).get(url) + end + + resp end - memoize :artist_xml, :illust_xml + def regenerate_cookie_header + form = { + mail_tel: Danbooru.config.nico_seiga_login, + password: Danbooru.config.nico_seiga_password + } + resp = Danbooru::Http.post("https://account.nicovideo.jp/api/v1/login", form: form) + cookies = resp.cookies.map { |c| c.name + "=" + c.value } + cookies << "accept_fetish_warning=2" + + Cache.put("nicoseiga-cookie-header", cookies.join(";"), 1.week) + end + + memoize :api_response, :manga_api_response, :user_api_response end diff --git a/app/logical/nico_seiga_manga_api_client.rb b/app/logical/nico_seiga_manga_api_client.rb deleted file mode 100644 index 4bdb6a7f4..000000000 --- a/app/logical/nico_seiga_manga_api_client.rb +++ /dev/null @@ -1,60 +0,0 @@ -class NicoSeigaMangaApiClient - extend Memoist - BASE_URL = "https://seiga.nicovideo.jp/api" - attr_reader :theme_id - - def initialize(theme_id) - @theme_id = theme_id - end - - def user_id - theme_info_xml["response"]["theme"]["user_id"].to_i - end - - def title - theme_info_xml["response"]["theme"]["title"] - end - - def desc - theme_info_xml["response"]["theme"]["description"] - end - - def moniker - artist_xml["response"]["user"]["nickname"] - end - - def image_ids - images = theme_data_xml["response"]["image_list"]["image"] - images = [images] unless images.is_a?(Array) - images.map {|x| x["id"]} - end - - def tags - theme_info_xml["response"]["theme"]["tag_list"]["tag"].map {|x| x["name"]} - end - - def theme_data_xml - uri = "#{BASE_URL}/theme/data?theme_id=#{theme_id}" - body = NicoSeigaApiClient.agent.get(uri).body - Hash.from_xml(body) - end - - def theme_info_xml - uri = "#{BASE_URL}/theme/info?id=#{theme_id}" - body = NicoSeigaApiClient.agent.get(uri).body - Hash.from_xml(body) - end - - def artist_xml - get("#{BASE_URL}/user/info?id=#{user_id}") - end - - def get(url) - response = Danbooru::Http.cache(1.minute).get(url) - raise "nico seiga api call failed (code=#{response.code}, body=#{response.body})" if response.code != 200 - - Hash.from_xml(response.to_s) - end - - memoize :theme_data_xml, :theme_info_xml, :artist_xml -end diff --git a/app/logical/sources/strategies/nico_seiga.rb b/app/logical/sources/strategies/nico_seiga.rb index 32a3fd65f..3cefcface 100644 --- a/app/logical/sources/strategies/nico_seiga.rb +++ b/app/logical/sources/strategies/nico_seiga.rb @@ -1,25 +1,51 @@ -# Image Direct URL +# Direct URL # * https://lohas.nicoseiga.jp/o/971eb8af9bbcde5c2e51d5ef3a2f62d6d9ff5552/1589933964/3583893 # * http://lohas.nicoseiga.jp/priv/3521156?e=1382558156&h=f2e089256abd1d453a455ec8f317a6c703e2cedf # * http://lohas.nicoseiga.jp/priv/b80f86c0d8591b217e7513a9e175e94e00f3c7a1/1384936074/3583893 +# +# * http://lohas.nicoseiga.jp/material/5746c5/4459092 +# +# (Manga direct url) +# * https://lohas.nicoseiga.jp/priv/f5b8966fd53bf7e06cccff9fbb2c4eef62877538/1590752727/8947170 +# +# Samples +# * http://lohas.nicoseiga.jp/thumb/2163478i? +# * https://lohas.nicoseiga.jp/thumb/8947170p +# +## The direct urls and samples above can belong to both illust and manga. +## There's two ways to tell them apart: +## * visit the /source/ equivalent: illusts redirect to the /o/ intermediary page, manga redirect to /priv/ directly +## * try an api call: illusts will succeed, manga will fail +# +# Source Link # * http://seiga.nicovideo.jp/image/source?id=3312222 # -# Image Page URL +# Illust Page URL # * https://seiga.nicovideo.jp/seiga/im3521156 +# * https://seiga.nicovideo.jp/seiga/im520647 (anonymous artist) # # Manga Page URL # * http://seiga.nicovideo.jp/watch/mg316708 +# +# Video Page URL (not supported) +# * https://www.nicovideo.jp/watch/sm36465441 +# +# Oekaki +# * https://dic.nicovideo.jp/oekaki/52833.png module Sources module Strategies class NicoSeiga < Base - URL = %r!\Ahttps?://(?:\w+\.)?nico(?:seiga|video)\.jp! - DIRECT1 = %r!\Ahttps?://lohas\.nicoseiga\.jp/priv/[0-9a-f]+! - DIRECT2 = %r!\Ahttps?://lohas\.nicoseiga\.jp/o/[0-9a-f]+/\d+/\d+! - DIRECT3 = %r!\Ahttps?://seiga\.nicovideo\.jp/images/source/\d+! - PAGE = %r!\Ahttps?://seiga\.nicovideo\.jp/seiga/im(\d+)!i - PROFILE = %r!\Ahttps?://seiga\.nicovideo\.jp/user/illust/(\d+)!i - MANGA_PAGE = %r!\Ahttps?://seiga\.nicovideo\.jp/watch/mg(\d+)!i + DIRECT = %r{\Ahttps?://lohas\.nicoseiga\.jp/(priv|o)/(?:\w+/\d+/)?(?\d+)(?:\?.+)?}i + SOURCE = %r{\Ahttps?://seiga\.nicovideo\.jp/image/source(?:/|\?id=)(?\d+)}i + + ILLUST_THUMB = %r{\Ahttps?://lohas\.nicoseiga\.jp/thumb/(?\d+)i}i + MANGA_THUMB = %r{\Ahttps?://lohas\.nicoseiga\.jp/thumb/(?\d+)p}i + + ILLUST_PAGE = %r{\Ahttps?://(?:sp\.)?seiga\.nicovideo\.jp/seiga/im(?\d+)}i + MANGA_PAGE = %r{\Ahttps?://(?:sp\.)?seiga\.nicovideo\.jp/watch/mg(?\d+)}i + + PROFILE_PAGE = %r{\Ahttps?://seiga\.nicovideo\.jp/user/illust/(?\d+)}i def domains ["nicoseiga.jp", "nicovideo.jp"] @@ -30,160 +56,125 @@ module Sources end def image_urls - if url =~ DIRECT1 - return [url] + urls = [] + return urls if api_client&.api_response.blank? + + if image_id.present? + urls << "https://seiga.nicovideo.jp/image/source/#{image_id}" + elsif illust_id.present? + urls << "https://seiga.nicovideo.jp/image/source/#{illust_id}" + elsif manga_id.present? && api_client.image_ids.present? + urls += api_client.image_ids.map { |id| "https://seiga.nicovideo.jp/image/source/#{id}" } + end + urls + end + + def image_url + return if image_urls.blank? + return url if api_client.blank? + + img = case url + when DIRECT then "https://seiga.nicovideo.jp/image/source/#{image_id_from_url(url)}" + when SOURCE then url + else image_urls.first end - if theme_id - return api_client.image_ids.map do |image_id| - "https://seiga.nicovideo.jp/image/source/#{image_id}" - end - end - - link = page.search("a#illust_link") - - if link.any? - image_url = "http://seiga.nicovideo.jp" + link[0]["href"] - page = agent.get(image_url) # need to follow this redirect while logged in or it won't work - - if page.is_a?(Mechanize::Image) - return [page.uri.to_s] - end - - images = page.search("div.illust_view_big").select {|x| x["data-src"] =~ /\/priv\//} - - if images.any? - return ["http://lohas.nicoseiga.jp" + images[0]["data-src"]] - end - end - - raise "image url not found for (#{url}, #{referer_url})" + resp = api_client.get(img) + resp.headers["Location"]&.gsub(%r{nicoseiga.jp/o/}i, 'nicoseiga.jp/priv/') end def page_url - [url, referer_url].each do |x| - if x =~ %r!\Ahttps?://lohas\.nicoseiga\.jp/o/[a-f0-9]+/\d+/(\d+)! - return "http://seiga.nicovideo.jp/seiga/im#{$1}" - end - - if x =~ %r{\Ahttps?://lohas\.nicoseiga\.jp/priv/(\d+)\?e=\d+&h=[a-f0-9]+}i - return "http://seiga.nicovideo.jp/seiga/im#{$1}" - end - - if x =~ %r{\Ahttps?://lohas\.nicoseiga\.jp/priv/[a-f0-9]+/\d+/(\d+)}i - return "http://seiga.nicovideo.jp/seiga/im#{$1}" - end - - if x =~ %r{\Ahttps?://lohas\.nicoseiga\.jp/priv/(\d+)}i - return "http://seiga.nicovideo.jp/seiga/im#{$1}" - end - - if x =~ %r{\Ahttps?://lohas\.nicoseiga\.jp//?thumb/(\d+)i?}i - return "http://seiga.nicovideo.jp/seiga/im#{$1}" - end - - if x =~ %r{/seiga/im\d+} - return x - end - - if x =~ %r{/watch/mg\d+} - return x - end - - if x =~ %r{/image/source\?id=(\d+)} - return "http://seiga.nicovideo.jp/seiga/im#{$1}" - end + if illust_id.present? + "https://seiga.nicovideo.jp/seiga/im#{illust_id}" + elsif manga_id.present? + "https://seiga.nicovideo.jp/watch/mg#{manga_id}" + elsif image_id.present? + "https://seiga.nicovideo.jp/image/source/#{image_id}" end - - return super - end - - def canonical_url - image_url end def profile_url - if url =~ PROFILE - return url - end + user_id = api_client&.user_id + return if user_id.blank? # artists can be anonymous "http://seiga.nicovideo.jp/user/illust/#{api_client.user_id}" end def artist_name - api_client.moniker + return if api_client.blank? + api_client.user_name end def artist_commentary_title + return if api_client.blank? api_client.title end def artist_commentary_desc - api_client.desc + return if api_client.blank? + api_client.description + end + + def dtext_artist_commentary_desc + DText.from_html(artist_commentary_desc).gsub(/[^\w]im(\d+)/, ' seiga #\1 ') end def normalize_for_source - if illust_id.present? - "https://seiga.nicovideo.jp/seiga/im#{illust_id}" - elsif theme_id.present? - "http://seiga.nicovideo.jp/watch/mg#{theme_id}" + # There's no way to tell apart illust from manga from the direct image url alone. What's worse, + # nicoseiga itself doesn't know how to normalize back to manga, so if it's not an illust type then + # it's impossible to get the original manga page back from the image url alone. + # /source/ links on the other hand correctly redirect, hence we use them to normalize saved direct sources. + if url =~ DIRECT + "https://seiga.nicovideo.jp/image/source/#{image_id}" + else + page_url end end def tag_name + return if api_client&.user_id.blank? "nicoseiga#{api_client.user_id}" end def tags - string = page.at("meta[name=keywords]").try(:[], "content") || "" - string.split(/,/).map do |name| - [name, "https://seiga.nicovideo.jp/tag/#{CGI.escape(name)}"] + return [] if api_client.blank? + + base_url = "https://seiga.nicovideo.jp/" + base_url += "manga/" if manga_id.present? + base_url += "tag/" + + api_client.tags.map do |name| + [name, base_url + CGI.escape(name)] end end - memoize :tags + + def image_id + image_id_from_url(url) + end + + def image_id_from_url(url) + url[DIRECT, :image_id] || url[SOURCE, :image_id] || url[MANGA_THUMB, :image_id] + end + + def illust_id + urls.map { |u| u[ILLUST_PAGE, :illust_id] || u[ILLUST_THUMB, :illust_id] }.compact.first + end + + def manga_id + urls.compact.map { |u| u[MANGA_PAGE, :manga_id] }.compact.first + end def api_client - if illust_id - NicoSeigaApiClient.new(illust_id: illust_id) - elsif theme_id - NicoSeigaMangaApiClient.new(theme_id) + if illust_id.present? + NicoSeigaApiClient.new(work_id: illust_id, type: "illust") + elsif manga_id.present? + NicoSeigaApiClient.new(work_id: manga_id, type: "manga") + elsif image_id.present? + # We default to illust to attempt getting the api anyway + NicoSeigaApiClient.new(work_id: image_id, type: "illust") end end memoize :api_client - - def illust_id - if page_url =~ PAGE - return $1.to_i - end - - return nil - end - - def theme_id - if page_url =~ MANGA_PAGE - return $1.to_i - end - - return nil - end - - def page - doc = agent.get(page_url) - - if doc.search("a#link_btn_login").any? - # Session cache is invalid, clear it and log in normally. - Cache.delete("nico-seiga-session") - doc = agent.get(page_url) - end - - doc - end - memoize :page - - def agent - NicoSeigaApiClient.agent - end - memoize :agent end end end diff --git a/test/unit/sources/nico_seiga_manga_test.rb b/test/unit/sources/nico_seiga_manga_test.rb deleted file mode 100644 index fe5bdc3b8..000000000 --- a/test/unit/sources/nico_seiga_manga_test.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'test_helper' - -module Sources - class NicoSeigaTest < ActiveSupport::TestCase - context "The source site for nico seiga" do - setup do - @site = Sources::Strategies.find("http://seiga.nicovideo.jp/watch/mg316708", "http://seiga.nicovideo.jp/watch/mg316708") - end - - should "find the image urls" do - assert_equal(["https://seiga.nicovideo.jp/image/source/8100968", "https://seiga.nicovideo.jp/image/source/8100969", "https://seiga.nicovideo.jp/image/source/8100970", "https://seiga.nicovideo.jp/image/source/8100971", "https://seiga.nicovideo.jp/image/source/8100972", "https://seiga.nicovideo.jp/image/source/8100973", "https://seiga.nicovideo.jp/image/source/8100974", "https://seiga.nicovideo.jp/image/source/8100975"], @site.image_urls) - end - - should "find the page url" do - assert_equal("http://seiga.nicovideo.jp/watch/mg316708", @site.page_url) - end - - should "find the artist name" do - assert_not_nil(@site.artist_name) - end - end - end -end diff --git a/test/unit/sources/nico_seiga_test.rb b/test/unit/sources/nico_seiga_test.rb index 328706281..9ab04edb4 100644 --- a/test/unit/sources/nico_seiga_test.rb +++ b/test/unit/sources/nico_seiga_test.rb @@ -6,32 +6,52 @@ module Sources setup do @site_1 = Sources::Strategies.find("http://lohas.nicoseiga.jp/o/910aecf08e542285862954017f8a33a8c32a8aec/1433298801/4937663") @site_2 = Sources::Strategies.find("http://seiga.nicovideo.jp/seiga/im4937663") - @site_3 = Sources::Strategies.find("http://seiga.nicovideo.jp/watch/mg376206") + @site_3 = Sources::Strategies.find("https://seiga.nicovideo.jp/watch/mg470189?track=ct_episode") end should "get the profile" do assert_equal("http://seiga.nicovideo.jp/user/illust/7017777", @site_1.profile_url) assert_equal("http://seiga.nicovideo.jp/user/illust/7017777", @site_2.profile_url) + assert_equal("http://seiga.nicovideo.jp/user/illust/20797022", @site_3.profile_url) end should "get the artist name" do assert_equal("osamari", @site_1.artist_name) assert_equal("osamari", @site_2.artist_name) + assert_equal("風呂", @site_3.artist_name) end should "get the artist commentary" do assert_equal("コジコジ", @site_2.artist_commentary_title) assert_equal("コジコジのドット絵\nこんなかわいらしい容姿で毒を吐くコジコジが堪らん(切実)", @site_2.artist_commentary_desc) + + assert_equal("ハコ女子 1ハコ目", @site_3.artist_commentary_title) + assert_equal("同じクラスの箱田さんはいつもハコを被っている。しかしてその素顔は…? twitter(@hakojoshi1)にてだいたい毎日更新中。こっちだともうちょっと先まで読めるよ。", @site_3.artist_commentary_desc) end - should "get the image url" do - assert_match(/^http:\/\/lohas\.nicoseiga\.jp\/priv\//, @site_1.image_url) - assert_match(/^http:\/\/lohas\.nicoseiga\.jp\/priv\//, @site_2.image_url) + should "get the image url(s)" do + assert_match(%r{^https?://lohas\.nicoseiga\.jp/priv/}, @site_1.image_url) + assert_match(%r{^https?://lohas\.nicoseiga\.jp/priv/}, @site_2.image_url) + + expected = %w[ + https://seiga.nicovideo.jp/image/source/10315315 + https://seiga.nicovideo.jp/image/source/10315318 + https://seiga.nicovideo.jp/image/source/10315319 + https://seiga.nicovideo.jp/image/source/10315320 + https://seiga.nicovideo.jp/image/source/10315321 + https://seiga.nicovideo.jp/image/source/10315322 + https://seiga.nicovideo.jp/image/source/10315323 + https://seiga.nicovideo.jp/image/source/10315324 + https://seiga.nicovideo.jp/image/source/10315316 + ] + assert_equal(expected.sort, @site_3.image_urls.sort) + assert_match(%r{^https?://lohas\.nicoseiga\.jp/priv/}, @site_3.image_url) end should "get the canonical url" do - assert_match(%r!\Ahttps?://lohas\.nicoseiga\.jp/priv/\h{40}/\d+/4937663!, @site_1.canonical_url) - assert_match(%r!\Ahttps?://lohas\.nicoseiga\.jp/priv/\h{40}/\d+/4937663!, @site_2.canonical_url) + assert_equal("https://seiga.nicovideo.jp/image/source/4937663", @site_1.canonical_url) + assert_equal("https://seiga.nicovideo.jp/seiga/im4937663", @site_2.canonical_url) + assert_equal("https://seiga.nicovideo.jp/watch/mg470189", @site_3.canonical_url) end should "get the tags" do @@ -42,23 +62,70 @@ module Sources assert_not(@site_2.tags.empty?) first_tag = @site_2.tags.first assert_equal(["アニメ", "https://seiga.nicovideo.jp/tag/%E3%82%A2%E3%83%8B%E3%83%A1"], first_tag) + + assert_not(@site_3.tags.empty?) + first_tag = @site_3.tags.first + assert_equal(["4コマ漫画", "https://seiga.nicovideo.jp/manga/tag/4%E3%82%B3%E3%83%9E%E6%BC%AB%E7%94%BB"], first_tag) end should "convert a page into a json representation" do - assert_nothing_raised do - @site_1.to_h - end - assert_nothing_raised do - @site_2.to_h - end + assert_nothing_raised { @site_1.to_h } + assert_nothing_raised { @site_2.to_h } + assert_nothing_raised { @site_3.to_h } end should "work for a https://lohas.nicoseiga.jp/thumb/${id}i url" do site = Sources::Strategies.find("https://lohas.nicoseiga.jp/thumb/6844226i") - full_image_url = %r!https?://lohas.nicoseiga.jp/priv/[a-f0-9]{40}/[0-9]+/6844226! - assert_match(full_image_url, site.image_url) - assert_match(full_image_url, site.canonical_url) + assert_match(%r!https?://lohas.nicoseiga.jp/priv/[a-f0-9]{40}/[0-9]+/6844226!, site.image_url) + assert_match("https://seiga.nicovideo.jp/seiga/im6844226", site.canonical_url) + end + end + + context "A manga upload through bookmarklet" do + setup do + @url = "https://seiga.nicovideo.jp/image/source/9146749" + @ref = "https://seiga.nicovideo.jp/watch/mg389884" + @site = Sources::Strategies.find(@url, @ref) + end + + should "get the correct pic" do + assert_match(%r!https?://lohas.nicoseiga.jp/priv/[a-f0-9]{40}/[0-9]+/9146749!, @site.image_url) + end + + should "set the correct source" do + assert_equal(@ref, @site.canonical_url) + end + end + + context "A nicoseiga video" do + should "not raise anything" do + site = Sources::Strategies.find("https://www.nicovideo.jp/watch/sm36465441") + assert_nothing_raised { site.to_h } + end + end + + context "An anonymous picture" do + should "still work" do + site = Sources::Strategies.find("https://seiga.nicovideo.jp/seiga/im520647") + + assert_nothing_raised { site.to_h } + end + end + + context "An age-restricted picture" do + should "still work" do + site = Sources::Strategies.find("http://seiga.nicovideo.jp/seiga/im9208126") + + assert_match(%r!https?://lohas.nicoseiga.jp/priv/[a-f0-9]{40}/[0-9]+/9208126!, site.image_url) + assert_nothing_raised { site.to_h } + end + end + + context "An oekaki picture" do + should "still work" do + site = Sources::Strategies.find("https://dic.nicovideo.jp/oekaki/52833.png") + assert_nothing_raised { site.to_h } end end @@ -69,10 +136,10 @@ module Sources source3 = "http://lohas.nicoseiga.jp/o/910aecf08e542285862954017f8a33a8c32a8aec/1433298801/4937663" source4 = "http://seiga.nicovideo.jp/image/source?id=3312222" - assert_equal("https://seiga.nicovideo.jp/seiga/im3521156", Sources::Strategies.normalize_source(source1)) - assert_equal("https://seiga.nicovideo.jp/seiga/im3583893", Sources::Strategies.normalize_source(source2)) - assert_equal("https://seiga.nicovideo.jp/seiga/im4937663", Sources::Strategies.normalize_source(source3)) - assert_equal("https://seiga.nicovideo.jp/seiga/im3312222", Sources::Strategies.normalize_source(source4)) + assert_equal("https://seiga.nicovideo.jp/image/source/3521156", Sources::Strategies.normalize_source(source1)) + assert_equal("https://seiga.nicovideo.jp/image/source/3583893", Sources::Strategies.normalize_source(source2)) + assert_equal("https://seiga.nicovideo.jp/image/source/4937663", Sources::Strategies.normalize_source(source3)) + assert_equal("https://seiga.nicovideo.jp/image/source/3312222", Sources::Strategies.normalize_source(source4)) end should "avoid normalizing unnormalizable urls" do From 260bc997f6cdc7eb2a2861c251c11e46ee59140d Mon Sep 17 00:00:00 2001 From: nonamethanks Date: Tue, 2 Jun 2020 13:14:09 +0200 Subject: [PATCH 009/173] NicoSeiga: Add preview urls --- app/logical/sources/strategies/nico_seiga.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/logical/sources/strategies/nico_seiga.rb b/app/logical/sources/strategies/nico_seiga.rb index 3cefcface..385dd37f2 100644 --- a/app/logical/sources/strategies/nico_seiga.rb +++ b/app/logical/sources/strategies/nico_seiga.rb @@ -83,6 +83,19 @@ module Sources resp.headers["Location"]&.gsub(%r{nicoseiga.jp/o/}i, 'nicoseiga.jp/priv/') end + def preview_urls + if manga_id.present? + image_urls.map do |img| + id = image_id_from_url(img) + "https://lohas.nicoseiga.jp/thumb/#{id}p" + end + elsif illust_id.present? + ["https://lohas.nicoseiga.jp/thumb/#{illust_id}i"] + else + [] + end + end + def page_url if illust_id.present? "https://seiga.nicovideo.jp/seiga/im#{illust_id}" From 6fc4d3ec44273d264904693d7dba91f17e6285a2 Mon Sep 17 00:00:00 2001 From: nonamethanks Date: Wed, 3 Jun 2020 10:39:31 +0200 Subject: [PATCH 010/173] Nicoseiga: Add support for drm-served manga --- app/logical/nico_seiga_api_client.rb | 5 ++++- app/logical/sources/strategies/nico_seiga.rb | 9 ++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/logical/nico_seiga_api_client.rb b/app/logical/nico_seiga_api_client.rb index 4290dae96..d75aaade3 100644 --- a/app/logical/nico_seiga_api_client.rb +++ b/app/logical/nico_seiga_api_client.rb @@ -12,7 +12,10 @@ class NicoSeigaApiClient [api_response["id"]] elsif @work_type == "manga" manga_api_response.map do |x| - x["meta"]["source_url"].match(%r{/thumb/(\d+)\w}i).captures[0] + case x["meta"]["source_url"] + when %r{/thumb/(\d+)\w}i then Regexp.last_match(1) + when %r{nicoseiga\.cdn\.nimg\.jp/drm/image/\w+/(\d+)\w}i then Regexp.last_match(1) + end end end end diff --git a/app/logical/sources/strategies/nico_seiga.rb b/app/logical/sources/strategies/nico_seiga.rb index 385dd37f2..714763d3e 100644 --- a/app/logical/sources/strategies/nico_seiga.rb +++ b/app/logical/sources/strategies/nico_seiga.rb @@ -84,15 +84,10 @@ module Sources end def preview_urls - if manga_id.present? - image_urls.map do |img| - id = image_id_from_url(img) - "https://lohas.nicoseiga.jp/thumb/#{id}p" - end - elsif illust_id.present? + if illust_id.present? ["https://lohas.nicoseiga.jp/thumb/#{illust_id}i"] else - [] + image_urls end end From 5b186f30720d5de7bccb7bd2112e6ad722da608d Mon Sep 17 00:00:00 2001 From: nonamethanks Date: Mon, 15 Jun 2020 04:01:34 +0200 Subject: [PATCH 011/173] Support for new nicoseiga cdn domain --- app/logical/sources/strategies/nico_seiga.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/logical/sources/strategies/nico_seiga.rb b/app/logical/sources/strategies/nico_seiga.rb index 714763d3e..e63094bb9 100644 --- a/app/logical/sources/strategies/nico_seiga.rb +++ b/app/logical/sources/strategies/nico_seiga.rb @@ -2,6 +2,8 @@ # * https://lohas.nicoseiga.jp/o/971eb8af9bbcde5c2e51d5ef3a2f62d6d9ff5552/1589933964/3583893 # * http://lohas.nicoseiga.jp/priv/3521156?e=1382558156&h=f2e089256abd1d453a455ec8f317a6c703e2cedf # * http://lohas.nicoseiga.jp/priv/b80f86c0d8591b217e7513a9e175e94e00f3c7a1/1384936074/3583893 +# * https://dcdn.cdn.nimg.jp/priv/62a56a7f67d3d3746ae5712db9cac7d465f4a339/1592186183/10466669 +# * https://dcdn.cdn.nimg.jp/nicoseiga/lohas/o/8ba0a9b2ea34e1ef3b5cc50785bd10cd63ec7e4a/1592187477/10466669 # # * http://lohas.nicoseiga.jp/material/5746c5/4459092 # @@ -37,6 +39,7 @@ module Sources module Strategies class NicoSeiga < Base DIRECT = %r{\Ahttps?://lohas\.nicoseiga\.jp/(priv|o)/(?:\w+/\d+/)?(?\d+)(?:\?.+)?}i + CDN_DIRECT = %r{\Ahttps?://dcdn\.cdn\.nimg\.jp/.+/\w+/\d+/(?\d+)}i SOURCE = %r{\Ahttps?://seiga\.nicovideo\.jp/image/source(?:/|\?id=)(?\d+)}i ILLUST_THUMB = %r{\Ahttps?://lohas\.nicoseiga\.jp/thumb/(?\d+)i}i @@ -74,13 +77,17 @@ module Sources return url if api_client.blank? img = case url - when DIRECT then "https://seiga.nicovideo.jp/image/source/#{image_id_from_url(url)}" + when DIRECT || CDN_DIRECT then "https://seiga.nicovideo.jp/image/source/#{image_id_from_url(url)}" when SOURCE then url else image_urls.first end resp = api_client.get(img) - resp.headers["Location"]&.gsub(%r{nicoseiga.jp/o/}i, 'nicoseiga.jp/priv/') + if resp.headers["Location"] =~ %r{https?://.+/(\w+/\d+/\d+)\z}i + "https://lohas.nicoseiga.jp/priv/#{$1}" + else + img + end end def preview_urls @@ -161,7 +168,7 @@ module Sources end def image_id_from_url(url) - url[DIRECT, :image_id] || url[SOURCE, :image_id] || url[MANGA_THUMB, :image_id] + url[DIRECT, :image_id] || url[SOURCE, :image_id] || url[MANGA_THUMB, :image_id] || url[CDN_DIRECT, :image_id] end def illust_id From ec5aa6c6620e068de4e926902cd392f53cf7312b Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 15 Jun 2020 16:29:48 -0500 Subject: [PATCH 012/173] tests: fix saved search index action test. This passed in development but failed in CI because SavedSearch.redis used the live Redis server, which worked by accident as long as you had a Redis server running. --- test/functional/saved_searches_controller_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/saved_searches_controller_test.rb b/test/functional/saved_searches_controller_test.rb index f5a703579..bc390dd08 100644 --- a/test/functional/saved_searches_controller_test.rb +++ b/test/functional/saved_searches_controller_test.rb @@ -5,6 +5,7 @@ class SavedSearchesControllerTest < ActionDispatch::IntegrationTest setup do @user = create(:user) @saved_search = create(:saved_search, user: @user) + SavedSearch.stubs(:redis).returns(MockRedis.new) end context "index action" do From 6a7d25591b58858a83f297e2e8679b20e4d5012a Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 15 Jun 2020 15:57:54 -0500 Subject: [PATCH 013/173] ci: use more permissive rubocop / codeclimate settings. --- .codeclimate.yml | 18 +++++++++++++----- .rubocop.yml | 15 ++++++++++++--- test/.rubocop.yml | 5 +++++ 3 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 test/.rubocop.yml diff --git a/.codeclimate.yml b/.codeclimate.yml index dbb000b8a..646bfcd16 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,17 +1,25 @@ version: 2 checks: argument-count: + enabled: false + complex-logic: config: - threshold: 4 + threshold: 8 file-lines: config: - threshold: 500 + threshold: 1000 + method-complexity: + config: + threshold: 15 method-count: - config: - threshold: 40 + enabled: false method-lines: + enabled: false + nested-control-flow: config: - threshold: 100 + threshold: 4 + return-statements: + enabled: false plugins: eslint: enabled: true diff --git a/.rubocop.yml b/.rubocop.yml index 001265032..932920c18 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,8 +1,9 @@ +# some of these settings are overriden in also test/.rubocop.yml + require: - rubocop-rails AllCops: - TargetRubyVersion: 2.7.0 NewCops: enable Exclude: - "bin/*" @@ -41,14 +42,22 @@ Layout/SpaceInsideHashLiteralBraces: Metrics/AbcSize: Enabled: false +Metrics/BlockLength: + Max: 50 + ExcludedMethods: + - concerning + - context + - should + Metrics/BlockNesting: + CountBlocks: false Max: 4 Metrics/ClassLength: Max: 500 Metrics/CyclomaticComplexity: - Max: 10 + Enabled: false Metrics/MethodLength: Max: 100 @@ -60,7 +69,7 @@ Metrics/ParameterLists: Max: 4 Metrics/PerceivedComplexity: - Enabled: false + Max: 20 Lint/InheritException: EnforcedStyle: standard_error diff --git a/test/.rubocop.yml b/test/.rubocop.yml new file mode 100644 index 000000000..f278ac69f --- /dev/null +++ b/test/.rubocop.yml @@ -0,0 +1,5 @@ +inherit_from: + - ../.rubocop.yml + +Metrics/ClassLength: + Enabled: false From f60b184a12fa6e2478b292e44390a32329f7cf41 Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 15 Jun 2020 16:44:41 -0500 Subject: [PATCH 014/173] ci: don't trigger twice on pull requests. --- .github/workflows/test.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 52d1c81c9..b8fe26966 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,6 +1,13 @@ name: Test -on: [push, pull_request] +# Trigger on pushes to master or pull requests to master, but not both. +on: + push: + branches: + - master + pull_request: + branches: + - master jobs: test: From a615a28b1b078e33a98b87be1048b55a2a78be0c Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 15 Jun 2020 16:45:15 -0500 Subject: [PATCH 015/173] ci: disable warnings during tests. --- .github/workflows/test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b8fe26966..fc5f3a46b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,6 +17,8 @@ jobs: env: DEBIAN_FRONTEND: noninteractive PARALLEL_WORKERS: 8 # number of parallel tests to run + RUBYOPT: -W0 # silence ruby warnings + VIPS_WARNING: 0 # silence libvips warnings # Code Climate configuration. https://docs.codeclimate.com/docs/finding-your-test-coverage-token CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} From 107729e17fe3270993d2b16f3c1c250820560976 Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 15 Jun 2020 17:21:41 -0500 Subject: [PATCH 016/173] ci: enable codeclimate reporter debug output. --- .github/workflows/test.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fc5f3a46b..27e3cb26d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,4 +1,4 @@ -name: Test +name: Github # Trigger on pushes to master or pull requests to master, but not both. on: @@ -56,12 +56,6 @@ jobs: steps: - name: Check out code uses: actions/checkout@v2 -# - name: Save environment -# run: env | egrep "DANBOORU|DATABASE_URL" > ~/.env -# - name: Install docker-compose -# run: sudo apt-get update && sudo apt-get -y install docker-compose -# - name: Run tests -# run: docker-compose --env-file ~/.env -f config/docker/docker-compose.test.yaml -p danbooru up - name: Install OS dependencies run: | apt-get update @@ -82,4 +76,4 @@ jobs: - name: Run tests run: bin/rails test - name: Upload test coverage to Code Climate - run: ./test-reporter-latest-linux-amd64 after-build + run: ./test-reporter-latest-linux-amd64 after-build --debug From 4c56447d66b447ea8bba23e855b31f0c352df2fe Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 15 Jun 2020 17:32:59 -0500 Subject: [PATCH 017/173] ci: add codecov.io integration. --- .github/workflows/test.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 27e3cb26d..0c503fe4b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -25,6 +25,9 @@ jobs: GIT_COMMIT_SHA: ${{ github.sha }} GIT_BRANCH: ${{ github.ref }} + # https://docs.codecov.io/docs + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + DATABASE_URL: postgresql://danbooru:danbooru@postgres/danbooru ARCHIVE_DATABASE_URL: postgresql://danbooru:danbooru@postgres/danbooru DANBOORU_SECRET_KEY_BASE: 1234 @@ -77,3 +80,5 @@ jobs: run: bin/rails test - name: Upload test coverage to Code Climate run: ./test-reporter-latest-linux-amd64 after-build --debug + - name: Upload test coverage to Codecov.io + run: bash <(curl -s https://codecov.io/bash) From 52367c79e217d44599b948855f179bbb7900d060 Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 15 Jun 2020 18:11:23 -0500 Subject: [PATCH 018/173] ci: upload code coverage even when tests fail. --- .github/workflows/test.yaml | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0c503fe4b..53485a29a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,6 +14,10 @@ jobs: runs-on: ubuntu-latest container: ubuntu:20.04 + defaults: + run: + shell: bash + env: DEBIAN_FRONTEND: noninteractive PARALLEL_WORKERS: 8 # number of parallel tests to run @@ -25,9 +29,6 @@ jobs: GIT_COMMIT_SHA: ${{ github.sha }} GIT_BRANCH: ${{ github.ref }} - # https://docs.codecov.io/docs - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - DATABASE_URL: postgresql://danbooru:danbooru@postgres/danbooru ARCHIVE_DATABASE_URL: postgresql://danbooru:danbooru@postgres/danbooru DANBOORU_SECRET_KEY_BASE: 1234 @@ -59,26 +60,39 @@ jobs: steps: - name: Check out code uses: actions/checkout@v2 + - name: Install OS dependencies run: | apt-get update apt-get -y install --no-install-recommends build-essential ruby ruby-dev ruby-bundler git nodejs yarnpkg webpack ffmpeg mkvtoolnix libvips-dev libxml2-dev postgresql-server-dev-all wget ln -sf /usr/bin/yarnpkg /usr/bin/yarn + - name: Install Ruby dependencies run: BUNDLE_DEPLOYMENT=true bundle install --jobs 4 + - name: Install Javascript dependencies run: yarn install + - name: Prepare database run: config/docker/prepare-tests.sh + # https://docs.codeclimate.com/docs/configuring-test-coverage - name: Prepare test coverage for Code Climate run: | wget https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 chmod +x test-reporter-latest-linux-amd64 ./test-reporter-latest-linux-amd64 before-build + - name: Run tests run: bin/rails test + - name: Upload test coverage to Code Climate - run: ./test-reporter-latest-linux-amd64 after-build --debug + if: ${{ !cancelled() }} + run: GIT_COMMITTED_AT="$(date -u +%s)" ./test-reporter-latest-linux-amd64 after-build --debug + + # https://docs.codecov.io/docs - name: Upload test coverage to Codecov.io - run: bash <(curl -s https://codecov.io/bash) + if: ${{ !cancelled() }} + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} From 7868e5045e8b2e9258535fec9f5bfc57c779f2a3 Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 15 Jun 2020 22:07:41 -0500 Subject: [PATCH 019/173] nicoseiga: fix regression with http redirects. 3cdf67920 changed it so that Danbooru::Http follows redirects by default. This broke some things in the Nico Seiga strategy, so disable following redirects in the Nico Seiga API client for now. Also change it so that Danbooru::Http follows redirects after a POST request (by setting `strict: false`). Nico Seiga needs this because it sends a redirect after we POST the login form. --- app/logical/danbooru/http.rb | 8 ++++++-- app/logical/nico_seiga_api_client.rb | 16 ++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index d80c869c9..343d8d886 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -6,7 +6,7 @@ module Danbooru attr_writer :cache, :http class << self - delegate :get, :put, :post, :delete, :cache, :timeout, :auth, :basic_auth, :headers, to: :new + delegate :get, :put, :post, :delete, :cache, :follow, :timeout, :auth, :basic_auth, :headers, to: :new end def get(url, **options) @@ -29,6 +29,10 @@ module Danbooru dup.tap { |o| o.cache = expiry.to_i } end + def follow(*args) + dup.tap { |o| o.http = o.http.follow(*args) } + end + def timeout(*args) dup.tap { |o| o.http = o.http.timeout(*args) } end @@ -77,7 +81,7 @@ module Danbooru def http @http ||= ::HTTP. - follow(max_hops: MAX_REDIRECTS). + follow(strict: false, max_hops: MAX_REDIRECTS). timeout(DEFAULT_TIMEOUT). use(:auto_inflate). headers(Danbooru.config.http_headers). diff --git a/app/logical/nico_seiga_api_client.rb b/app/logical/nico_seiga_api_client.rb index d75aaade3..f5418ac76 100644 --- a/app/logical/nico_seiga_api_client.rb +++ b/app/logical/nico_seiga_api_client.rb @@ -2,9 +2,13 @@ class NicoSeigaApiClient extend Memoist XML_API = "https://seiga.nicovideo.jp/api" - def initialize(work_id:, type:) + attr_reader :http + + # XXX temp disable following redirects. + def initialize(work_id:, type:, http: Danbooru::Http.follow(nil)) @work_id = work_id @work_type = type + @http = http end def image_ids @@ -51,7 +55,7 @@ class NicoSeigaApiClient api_response = JSON.parse(resp)["target_image"] elsif @work_type == "manga" - resp = Danbooru::Http.cache(1.minute).get("#{XML_API}/theme/info?id=#{@work_id}") + resp = http.cache(1.minute).get("#{XML_API}/theme/info?id=#{@work_id}") return {} if resp.blank? || resp.code.to_i == 404 api_response = Hash.from_xml(resp.to_s)["response"]["theme"] end @@ -70,7 +74,7 @@ class NicoSeigaApiClient end def user_api_response(user_id) - resp = Danbooru::Http.cache(1.minute).get("#{XML_API}/user/info?id=#{user_id}") + resp = http.cache(1.minute).get("#{XML_API}/user/info?id=#{user_id}") return {} if resp.blank? || resp.code.to_i == 404 Hash.from_xml(resp.to_s)["response"]["user"] end @@ -78,11 +82,11 @@ class NicoSeigaApiClient def get(url) cookie_header = Cache.get("nicoseiga-cookie-header") || regenerate_cookie_header - resp = Danbooru::Http.headers({Cookie: cookie_header}).cache(1.minute).get(url) + resp = http.headers({Cookie: cookie_header}).cache(1.minute).get(url) if resp.headers["Location"] =~ %r{seiga\.nicovideo\.jp/login/}i cookie_header = regenerate_cookie_header - resp = Danbooru::Http.headers({Cookie: cookie_header}).cache(1.minute).get(url) + resp = http.headers({Cookie: cookie_header}).cache(1.minute).get(url) end resp @@ -93,7 +97,7 @@ class NicoSeigaApiClient mail_tel: Danbooru.config.nico_seiga_login, password: Danbooru.config.nico_seiga_password } - resp = Danbooru::Http.post("https://account.nicovideo.jp/api/v1/login", form: form) + resp = http.post("https://account.nicovideo.jp/api/v1/login", form: form) cookies = resp.cookies.map { |c| c.name + "=" + c.value } cookies << "accept_fetish_warning=2" From b583b3c8109d888a05f647f463b67b09a3205694 Mon Sep 17 00:00:00 2001 From: evazion Date: Tue, 16 Jun 2020 00:04:31 -0500 Subject: [PATCH 020/173] tests: fix nicoseiga download tests. --- test/unit/downloads/nico_seiga_test.rb | 32 -------------------------- test/unit/sources/nico_seiga_test.rb | 27 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 32 deletions(-) delete mode 100644 test/unit/downloads/nico_seiga_test.rb diff --git a/test/unit/downloads/nico_seiga_test.rb b/test/unit/downloads/nico_seiga_test.rb deleted file mode 100644 index de00ed7bb..000000000 --- a/test/unit/downloads/nico_seiga_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require "test_helper" - -module Downloads - class NicoSeigaTest < ActiveSupport::TestCase - context "downloading a 'http://seiga.nicovideo.jp/seiga/:id' url" do - should "download the original file" do - @source = "http://seiga.nicovideo.jp/seiga/im4937663" - @rewrite = %r!http://lohas.nicoseiga.jp/priv/\h{40}/\d+/4937663! - assert_rewritten(@rewrite, @source) - assert_downloaded(2032, @source) - end - end - - context "downloading a 'http://lohas.nicoseiga.jp/o/:hash/:id' url" do - should "download the original file" do - @source = "http://lohas.nicoseiga.jp/o/910aecf08e542285862954017f8a33a8c32a8aec/1433298801/4937663" - @rewrite = %r!http://lohas.nicoseiga.jp/priv/\h{40}/\d+/4937663! - assert_rewritten(@rewrite, @source) - assert_downloaded(2032, @source) - end - end - - context "downloading a 'https://lohas.nicoseiga.jp/thumb/:id' url" do - should "download the original file" do - @source = "https://lohas.nicoseiga.jp/thumb/4937663i" - @rewrite = %r!http://lohas.nicoseiga.jp/priv/\h{40}/\d+/4937663! - assert_rewritten(@rewrite, @source) - assert_downloaded(2032, @source) - end - end - end -end diff --git a/test/unit/sources/nico_seiga_test.rb b/test/unit/sources/nico_seiga_test.rb index 9ab04edb4..ff2645d9a 100644 --- a/test/unit/sources/nico_seiga_test.rb +++ b/test/unit/sources/nico_seiga_test.rb @@ -147,5 +147,32 @@ module Sources assert_equal(bad_source, Sources::Strategies.normalize_source(bad_source)) end end + + context "downloading a 'http://seiga.nicovideo.jp/seiga/:id' url" do + should "download the original file" do + @source = "http://seiga.nicovideo.jp/seiga/im4937663" + @rewrite = %r{https://lohas.nicoseiga.jp/priv/\h{40}/\d+/4937663} + assert_rewritten(@rewrite, @source) + assert_downloaded(2_032, @source) + end + end + + context "downloading a 'http://lohas.nicoseiga.jp/o/:hash/:id' url" do + should "download the original file" do + @source = "http://lohas.nicoseiga.jp/o/910aecf08e542285862954017f8a33a8c32a8aec/1433298801/4937663" + @rewrite = %r{https://lohas.nicoseiga.jp/priv/\h{40}/\d+/4937663} + assert_rewritten(@rewrite, @source) + assert_downloaded(2_032, @source) + end + end + + context "downloading a 'https://lohas.nicoseiga.jp/thumb/:id' url" do + should "download the original file" do + @source = "https://lohas.nicoseiga.jp/thumb/4937663i" + @rewrite = %r{https://lohas.nicoseiga.jp/priv/\h{40}/\d+/4937663} + assert_rewritten(@rewrite, @source) + assert_downloaded(2_032, @source) + end + end end end From 049f33916bf9bc80ec72327259fcfffab113996d Mon Sep 17 00:00:00 2001 From: evazion Date: Tue, 16 Jun 2020 00:08:57 -0500 Subject: [PATCH 021/173] tests: fix random username conflicts. Fix parallel tests randomly failing because of username conflicts. --- test/factories/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/factories/user.rb b/test/factories/user.rb index 8eff8378d..577334f40 100644 --- a/test/factories/user.rb +++ b/test/factories/user.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory(:user, aliases: [:creator, :updater]) do - name { FFaker::Internet.user_name + $PID.to_s } + name { SecureRandom.uuid } password {"password"} default_image_size {"large"} level {20} From 1aa0f651871a81ee4d495df8d15f8e1ef5220a50 Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 15 Jun 2020 23:30:29 -0500 Subject: [PATCH 022/173] sources: fix rubocop warnings. --- .rubocop.yml | 4 +- app/logical/sources/strategies.rb | 2 +- app/logical/sources/strategies/art_station.rb | 8 +- app/logical/sources/strategies/base.rb | 14 +-- app/logical/sources/strategies/deviant_art.rb | 32 ++--- .../sources/strategies/hentai_foundry.rb | 10 +- app/logical/sources/strategies/moebooru.rb | 10 +- app/logical/sources/strategies/nico_seiga.rb | 2 +- app/logical/sources/strategies/nijie.rb | 33 +++--- app/logical/sources/strategies/null.rb | 4 +- app/logical/sources/strategies/pawoo.rb | 53 ++++----- app/logical/sources/strategies/pixiv.rb | 112 ++++++++---------- app/logical/sources/strategies/tumblr.rb | 20 ++-- app/logical/sources/strategies/twitter.rb | 34 +++--- app/logical/sources/strategies/weibo.rb | 6 +- 15 files changed, 158 insertions(+), 186 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 932920c18..371b1967c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -119,7 +119,9 @@ Style/NumericPredicate: Style/PercentLiteralDelimiters: PreferredDelimiters: "default": "[]" - "%r": "!!" + +Style/ParallelAssignment: + Enabled: false Style/PerlBackrefs: Enabled: false diff --git a/app/logical/sources/strategies.rb b/app/logical/sources/strategies.rb index 0e2f2f82c..92232e59e 100644 --- a/app/logical/sources/strategies.rb +++ b/app/logical/sources/strategies.rb @@ -1,7 +1,7 @@ module Sources module Strategies def self.all - return [ + [ Strategies::Pixiv, Strategies::NicoSeiga, Strategies::Twitter, diff --git a/app/logical/sources/strategies/art_station.rb b/app/logical/sources/strategies/art_station.rb index 2c78826fe..6ab7445ca 100644 --- a/app/logical/sources/strategies/art_station.rb +++ b/app/logical/sources/strategies/art_station.rb @@ -22,15 +22,15 @@ module Sources::Strategies class ArtStation < Base - PROJECT1 = %r!\Ahttps?://www\.artstation\.com/artwork/(?[a-z0-9-]+)/?\z!i - PROJECT2 = %r!\Ahttps?://(?[\w-]+)\.artstation\.com/projects/(?[a-z0-9-]+)(?:/|\?[\w=-]+)?\z!i + PROJECT1 = %r{\Ahttps?://www\.artstation\.com/artwork/(?[a-z0-9-]+)/?\z}i + PROJECT2 = %r{\Ahttps?://(?[\w-]+)\.artstation\.com/projects/(?[a-z0-9-]+)(?:/|\?[\w=-]+)?\z}i PROJECT = Regexp.union(PROJECT1, PROJECT2) ARTIST1 = %r{\Ahttps?://(?[\w-]+)(?[\w-]+)/?\z}i ARTIST3 = %r{\Ahttps?://www\.artstation\.com/(?[\w-]+)/?\z}i ARTIST = Regexp.union(ARTIST1, ARTIST2, ARTIST3) - ASSET = %r!\Ahttps?://cdn\w*\.artstation\.com/p/assets/(?images|covers)/images/(?\d+/\d+/\d+)/(?[^/]+)/(?.+)\z!i + ASSET = %r{\Ahttps?://cdn\w*\.artstation\.com/p/assets/(?images|covers)/images/(?\d+/\d+/\d+)/(?[^/]+)/(?.+)\z}i attr_reader :json @@ -144,7 +144,7 @@ module Sources::Strategies urls = image_url_sizes($~[:type], $~[:id], $~[:filename]) if size == :smallest - urls = urls.reverse() + urls = urls.reverse end chosen_url = urls.find { |url| http_exists?(url, headers) } diff --git a/app/logical/sources/strategies/base.rb b/app/logical/sources/strategies/base.rb index 55bf676ff..0ee78f365 100644 --- a/app/logical/sources/strategies/base.rb +++ b/app/logical/sources/strategies/base.rb @@ -58,8 +58,8 @@ module Sources end def site_name - Addressable::URI.heuristic_parse(url).host - rescue Addressable::URI::InvalidURIError => e + Addressable::URI.heuristic_parse(url)&.host + rescue Addressable::URI::InvalidURIError nil end @@ -90,9 +90,7 @@ module Sources # eventually be assigned as the source for the post, but it does not # represent what the downloader will fetch. def page_url - Rails.logger.warn "Valid page url for (#{url}, #{referer_url}) not found" - - return nil + nil end # This will be the url stored in posts. Typically this is the page @@ -141,7 +139,7 @@ module Sources # Subclasses should merge in any required headers needed to access resources # on the site. def headers - return Danbooru.config.http_headers + Danbooru.config.http_headers end # Returns the size of the image resource without actually downloading the file. @@ -189,7 +187,7 @@ module Sources end def normalized_tags - tags.map { |tag, url| normalize_tag(tag) }.sort.uniq + tags.map { |tag, _url| normalize_tag(tag) }.sort.uniq end def normalize_tag(tag) @@ -243,7 +241,7 @@ module Sources end def to_h - return { + { :artist => { :name => artist_name, :tag_name => tag_name, diff --git a/app/logical/sources/strategies/deviant_art.rb b/app/logical/sources/strategies/deviant_art.rb index 8bb193672..41c12382a 100644 --- a/app/logical/sources/strategies/deviant_art.rb +++ b/app/logical/sources/strategies/deviant_art.rb @@ -47,18 +47,18 @@ module Sources module Strategies class DeviantArt < Base - ASSET_SUBDOMAINS = %r{(?:fc|th|pre|img|orig|origin-orig)\d*}i + ASSET_SUBDOMAINS = /(?:fc|th|pre|img|orig|origin-orig)\d*/i RESERVED_SUBDOMAINS = %r{\Ahttps?://(?:#{ASSET_SUBDOMAINS}|www)\.}i MAIN_DOMAIN = %r{\Ahttps?://(?:www\.)?deviantart.com}i - TITLE = %r{(?[a-z0-9_-]+?)}i - ARTIST = %r{(?<artist>[a-z0-9_-]+?)}i - DEVIATION_ID = %r{(?<deviation_id>[0-9]+)}i + TITLE = /(?<title>[a-z0-9_-]+?)/i + ARTIST = /(?<artist>[a-z0-9_-]+?)/i + DEVIATION_ID = /(?<deviation_id>[0-9]+)/i - DA_FILENAME_1 = %r{[a-f0-9]{32}-d(?<base36_deviation_id>[a-z0-9]+)\.}i - DA_FILENAME_2 = %r{#{TITLE}(?:_by_#{ARTIST}(?:-d(?<base36_deviation_id>[a-z0-9]+))?)?\.}i + DA_FILENAME_1 = /[a-f0-9]{32}-d(?<base36_deviation_id>[a-z0-9]+)\./i + DA_FILENAME_2 = /#{TITLE}(?:_by_#{ARTIST}(?:-d(?<base36_deviation_id>[a-z0-9]+))?)?\./i DA_FILENAME = Regexp.union(DA_FILENAME_1, DA_FILENAME_2) - WIX_FILENAME = %r{d(?<base36_deviation_id>[a-z0-9]+)[0-9a-f-]+\.\w+(?:/\w+/\w+/[\w,]+/(?<title>[\w-]+)_by_(?<artist>[\w-]+)_d\w+-\w+\.\w+)?.+}i + WIX_FILENAME = %r{d(?<base36_deviation_id>[a-z0-9]+)[0-9a-f-]+\.\w+(?:/\w+/\w+/[\w,]+/(?<title>[\w-]+)_by_(?<artist>[\w-]+)_d\w+-\w+\.\w+)?.+}i NOT_NORMALIZABLE_ASSET = %r{\Ahttps?://#{ASSET_SUBDOMAINS}\.deviantart\.net/.+/[0-9a-f]{32}(?:-[^d]\w+)?\.}i @@ -75,7 +75,7 @@ module Sources PATH_PROFILE = %r{#{MAIN_DOMAIN}/#{ARTIST}/?\z}i SUBDOMAIN_PROFILE = %r{\Ahttps?://#{ARTIST}\.deviantart\.com/?\z}i - FAVME = %r{\Ahttps?://(www\.)?fav\.me/d(?<base36_deviation_id>[a-z0-9]+)\z}i + FAVME = %r{\Ahttps?://(?:www\.)?fav\.me/d(?<base36_deviation_id>[a-z0-9]+)\z}i def domains ["deviantart.net", "deviantart.com", "fav.me"] @@ -110,12 +110,12 @@ module Sources api_deviation[:videos].max_by { |x| x[:filesize] }[:src] else src = api_deviation.dig(:content, :src) - if deviation_id && deviation_id.to_i <= 790677560 && src =~ /^https:\/\/images-wixmp-/ && src !~ /\.gif\?/ - src = src.sub(%r!(/f/[a-f0-9-]+/[a-f0-9-]+)!, '/intermediary\1') - src = src.sub(%r!/v1/(fit|fill)/.*\z!i, "") + if deviation_id && deviation_id.to_i <= 790_677_560 && src =~ %r{\Ahttps://images-wixmp-} && src !~ /\.gif\?/ + src = src.sub(%r{(/f/[a-f0-9-]+/[a-f0-9-]+)}, '/intermediary\1') + src = src.sub(%r{/v1/(fit|fill)/.*\z}i, "") end - src = src.sub(%r!\Ahttps?://orig\d+\.deviantart\.net!i, "http://origin-orig.deviantart.net") - src = src.gsub(%r!q_\d+,strp!, "q_100") + src = src.sub(%r{\Ahttps?://orig\d+\.deviantart\.net}i, "http://origin-orig.deviantart.net") + src = src.gsub(/q_\d+,strp/, "q_100") src end end @@ -191,7 +191,7 @@ module Sources # <a href="https://sa-dui.deviantart.com/journal/About-Commissions-223178193" data-sigil="thumb" class="thumb lit" ...> if element["class"].split.include?("lit") - deviation_id = element["href"][%r!-(\d+)\z!, 1].to_i + deviation_id = element["href"][/-(\d+)\z/, 1].to_i element.content = "deviantart ##{deviation_id}" else element.content = "" @@ -199,7 +199,7 @@ module Sources end if element.name == "a" && element["href"].present? - element["href"] = element["href"].gsub(%r!\Ahttps?://www\.deviantart\.com/users/outgoing\?!i, "") + element["href"] = element["href"].gsub(%r{\Ahttps?://www\.deviantart\.com/users/outgoing\?}i, "") # href may be missing the `http://` bit (ex: `inprnt.com`, `//inprnt.com`). Add it if missing. uri = Addressable::URI.heuristic_parse(element["href"]) rescue nil @@ -283,7 +283,7 @@ module Sources return nil if meta.nil? appurl = meta["content"] - uuid = appurl[%r!\ADeviantArt://deviation/(.*)\z!, 1] + uuid = appurl[%r{\ADeviantArt://deviation/(.*)\z}, 1] uuid end memoize :uuid diff --git a/app/logical/sources/strategies/hentai_foundry.rb b/app/logical/sources/strategies/hentai_foundry.rb index 5ab3ca2ed..74f570288 100644 --- a/app/logical/sources/strategies/hentai_foundry.rb +++ b/app/logical/sources/strategies/hentai_foundry.rb @@ -23,11 +23,11 @@ module Sources module Strategies class HentaiFoundry < Base - BASE_URL = %r!\Ahttps?://(?:www\.)?hentai-foundry\.com!i - PAGE_URL = %r!#{BASE_URL}/pictures/user/(?<artist_name>[\w-]+)/(?<illust_id>\d+)(?:/[\w.-]*)?(\?[\w=]*)?\z!i - OLD_PAGE = %r!#{BASE_URL}/pic-(?<illust_id>\d+)(?:\.html)?\z!i - PROFILE_URL = %r!#{BASE_URL}/(?:pictures/)?user/(?<artist_name>[\w-]+)(?:/[a-z]*)?\z!i - IMAGE_URL = %r!\Ahttps?://pictures\.hentai-foundry\.com/+\w/(?<artist_name>[\w-]+)/(?<illust_id>\d+)(?:(?:/[\w.-]+)?\.\w+)?\z!i + BASE_URL = %r{\Ahttps?://(?:www\.)?hentai-foundry\.com}i + PAGE_URL = %r{#{BASE_URL}/pictures/user/(?<artist_name>[\w-]+)/(?<illust_id>\d+)(?:/[\w.-]*)?(\?[\w=]*)?\z}i + OLD_PAGE = %r{#{BASE_URL}/pic-(?<illust_id>\d+)(?:\.html)?\z}i + PROFILE_URL = %r{#{BASE_URL}/(?:pictures/)?user/(?<artist_name>[\w-]+)(?:/[a-z]*)?\z}i + IMAGE_URL = %r{\Ahttps?://pictures\.hentai-foundry\.com/+\w/(?<artist_name>[\w-]+)/(?<illust_id>\d+)(?:(?:/[\w.-]+)?\.\w+)?\z}i def domains ["hentai-foundry.com"] diff --git a/app/logical/sources/strategies/moebooru.rb b/app/logical/sources/strategies/moebooru.rb index af149682e..9c4cc1f8d 100644 --- a/app/logical/sources/strategies/moebooru.rb +++ b/app/logical/sources/strategies/moebooru.rb @@ -32,10 +32,10 @@ module Sources module Strategies class Moebooru < Base - BASE_URL = %r!\Ahttps?://(?:[^.]+\.)?(?<domain>yande\.re|konachan\.com)!i - POST_URL = %r!#{BASE_URL}/post/show/(?<id>\d+)!i - URL_SLUG = %r!/(?:yande\.re%20|Konachan\.com%20-%20)?(?<id>\d+)?.*!i - IMAGE_URL = %r!#{BASE_URL}/(?<type>image|jpeg|sample)/(?<md5>\h{32})#{URL_SLUG}?\.(?<ext>jpg|jpeg|png|gif)\z!i + BASE_URL = %r{\Ahttps?://(?:[^.]+\.)?(?<domain>yande\.re|konachan\.com)}i + POST_URL = %r{#{BASE_URL}/post/show/(?<id>\d+)}i + URL_SLUG = %r{/(?:yande\.re%20|Konachan\.com%20-%20)?(?<id>\d+)?.*}i + IMAGE_URL = %r{#{BASE_URL}/(?<type>image|jpeg|sample)/(?<md5>\h{32})#{URL_SLUG}?\.(?<ext>jpg|jpeg|png|gif)\z}i delegate :artist_name, :profile_url, :tag_name, :artist_commentary_title, :artist_commentary_desc, :dtext_artist_commentary_title, :dtext_artist_commentary_desc, to: :sub_strategy, allow_nil: true @@ -63,7 +63,7 @@ module Sources end def preview_urls - return image_urls unless post_md5.present? + return image_urls if post_md5.blank? ["https://#{file_host}/data/preview/#{post_md5[0..1]}/#{post_md5[2..3]}/#{post_md5}.jpg"] end diff --git a/app/logical/sources/strategies/nico_seiga.rb b/app/logical/sources/strategies/nico_seiga.rb index e63094bb9..ee4efd3d5 100644 --- a/app/logical/sources/strategies/nico_seiga.rb +++ b/app/logical/sources/strategies/nico_seiga.rb @@ -38,7 +38,7 @@ module Sources module Strategies class NicoSeiga < Base - DIRECT = %r{\Ahttps?://lohas\.nicoseiga\.jp/(priv|o)/(?:\w+/\d+/)?(?<image_id>\d+)(?:\?.+)?}i + DIRECT = %r{\Ahttps?://lohas\.nicoseiga\.jp/(?:priv|o)/(?:\w+/\d+/)?(?<image_id>\d+)(?:\?.+)?}i CDN_DIRECT = %r{\Ahttps?://dcdn\.cdn\.nimg\.jp/.+/\w+/\d+/(?<image_id>\d+)}i SOURCE = %r{\Ahttps?://seiga\.nicovideo\.jp/image/source(?:/|\?id=)(?<image_id>\d+)}i diff --git a/app/logical/sources/strategies/nijie.rb b/app/logical/sources/strategies/nijie.rb index e41d47f3d..b24605168 100644 --- a/app/logical/sources/strategies/nijie.rb +++ b/app/logical/sources/strategies/nijie.rb @@ -44,25 +44,25 @@ module Sources module Strategies class Nijie < Base - BASE_URL = %r!\Ahttps?://(?:[^.]+\.)?nijie\.info!i - PAGE_URL = %r!#{BASE_URL}/view(?:_popup)?\.php\?id=(?<illust_id>\d+)!i - PROFILE_URL = %r!#{BASE_URL}/members(?:_illust)?\.php\?id=(?<artist_id>\d+)\z!i + BASE_URL = %r{\Ahttps?://(?:[^.]+\.)?nijie\.info}i + PAGE_URL = %r{#{BASE_URL}/view(?:_popup)?\.php\?id=(?<illust_id>\d+)}i + PROFILE_URL = %r{#{BASE_URL}/members(?:_illust)?\.php\?id=(?<artist_id>\d+)\z}i # https://pic03.nijie.info/nijie_picture/28310_20131101215959.jpg # https://pic03.nijie.info/nijie_picture/236014_20170620101426_0.png # http://pic.nijie.net/03/nijie_picture/829001_20190620004513_0.mp4 # https://pic05.nijie.info/nijie_picture/diff/main/559053_20180604023346_1.png - FILENAME1 = %r!(?<artist_id>\d+)_(?<timestamp>\d{14})(?:_\d+)?!i + FILENAME1 = /(?<artist_id>\d+)_(?<timestamp>\d{14})(?:_\d+)?/i # https://pic01.nijie.info/nijie_picture/diff/main/218856_0_236014_20170620101329.png - FILENAME2 = %r!(?<illust_id>\d+)_\d+_(?<artist_id>\d+)_(?<timestamp>\d{14})!i + FILENAME2 = /(?<illust_id>\d+)_\d+_(?<artist_id>\d+)_(?<timestamp>\d{14})/i # https://pic04.nijie.info/nijie_picture/diff/main/287736_161475_20181112032855_1.png - FILENAME3 = %r!(?<illust_id>\d+)_(?<artist_id>\d+)_(?<timestamp>\d{14})_\d+!i + FILENAME3 = /(?<illust_id>\d+)_(?<artist_id>\d+)_(?<timestamp>\d{14})_\d+/i - IMAGE_BASE_URL = %r!\Ahttps?://(?:pic\d+\.nijie\.info|pic\.nijie\.net)!i - DIR = %r!(?:\d+/)?(?:__rs_\w+/)?nijie_picture(?:/diff/main)?! - IMAGE_URL = %r!#{IMAGE_BASE_URL}/#{DIR}/#{Regexp.union(FILENAME1, FILENAME2, FILENAME3)}\.\w+\z!i + IMAGE_BASE_URL = %r{\Ahttps?://(?:pic\d+\.nijie\.info|pic\.nijie\.net)}i + DIR = %r{(?:\d+/)?(?:__rs_\w+/)?nijie_picture(?:/diff/main)?} + IMAGE_URL = %r{#{IMAGE_BASE_URL}/#{DIR}/#{Regexp.union(FILENAME1, FILENAME2, FILENAME3)}\.\w+\z}i def domains ["nijie.info", "nijie.net"] @@ -146,7 +146,7 @@ module Sources end def to_full_image_url(x) - x.gsub(%r!__rs_\w+/!i, "").gsub(/\Ahttp:/, "https:") + x.gsub(%r{__rs_\w+/}i, "").gsub(/\Ahttp:/, "https:") end def to_preview_url(url) @@ -186,7 +186,7 @@ module Sources doc = agent.get(page_url) end - return doc + doc rescue Mechanize::ResponseCodeError => e return nil if e.response_code.to_i == 404 raise @@ -220,13 +220,10 @@ module Sources mech.cookie_jar.add(cookie) mech - rescue Mechanize::ResponseCodeError => x - if x.response_code.to_i == 429 - sleep(5) - retry - else - raise - end + rescue Mechanize::ResponseCodeError => e + raise unless e.response_code.to_i == 429 + sleep(5) + retry end memoize :agent end diff --git a/app/logical/sources/strategies/null.rb b/app/logical/sources/strategies/null.rb index e17c5e5c4..4ce04c30c 100644 --- a/app/logical/sources/strategies/null.rb +++ b/app/logical/sources/strategies/null.rb @@ -28,7 +28,7 @@ module Sources when %r{\Ahttp://p\.twpl\.jp/show/(?:large|orig)/([a-z0-9]+)}i "http://p.twipple.jp/#{$1}" - when %r{\Ahttps?://blog(?:(?:-imgs-)?\d*(?:-origin)?)?\.fc2\.com/(?:(?:[^/]/){3}|(?:[^/]/))([^/]+)/(?:file/)?([^\.]+\.[^\?]+)}i + when %r{\Ahttps?://blog(?:(?:-imgs-)?\d*(?:-origin)?)?\.fc2\.com/(?:(?:[^/]/){3}|(?:[^/]/))([^/]+)/(?:file/)?([^.]+\.[^?]+)}i username = $1 filename = $2 "http://#{username}.blog.fc2.com/img/#{filename}/" @@ -105,7 +105,7 @@ module Sources # http://img.toranoana.jp/popup_img18/04/0010/22/87/040010228714-1p.jpg # http://img.toranoana.jp/popup_blimg/04/0030/08/30/040030083068-1p.jpg # https://ecdnimg.toranoana.jp/ec/img/04/0030/65/34/040030653417-6p.jpg - when %r{\Ahttps?://(\w+\.)?toranoana\.jp/(?:popup_(?:bl)?img\d*|ec/img)/\d{2}/\d{4}/\d{2}/\d{2}/(?<work_id>\d+)}i + when %r{\Ahttps?://(?:\w+\.)?toranoana\.jp/(?:popup_(?:bl)?img\d*|ec/img)/\d{2}/\d{4}/\d{2}/\d{2}/(?<work_id>\d+)}i "https://ec.toranoana.jp/tora_r/ec/item/#{$~[:work_id]}/" # https://a.hitomi.la/galleries/907838/1.png diff --git a/app/logical/sources/strategies/pawoo.rb b/app/logical/sources/strategies/pawoo.rb index 49a04b5ae..3af7ef8a7 100644 --- a/app/logical/sources/strategies/pawoo.rb +++ b/app/logical/sources/strategies/pawoo.rb @@ -16,13 +16,13 @@ module Sources::Strategies class Pawoo < Base - HOST = %r!\Ahttps?://(www\.)?pawoo\.net!i - IMAGE = %r!\Ahttps?://img\.pawoo\.net/media_attachments/files/(\d+/\d+/\d+)! - NAMED_PROFILE = %r!#{HOST}/@(?<artist_name>\w+)!i - ID_PROFILE = %r!#{HOST}/web/accounts/(?<artist_id>\d+)! + HOST = %r{\Ahttps?://(www\.)?pawoo\.net}i + IMAGE = %r{\Ahttps?://img\.pawoo\.net/media_attachments/files/(\d+/\d+/\d+)} + NAMED_PROFILE = %r{#{HOST}/@(?<artist_name>\w+)}i + ID_PROFILE = %r{#{HOST}/web/accounts/(?<artist_id>\d+)} - STATUS1 = %r!\A#{HOST}/web/statuses/(?<status_id>\d+)! - STATUS2 = %r!\A#{NAMED_PROFILE}/(?<status_id>\d+)! + STATUS1 = %r{\A#{HOST}/web/statuses/(?<status_id>\d+)} + STATUS2 = %r{\A#{NAMED_PROFILE}/(?<status_id>\d+)} def domains ["pawoo.net"] @@ -37,15 +37,13 @@ module Sources::Strategies end def image_urls - if url =~ %r!#{IMAGE}/small/([a-z0-9]+\.\w+)\z!i - return ["https://img.pawoo.net/media_attachments/files/#{$1}/original/#{$2}"] + if url =~ %r{#{IMAGE}/small/([a-z0-9]+\.\w+)\z}i + ["https://img.pawoo.net/media_attachments/files/#{$1}/original/#{$2}"] + elsif url =~ %r{#{IMAGE}/original/([a-z0-9]+\.\w+)\z}i + [url] + else + api_response.image_urls end - - if url =~ %r!#{IMAGE}/original/([a-z0-9]+\.\w+)\z!i - return [url] - end - - return api_response.image_urls end def page_url @@ -55,16 +53,17 @@ module Sources::Strategies end end - return super + super end def profile_url if url =~ PawooApiClient::PROFILE2 - return "https://pawoo.net/@#{$1}" + "https://pawoo.net/@#{$1}" + elsif api_response.profile_url.blank? + url + else + api_response.profile_url end - - return url if api_response.profile_url.blank? - api_response.profile_url end def artist_name @@ -87,10 +86,6 @@ module Sources::Strategies urls.map { |url| url[STATUS1, :status_id] || url[STATUS2, :status_id] }.compact.first end - def artist_commentary_title - nil - end - def artist_commentary_desc api_response.commentary end @@ -99,18 +94,10 @@ module Sources::Strategies api_response.tags end - def normalizable_for_artist_finder? - true - end - - def normalize_for_artist_finder - profile_url - end - def normalize_for_source artist_name = artist_name_from_url status_id = status_id_from_url - return unless status_id.present? + return if status_id.blank? if artist_name.present? "https://pawoo.net/@#{artist_name}/#{status_id}" @@ -131,7 +118,7 @@ module Sources::Strategies def api_response [url, referer_url].each do |x| - if client = PawooApiClient.new.get(x) + if (client = PawooApiClient.new.get(x)) return client end end diff --git a/app/logical/sources/strategies/pixiv.rb b/app/logical/sources/strategies/pixiv.rb index 5f2bbf669..ad7cd7eb1 100644 --- a/app/logical/sources/strategies/pixiv.rb +++ b/app/logical/sources/strategies/pixiv.rb @@ -50,35 +50,35 @@ module Sources module Strategies class Pixiv < Base - MONIKER = %r!(?:[a-zA-Z0-9_-]+)! - PROFILE = %r!\Ahttps?://www\.pixiv\.net/member\.php\?id=[0-9]+\z! - DATE = %r!(?<date>\d{4}/\d{2}/\d{2}/\d{2}/\d{2}/\d{2})!i - EXT = %r!(?:jpg|jpeg|png|gif)!i + MONIKER = /(?:[a-zA-Z0-9_-]+)/ + PROFILE = %r{\Ahttps?://www\.pixiv\.net/member\.php\?id=[0-9]+\z} + DATE = %r{(?<date>\d{4}/\d{2}/\d{2}/\d{2}/\d{2}/\d{2})}i + EXT = /(?:jpg|jpeg|png|gif)/i - WEB = %r!(?:\A(?:https?://)?www\.pixiv\.net)! - I12 = %r!(?:\A(?:https?://)?i[0-9]+\.pixiv\.net)! - IMG = %r!(?:\A(?:https?://)?img[0-9]*\.pixiv\.net)! - PXIMG = %r!(?:\A(?:https?://)?[^.]+\.pximg\.net)! - TOUCH = %r!(?:\A(?:https?://)?touch\.pixiv\.net)! - UGOIRA = %r!#{PXIMG}/img-zip-ugoira/img/#{DATE}/(?<illust_id>\d+)_ugoira1920x1080\.zip\z!i - ORIG_IMAGE = %r!#{PXIMG}/img-original/img/#{DATE}/(?<illust_id>\d+)_p(?<page>\d+)\.#{EXT}\z!i - STACC_PAGE = %r!\A#{WEB}/stacc/#{MONIKER}/?\z!i - NOVEL_PAGE = %r!(?:\Ahttps?://www\.pixiv\.net/novel/show\.php\?id=(\d+))! - FANBOX_ACCOUNT = %r!(?:\Ahttps?://www\.pixiv\.net/fanbox/creator/\d+\z)! - FANBOX_IMAGE = %r!(?:\Ahttps?://fanbox\.pixiv\.net/images/post/(\d+))! - FANBOX_PAGE = %r!(?:\Ahttps?://www\.pixiv\.net/fanbox/creator/\d+/post/(\d+))! + WEB = %r{(?:\A(?:https?://)?www\.pixiv\.net)} + I12 = %r{(?:\A(?:https?://)?i[0-9]+\.pixiv\.net)} + IMG = %r{(?:\A(?:https?://)?img[0-9]*\.pixiv\.net)} + PXIMG = %r{(?:\A(?:https?://)?[^.]+\.pximg\.net)} + TOUCH = %r{(?:\A(?:https?://)?touch\.pixiv\.net)} + UGOIRA = %r{#{PXIMG}/img-zip-ugoira/img/#{DATE}/(?<illust_id>\d+)_ugoira1920x1080\.zip\z}i + ORIG_IMAGE = %r{#{PXIMG}/img-original/img/#{DATE}/(?<illust_id>\d+)_p(?<page>\d+)\.#{EXT}\z}i + STACC_PAGE = %r{\A#{WEB}/stacc/#{MONIKER}/?\z}i + NOVEL_PAGE = %r{(?:\Ahttps?://www\.pixiv\.net/novel/show\.php\?id=(\d+))} + FANBOX_ACCOUNT = %r{(?:\Ahttps?://www\.pixiv\.net/fanbox/creator/\d+\z)} + FANBOX_IMAGE = %r{(?:\Ahttps?://fanbox\.pixiv\.net/images/post/(\d+))} + FANBOX_PAGE = %r{(?:\Ahttps?://www\.pixiv\.net/fanbox/creator/\d+/post/(\d+))} def self.to_dtext(text) if text.nil? return nil end - text = text.gsub(%r!https?://www\.pixiv\.net/member_illust\.php\?mode=medium&illust_id=([0-9]+)!i) do |match| + text = text.gsub(%r{https?://www\.pixiv\.net/member_illust\.php\?mode=medium&illust_id=([0-9]+)}i) do |_match| pixiv_id = $1 %(pixiv ##{pixiv_id} "»":[/posts?tags=pixiv:#{pixiv_id}]) end - text = text.gsub(%r!https?://www\.pixiv\.net/member\.php\?id=([0-9]+)!i) do |match| + text = text.gsub(%r{https?://www\.pixiv\.net/member\.php\?id=([0-9]+)}i) do |_match| member_id = $1 profile_url = "https://www.pixiv.net/member.php?id=#{member_id}" search_params = {"search[url_matches]" => profile_url}.to_param @@ -139,13 +139,13 @@ module Sources return "https://www.pixiv.net/artworks/#{illust_id}" end - return url + url rescue PixivApiClient::BadIDError nil end def canonical_url - return image_url + image_url end def profile_url @@ -200,7 +200,7 @@ module Sources } end - return { + { "Referer" => "https://www.pixiv.net" } end @@ -231,7 +231,7 @@ module Sources translated_tags = super(tag) if translated_tags.empty? && tag.include?("/") - translated_tags = tag.split("/").flat_map { |tag| super(tag) } + translated_tags = tag.split("/").flat_map { |translated_tag| super(translated_tag) } end translated_tags @@ -257,7 +257,7 @@ module Sources return [ugoira_zip_url] end - return metadata.pages + metadata.pages end # in order to prevent recursive loops, this method should not make any @@ -276,11 +276,11 @@ module Sources return url.query_values["illust_id"].to_i # http://www.pixiv.net/en/artworks/46324488 - elsif url.host == "www.pixiv.net" && url.path =~ %r!\A/(?:en/)?artworks/(?<illust_id>\d+)!i + elsif url.host == "www.pixiv.net" && url.path =~ %r{\A/(?:en/)?artworks/(?<illust_id>\d+)}i return $~[:illust_id].to_i # http://www.pixiv.net/i/18557054 - elsif url.host == "www.pixiv.net" && url.path =~ %r!\A/i/(?<illust_id>\d+)\z!i + elsif url.host == "www.pixiv.net" && url.path =~ %r{\A/i/(?<illust_id>\d+)\z}i return $~[:illust_id].to_i # http://img18.pixiv.net/img/evazion/14901720.png @@ -289,8 +289,8 @@ module Sources # http://i2.pixiv.net/img18/img/evazion/14901720_s.png # http://i1.pixiv.net/img07/img/pasirism/18557054_p1.png # http://i1.pixiv.net/img07/img/pasirism/18557054_big_p1.png - elsif url.host =~ %r!\A(?:i\d+|img\d+)\.pixiv\.net\z!i && - url.path =~ %r!\A(?:/img\d+)?/img/#{MONIKER}/(?<illust_id>\d+)(?:_\w+)?\.(?:jpg|jpeg|png|gif|zip)!i + elsif url.host =~ /\A(?:i\d+|img\d+)\.pixiv\.net\z/i && + url.path =~ %r{\A(?:/img\d+)?/img/#{MONIKER}/(?<illust_id>\d+)(?:_\w+)?\.(?:jpg|jpeg|png|gif|zip)}i return $~[:illust_id].to_i # http://i1.pixiv.net/img-inf/img/2011/05/01/23/28/04/18557054_64x64.jpg @@ -307,13 +307,13 @@ module Sources # # https://i.pximg.net/novel-cover-original/img/2019/01/14/01/15/05/10617324_d84daae89092d96bbe66efafec136e42.jpg # https://img-sketch.pixiv.net/uploads/medium/file/4463372/8906921629213362989.jpg - elsif url.host =~ %r!\A(?:[^.]+\.pximg\.net|i\d+\.pixiv\.net|tc-pximg01\.techorus-cdn\.com)\z!i && - url.path =~ %r!\A(/c/\w+)?/img-[a-z-]+/img/#{DATE}/(?<illust_id>\d+)(?:_\w+)?\.(?:jpg|jpeg|png|gif|zip)!i + elsif url.host =~ /\A(?:[^.]+\.pximg\.net|i\d+\.pixiv\.net|tc-pximg01\.techorus-cdn\.com)\z/i && + url.path =~ %r{\A(/c/\w+)?/img-[a-z-]+/img/#{DATE}/(?<illust_id>\d+)(?:_\w+)?\.(?:jpg|jpeg|png|gif|zip)}i return $~[:illust_id].to_i end end - return nil + nil end memoize :illust_id @@ -324,7 +324,7 @@ module Sources end end - return nil + nil end memoize :novel_id @@ -339,7 +339,7 @@ module Sources end end - return nil + nil end memoize :fanbox_id @@ -350,7 +350,7 @@ module Sources end end - return nil + nil end memoize :fanbox_account_id @@ -368,45 +368,39 @@ module Sources return PixivApiClient.new.fanbox(fanbox_id) end - return PixivApiClient.new.work(illust_id) + PixivApiClient.new.work(illust_id) end memoize :metadata def moniker # we can sometimes get the moniker from the url - if url =~ %r!#{IMG}/img/(#{MONIKER})!i - return $1 + if url =~ %r{#{IMG}/img/(#{MONIKER})}i + $1 + elsif url =~ %r{#{I12}/img[0-9]+/img/(#{MONIKER})}i + $1 + elsif url =~ %r{#{WEB}/stacc/(#{MONIKER})/?$}i + $1 + else + metadata.moniker end - - if url =~ %r!#{I12}/img[0-9]+/img/(#{MONIKER})!i - return $1 - end - - if url =~ %r!#{WEB}/stacc/(#{MONIKER})/?$!i - return $1 - end - - return metadata.moniker rescue PixivApiClient::BadIDError nil end memoize :moniker def data - return { - ugoira_frame_data: ugoira_frame_data - } + { ugoira_frame_data: ugoira_frame_data } end def ugoira_zip_url if metadata.pages.is_a?(Hash) && metadata.pages["ugoira600x600"] - return metadata.pages["ugoira600x600"].sub("_ugoira600x600.zip", "_ugoira1920x1080.zip") + metadata.pages["ugoira600x600"].sub("_ugoira600x600.zip", "_ugoira1920x1080.zip") end end memoize :ugoira_zip_url def ugoira_frame_data - return metadata.json.dig("metadata", "frames") + metadata.json.dig("metadata", "frames") rescue PixivApiClient::BadIDError nil end @@ -415,16 +409,14 @@ module Sources def ugoira_content_type case metadata.json["image_urls"].to_s when /\.jpg/ - return "image/jpeg" - + "image/jpeg" when /\.png/ - return "image/png" - + "image/png" when /\.gif/ - return "image/gif" + "image/gif" + else + raise Sources::Error, "content type not found for (#{url}, #{referer_url})" end - - raise Sources::Error.new("content type not found for (#{url}, #{referer_url})") end memoize :ugoira_content_type @@ -434,7 +426,7 @@ module Sources # http://i2.pixiv.net/img04/img/syounen_no_uta/46170939_p0.jpg # http://i1.pixiv.net/c/600x600/img-master/img/2014/09/24/23/25/08/46168376_p0_master1200.jpg # http://i1.pixiv.net/img-original/img/2014/09/25/23/09/29/46183440_p0.jpg - if url =~ %r!/\d+_p(\d+)(?:_\w+)?\.#{EXT}!i + if url =~ %r{/\d+_p(\d+)(?:_\w+)?\.#{EXT}}i return $1.to_i end @@ -445,7 +437,7 @@ module Sources end end - return nil + nil end memoize :manga_page end diff --git a/app/logical/sources/strategies/tumblr.rb b/app/logical/sources/strategies/tumblr.rb index c6d2fc5bc..6123f9f8b 100644 --- a/app/logical/sources/strategies/tumblr.rb +++ b/app/logical/sources/strategies/tumblr.rb @@ -12,19 +12,19 @@ module Sources::Strategies class Tumblr < Base SIZES = %w[1280 640 540 500h 500 400 250 100] - BASE_URL = %r!\Ahttps?://(?:[^/]+\.)*tumblr\.com!i - DOMAIN = %r{(data|(\d+\.)?media)\.tumblr\.com} - MD5 = %r{(?<md5>[0-9a-f]{32})}i - FILENAME = %r{(?<filename>(tumblr_(inline_)?)?[a-z0-9]+(_r[0-9]+)?)}i - EXT = %r{(?<ext>\w+)} + BASE_URL = %r{\Ahttps?://(?:[^/]+\.)*tumblr\.com}i + DOMAIN = /(data|(?:\d+\.)?media)\.tumblr\.com/i + MD5 = /(?<md5>[0-9a-f]{32})/i + FILENAME = /(?<filename>(?:tumblr_(?:inline_)?)?[a-z0-9]+(?:_r[0-9]+)?)/i + EXT = /(?<ext>\w+)/ # old: https://66.media.tumblr.com/2c6f55531618b4335c67e29157f5c1fc/tumblr_pz4a44xdVj1ssucdno1_1280.png # new: https://66.media.tumblr.com/168dabd09d5ad69eb5fedcf94c45c31a/3dbfaec9b9e0c2e3-72/s640x960/bf33a1324f3f36d2dc64f011bfeab4867da62bc8.png - OLD_IMAGE = %r!\Ahttps?://#{DOMAIN}/(?<dir>#{MD5}/)?#{FILENAME}_(?<size>\w+)\.#{EXT}\z!i + OLD_IMAGE = %r{\Ahttps?://#{DOMAIN}/(?<dir>#{MD5}/)?#{FILENAME}_(?<size>\w+)\.#{EXT}\z}i - IMAGE = %r!\Ahttps?://#{DOMAIN}/!i - VIDEO = %r!\Ahttps?://(?:vtt|ve\.media)\.tumblr\.com/!i - POST = %r!\Ahttps?://(?<blog_name>[^.]+)\.tumblr\.com/(?:post|image)/(?<post_id>\d+)!i + IMAGE = %r{\Ahttps?://#{DOMAIN}/}i + VIDEO = %r{\Ahttps?://(?:vtt|ve\.media)\.tumblr\.com/}i + POST = %r{\Ahttps?://(?<blog_name>[^.]+)\.tumblr\.com/(?:post|image)/(?<post_id>\d+)}i def self.enabled? Danbooru.config.tumblr_consumer_key.present? @@ -68,7 +68,7 @@ module Sources::Strategies def preview_urls image_urls.map do |x| - x.sub(%r!_1280\.(jpg|png|gif|jpeg)\z!, '_250.\1') + x.sub(/_1280\.(jpg|png|gif|jpeg)\z/, '_250.\1') end end diff --git a/app/logical/sources/strategies/twitter.rb b/app/logical/sources/strategies/twitter.rb index 69f363363..a51e162ea 100644 --- a/app/logical/sources/strategies/twitter.rb +++ b/app/logical/sources/strategies/twitter.rb @@ -1,20 +1,20 @@ module Sources::Strategies class Twitter < Base - PAGE = %r!\Ahttps?://(?:mobile\.)?twitter\.com!i - PROFILE = %r!\Ahttps?://(?:mobile\.)?twitter.com/(?<username>[a-z0-9_]+)!i + PAGE = %r{\Ahttps?://(?:mobile\.)?twitter\.com}i + PROFILE = %r{\Ahttps?://(?:mobile\.)?twitter.com/(?<username>[a-z0-9_]+)}i # https://pbs.twimg.com/media/EBGbJe_U8AA4Ekb.jpg # https://pbs.twimg.com/media/EBGbJe_U8AA4Ekb?format=jpg&name=900x900 # https://pbs.twimg.com/tweet_video_thumb/ETkN_L3X0AMy1aT.jpg # https://pbs.twimg.com/ext_tw_video_thumb/1243725361986375680/pu/img/JDA7g7lcw7wK-PIv.jpg # https://pbs.twimg.com/amplify_video_thumb/1215590775364259840/img/lolCkEEioFZTb5dl.jpg - BASE_IMAGE_URL = %r!\Ahttps?://pbs\.twimg\.com/(?<media_type>media|tweet_video_thumb|ext_tw_video_thumb|amplify_video_thumb)!i - FILENAME1 = %r!(?<file_name>[a-zA-Z0-9_-]+)\.(?<file_ext>\w+)!i - FILENAME2 = %r!(?<file_name>[a-zA-Z0-9_-]+)\?.*format=(?<file_ext>\w+)!i - FILEPATH1 = %r!(?<file_path>\d+/[\w_-]+/img)!i - FILEPATH2 = %r!(?<file_path>\d+/img)!i - IMAGE_URL1 = %r!#{BASE_IMAGE_URL}/#{Regexp.union(FILENAME1, FILENAME2)}!i - IMAGE_URL2 = %r!#{BASE_IMAGE_URL}/#{Regexp.union(FILEPATH1, FILEPATH2)}/#{FILENAME1}!i + BASE_IMAGE_URL = %r{\Ahttps?://pbs\.twimg\.com/(?<media_type>media|tweet_video_thumb|ext_tw_video_thumb|amplify_video_thumb)}i + FILENAME1 = /(?<file_name>[a-zA-Z0-9_-]+)\.(?<file_ext>\w+)/i + FILENAME2 = /(?<file_name>[a-zA-Z0-9_-]+)\?.*format=(?<file_ext>\w+)/i + FILEPATH1 = %r{(?<file_path>\d+/[\w_-]+/img)}i + FILEPATH2 = %r{(?<file_path>\d+/img)}i + IMAGE_URL1 = %r{#{BASE_IMAGE_URL}/#{Regexp.union(FILENAME1, FILENAME2)}}i + IMAGE_URL2 = %r{#{BASE_IMAGE_URL}/#{Regexp.union(FILEPATH1, FILEPATH2)}/#{FILENAME1}}i # Twitter provides a list but it's inaccurate; some names ('intent') aren't # included and other names in the list aren't actually reserved. @@ -47,7 +47,7 @@ module Sources::Strategies return $1 end - return nil + nil end def self.artist_name_from_url(url) @@ -78,7 +78,7 @@ module Sources::Strategies elsif media[:type].in?(["video", "animated_gif"]) variants = media.dig(:video_info, :variants) videos = variants.select { |variant| variant[:content_type] == "video/mp4" } - video = videos.max_by { |video| video[:bitrate].to_i } + video = videos.max_by { |v| v[:bitrate].to_i } video[:url] end end @@ -137,10 +137,6 @@ module Sources::Strategies api_response[:full_text].to_s end - def normalizable_for_artist_finder? - url =~ PAGE - end - def normalize_for_artist_finder profile_url.try(:downcase).presence || url end @@ -193,9 +189,9 @@ module Sources::Strategies desc = artist_commentary_desc.unicode_normalize(:nfkc) desc = CGI.unescapeHTML(desc) - desc = desc.gsub(%r!https?://t\.co/[a-zA-Z0-9]+!i, url_replacements) - desc = desc.gsub(%r!#([^[:space:]]+)!, '"#\\1":[https://twitter.com/hashtag/\\1]') - desc = desc.gsub(%r!@([a-zA-Z0-9_]+)!, '"@\\1":[https://twitter.com/\\1]') + desc = desc.gsub(%r{https?://t\.co/[a-zA-Z0-9]+}i, url_replacements) + desc = desc.gsub(/#([^[:space:]]+)/, '"#\\1":[https://twitter.com/hashtag/\\1]') + desc = desc.gsub(/@([a-zA-Z0-9_]+)/, '"@\\1":[https://twitter.com/\\1]') desc.strip end @@ -204,7 +200,7 @@ module Sources::Strategies end def api_response - return {} if !self.class.enabled? + return {} unless self.class.enabled? api_client.status(status_id) end diff --git a/app/logical/sources/strategies/weibo.rb b/app/logical/sources/strategies/weibo.rb index 8cad13cd1..eaac6e40f 100644 --- a/app/logical/sources/strategies/weibo.rb +++ b/app/logical/sources/strategies/weibo.rb @@ -38,7 +38,7 @@ module Sources PAGE_URL_1 = %r{\Ahttps?://(?:www\.)?weibo\.com/(?<artist_short_id>\d+)/(?<illust_base62_id>\w+)(?:\?.*)?\z}i PAGE_URL_2 = %r{#{PROFILE_URL_2}/(?:wbphotos/large/mid|talbum/detail/photo_id)/(?<illust_long_id>\d+)(?:/pid/(?<image_id>\w{32}))?}i - PAGE_URL_3 = %r{\Ahttps?://m\.weibo\.cn/(detail/(?<illust_long_id>\d+)|status/(?<illust_base62_id>\w+))}i + PAGE_URL_3 = %r{\Ahttps?://m\.weibo\.cn/(?:detail/(?<illust_long_id>\d+)|status/(?<illust_base62_id>\w+))}i PAGE_URL_4 = %r{\Ahttps?://tw\.weibo\.com/(?:(?<artist_short_id>\d+)|\w+)/(?<illust_long_id>\d+)}i IMAGE_URL = %r{\Ahttps?://\w{3}\.sinaimg\.cn/\w+/(?<image_id>\w{32})\.}i @@ -203,12 +203,12 @@ module Sources end def api_response - return nil if mobile_url.blank? + return {} if mobile_url.blank? resp = Danbooru::Http.cache(1.minute).get(mobile_url) json_string = resp.to_s[/var \$render_data = \[(.*)\]\[0\]/m, 1] - return nil if json_string.blank? + return {} if json_string.blank? JSON.parse(json_string)["status"] end From ed152a780b4b929d2e68acf05c2df62b291a1e77 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 16 Jun 2020 00:35:16 -0500 Subject: [PATCH 023/173] Upgrade ruby gems and yarn packages. --- Gemfile.lock | 28 +++++----- app/models/saved_search.rb | 1 + yarn.lock | 110 ++++++++++++++++--------------------- 3 files changed, 61 insertions(+), 78 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9d09ede27..8afa3be8f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -75,16 +75,16 @@ GEM airbrussh (1.4.0) sshkit (>= 1.6.1, != 1.7.0) ansi (1.5.0) - ast (2.4.0) + ast (2.4.1) aws-eventstream (1.1.0) - aws-partitions (1.326.0) - aws-sdk-core (3.98.0) + aws-partitions (1.329.0) + aws-sdk-core (3.100.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-sqs (1.26.0) - aws-sdk-core (~> 3, >= 3.71.0) + aws-sdk-sqs (1.27.1) + aws-sdk-core (~> 3, >= 3.99.0) aws-sigv4 (~> 1.1) aws-sigv4 (1.1.4) aws-eventstream (~> 1.0, >= 1.0.2) @@ -93,7 +93,7 @@ GEM msgpack (~> 1.0) builder (3.2.4) byebug (11.1.3) - capistrano (3.14.0) + capistrano (3.14.1) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) @@ -146,8 +146,8 @@ GEM faraday (1.0.1) multipart-post (>= 1.2, < 3) ffaker (2.15.0) - ffi (1.13.0) - ffi (1.13.0-x64-mingw32) + ffi (1.13.1) + ffi (1.13.1-x64-mingw32) ffi-compiler (1.0.1) ffi (>= 1.0.0) rake @@ -167,7 +167,7 @@ GEM http-form_data (2.3.0) http-parser (1.2.1) ffi-compiler (>= 1.0, < 2.0) - httparty (0.18.0) + httparty (0.18.1) mime-types (~> 3.0) multi_xml (>= 0.5.2) i18n (1.8.3) @@ -236,7 +236,7 @@ GEM net-ssh (>= 2.6.5, < 7.0.0) net-sftp (3.0.0) net-ssh (>= 5.0.0, < 7.0.0) - net-ssh (6.0.2) + net-ssh (6.1.0) newrelic_rpm (6.11.0.365) nio4r (2.5.2) nokogiri (1.10.9) @@ -270,7 +270,7 @@ GEM nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) - rack (2.2.2) + rack (2.2.3) rack-contrib (2.2.0) rack (~> 2.0) rack-mini-profiler (2.0.2) @@ -316,7 +316,7 @@ GEM ffi (~> 1.0) recaptcha (5.5.0) json - redis (4.1.4) + redis (4.2.1) regexp_parser (1.7.1) request_store (1.5.0) rack (>= 1.4) @@ -357,7 +357,7 @@ GEM childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) semantic_range (2.3.0) - shoulda-context (1.2.2) + shoulda-context (2.0.0) shoulda-matchers (4.3.0) activesupport (>= 4.2.0) simple_form (5.0.2) @@ -373,7 +373,7 @@ GEM rack (~> 2.0) rack-protection (= 2.0.8.1) tilt (~> 2.0) - sprockets (4.0.0) + sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.2.1) diff --git a/app/models/saved_search.rb b/app/models/saved_search.rb index 005f7dfa6..36478284f 100644 --- a/app/models/saved_search.rb +++ b/app/models/saved_search.rb @@ -18,6 +18,7 @@ class SavedSearch < ApplicationRecord post_ids = Set.new queries.each do |query| redis_key = "search:#{query}" + # XXX change to `exists?` (ref: https://github.com-sds/mock_redis/pull/188 if redis.exists(redis_key) sub_ids = redis.smembers(redis_key).map(&:to_i) post_ids.merge(sub_ids) diff --git a/yarn.lock b/yarn.lock index 66ee35855..54b157113 100644 --- a/yarn.lock +++ b/yarn.lock @@ -908,9 +908,9 @@ "@types/node" "*" "@types/json-schema@^7.0.4": - version "7.0.4" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" - integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== + version "7.0.5" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" + integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== "@types/minimatch@*": version "3.0.3" @@ -923,9 +923,9 @@ integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= "@types/node@*": - version "14.0.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.12.tgz#9c1d8ffb8084e8936603a6122a7649e40e68e04b" - integrity sha512-/sjzehvjkkpvLpYtN6/2dv5kg41otMGuHQUt9T2aiAuIfleCQRQHXXzF1eAw/qkZTj5Kcf4JSTf7EIizHocy6Q== + version "14.0.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.13.tgz#ee1128e881b874c371374c1f72201893616417c9" + integrity sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -1126,9 +1126,9 @@ acorn@^6.4.1: integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== acorn@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe" - integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ== + version "7.3.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" + integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== aggregate-error@^3.0.0: version "3.0.1" @@ -1883,9 +1883,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061: - version "1.0.30001079" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001079.tgz#ed3e5225cd9a6850984fdd88bf24ce45d69b9c22" - integrity sha512-2KaYheg0iOY+CMmDuAB3DHehrXhhb4OZU4KBVGDr/YKyYAcpudaiUQ9PJ9rxrPlKEoJ3ATasQ5AN48MqpwS43Q== + version "1.0.30001083" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001083.tgz#52410c20c6f029f604f0d45eca0439a82e712442" + integrity sha512-CnYJ27awX4h7yj5glfK7r1TOI13LBytpLzEgfj0s4mY75/F8pnQcYjL+oVpmS38FB59+vU0gscQ9D8tc+lIXvA== case-sensitive-paths-webpack-plugin@^2.3.0: version "2.3.0" @@ -1940,9 +1940,9 @@ chalk@^3.0.0: supports-color "^7.1.0" chalk@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" - integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A== + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" @@ -2439,22 +2439,22 @@ css-has-pseudo@^0.10.0: postcss-selector-parser "^5.0.0-rc.4" css-loader@^3.4.2: - version "3.5.3" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.5.3.tgz#95ac16468e1adcd95c844729e0bb167639eb0bcf" - integrity sha512-UEr9NH5Lmi7+dguAm+/JSPovNjYbm2k3TK58EiwQHzOHH5Jfq1Y+XoP2bQO6TMn7PptMd0opxxedAWcaSTRKHw== + version "3.6.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" + integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ== dependencies: camelcase "^5.3.1" cssesc "^3.0.0" icss-utils "^4.1.1" loader-utils "^1.2.3" normalize-path "^3.0.0" - postcss "^7.0.27" + postcss "^7.0.32" postcss-modules-extract-imports "^2.0.0" postcss-modules-local-by-default "^3.0.2" postcss-modules-scope "^2.2.0" postcss-modules-values "^3.0.0" - postcss-value-parser "^4.0.3" - schema-utils "^2.6.6" + postcss-value-parser "^4.1.0" + schema-utils "^2.7.0" semver "^6.3.0" css-prefers-color-scheme@^3.1.1: @@ -2935,9 +2935,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.413: - version "1.3.465" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.465.tgz#d692e5c383317570c2bd82092a24a0308c6ccf29" - integrity sha512-K/lUeT3NLAsJ5SHRDhK3/zd0tw7OUllYD8w+fTOXm6ljCPsp2qq+vMzxpLo8u1M27ZjZAjRbsA6rirvne2nAMQ== + version "1.3.474" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.474.tgz#161af012e11f96795eade84bf03b8ddc039621b9" + integrity sha512-fPkSgT9IBKmVJz02XioNsIpg0WYmkPrvU1lUJblMMJALxyE7/32NGvbJQKKxpNokozPvqfqkuUqVClYsvetcLw== elliptic@^6.0.0, elliptic@^6.5.2: version "6.5.2" @@ -2994,9 +2994,9 @@ enhanced-resolve@4.1.0: tapable "^1.0.0" enhanced-resolve@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" - integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA== + version "4.2.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz#5d43bda4a0fd447cb0ebbe71bef8deff8805ad0d" + integrity sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ== dependencies: graceful-fs "^4.1.2" memory-fs "^0.5.0" @@ -3027,21 +3027,21 @@ error-ex@^1.2.0, error-ex@^1.3.1: is-arrayish "^0.2.1" es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: - version "1.17.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" - integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== dependencies: es-to-primitive "^1.2.1" function-bind "^1.1.1" has "^1.0.3" has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" + is-callable "^1.2.0" + is-regex "^1.1.0" object-inspect "^1.7.0" object-keys "^1.1.1" object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" es-to-primitive@^1.2.1: version "1.2.1" @@ -3374,9 +3374,9 @@ fast-deep-equal@^3.1.1: integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.1.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.2.tgz#ade1a9d91148965d4bf7c51f72e1ca662d32e63d" - integrity sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A== + version "3.2.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.3.tgz#64d6bf0e32f1195ab45ac8896e4adbe267ccd798" + integrity sha512-fWSEEcoqcYqlFJrpSH5dJTwv6o0r+2bLAmnlne8OQMbFhpSTQXA8Ngp6q1DGA4B+eewHeuH5ndZeiV2qyXXNsA== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -4324,9 +4324,9 @@ ini@^1.3.4, ini@^1.3.5: integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== inquirer@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29" - integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg== + version "7.2.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.2.0.tgz#63ce99d823090de7eb420e4bb05e6f3449aa389a" + integrity sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ== dependencies: ansi-escapes "^4.2.1" chalk "^3.0.0" @@ -4468,7 +4468,7 @@ is-buffer@^2.0.0: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== -is-callable@^1.1.4, is-callable@^1.1.5: +is-callable@^1.1.4, is-callable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== @@ -4648,7 +4648,7 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-regex@^1.0.4, is-regex@^1.0.5: +is-regex@^1.0.4, is-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== @@ -6958,7 +6958,7 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.0.3, postcss-value-parser@^4.1.0: +postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== @@ -7704,7 +7704,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.6.6: +schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== @@ -8234,7 +8234,7 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.0: +string.prototype.trimend@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== @@ -8242,25 +8242,7 @@ string.prototype.trimend@^1.0.0: define-properties "^1.1.3" es-abstract "^1.17.5" -string.prototype.trimleft@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" - integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart "^1.0.0" - -string.prototype.trimright@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" - integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend "^1.0.0" - -string.prototype.trimstart@^1.0.0: +string.prototype.trimstart@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== From 101e8a13b818cf60167a0152ea2f0c9564ffb7ad Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 16 Jun 2020 12:44:51 -0500 Subject: [PATCH 024/173] /posts: fix blank tag sidebars. Fix the sidebar on the /posts index page sometimes being blank. This could happen when either the related tag calculation was too slow and timed out, or when Reporbooru was unavailable and we couldn't fetch the list of popular tags. In the tag list would otherwise be blank, we fall back to frequent tags (the most common tags on the current page of results). Also change it so that if Reportbooru is unconfigured, we fail gracefully by returning blank results instead of failing with an exception. This is so we can still view the popular searches and missed searches pages during testing (even though they'll be blank). --- app/logical/post_sets/post.rb | 14 +++----------- app/logical/reportbooru_service.rb | 7 +++---- app/views/posts/partials/index/_related.html.erb | 6 ++---- app/views/static/site_map.html.erb | 6 ++---- 4 files changed, 10 insertions(+), 23 deletions(-) diff --git a/app/logical/post_sets/post.rb b/app/logical/post_sets/post.rb index 88c415c13..2858b9b64 100644 --- a/app/logical/post_sets/post.rb +++ b/app/logical/post_sets/post.rb @@ -160,20 +160,16 @@ module PostSets elsif query.is_metatag?(:search) saved_search_tags elsif query.is_empty_search? || query.is_metatag?(:order, :rank) - popular_tags + popular_tags.presence || frequent_tags elsif query.is_single_term? - similar_tags + similar_tags.presence || frequent_tags else frequent_tags end end def popular_tags - if reportbooru_service.enabled? - reportbooru_service.popular_searches(Date.today, limit: MAX_SIDEBAR_TAGS).map(&:first) - else - frequent_tags - end + ReportbooruService.new.popular_searches(Date.today, limit: MAX_SIDEBAR_TAGS) end def similar_tags @@ -199,10 +195,6 @@ module PostSets def tag_list_html(**options) tag_set_presenter.tag_list_html(name_only: query.is_metatag?(:search), **options) end - - def reportbooru_service - @reportbooru_service ||= ReportbooruService.new - end end end end diff --git a/app/logical/reportbooru_service.rb b/app/logical/reportbooru_service.rb index 83c8bc895..5da79d724 100644 --- a/app/logical/reportbooru_service.rb +++ b/app/logical/reportbooru_service.rb @@ -11,7 +11,7 @@ class ReportbooruService end def missed_search_rankings(expires_in: 1.minutes) - raise NotImplementedError, "Reportbooru not configured, missed searches not available." unless enabled? + return [] unless enabled? response = http.cache(expires_in).get("#{reportbooru_server}/missed_searches") return [] if response.status != 200 @@ -21,7 +21,7 @@ class ReportbooruService end def post_search_rankings(date = Date.today, expires_in: 1.minutes) - raise NotImplementedError, "Reportbooru not configured, popular searches not available." unless enabled? + return [] unless enabled? response = http.cache(expires_in).get("#{reportbooru_server}/post_searches/rank?date=#{date}") return [] if response.status != 200 @@ -29,7 +29,7 @@ class ReportbooruService end def post_view_rankings(date = Date.today, expires_in: 1.minutes) - raise NotImplementedError, "Reportbooru not configured, post views not available." unless enabled? + return [] unless enabled? response = http.get("#{reportbooru_server}/post_views/rank?date=#{date}") return [] if response.status != 200 @@ -38,7 +38,6 @@ class ReportbooruService def popular_searches(date = Date.today, limit: 100) ranking = post_search_rankings(date) - ranking = post_search_rankings(date.yesterday) if ranking.blank? ranking.take(limit).map(&:first) end diff --git a/app/views/posts/partials/index/_related.html.erb b/app/views/posts/partials/index/_related.html.erb index c979869b8..cf0461b3f 100644 --- a/app/views/posts/partials/index/_related.html.erb +++ b/app/views/posts/partials/index/_related.html.erb @@ -5,10 +5,8 @@ <li id="secondary-links-posts-hot"><%= link_to "Hot", posts_path(:tags => "order:rank") %></li> <li id="secondary-links-posts-popular"><%= link_to "Popular", popular_explore_posts_path %></li> <li id="secondary-links-posts-curated"><%= link_to "Curated", curated_explore_posts_path %></li> - <% if ReportbooruService.enabled? %> - <li><%= link_to "Searches", searches_explore_posts_path %></li> - <li><%= link_to "Viewed", viewed_explore_posts_path %></li> - <% end %> + <li><%= link_to "Searches", searches_explore_posts_path %></li> + <li><%= link_to "Viewed", viewed_explore_posts_path %></li> <% end %> <li><%= link_to "Deleted", posts_path(tags: "#{params[:tags]} status:deleted") %></li> diff --git a/app/views/static/site_map.html.erb b/app/views/static/site_map.html.erb index 078b9ec7d..bcbfe1208 100644 --- a/app/views/static/site_map.html.erb +++ b/app/views/static/site_map.html.erb @@ -72,10 +72,8 @@ <ul> <li><h1>Reports</h1></li> <li><%= link_to("Performance Reports", "https://isshiki.donmai.us/user-reports") %></li> - <% if Danbooru.config.reportbooru_server %> - <li><%= link_to("Top Searches", searches_explore_posts_path) %></li> - <li><%= link_to("Missed Searches", missed_searches_explore_posts_path) %></li> - <% end %> + <li><%= link_to("Top Searches", searches_explore_posts_path) %></li> + <li><%= link_to("Missed Searches", missed_searches_explore_posts_path) %></li> </ul> </section> <section> From a572a6614de00800f81b367b9a4ae960a0418c19 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 16 Jun 2020 13:09:05 -0500 Subject: [PATCH 025/173] popular/missed searches: fix blank views. Fix missing call to `.each`. --- app/views/explore/posts/missed_searches.html.erb | 2 +- app/views/explore/posts/searches.html.erb | 2 +- test/functional/explore/posts_controller_test.rb | 2 ++ test/test_helpers/reportbooru_helper.rb | 3 ++- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/explore/posts/missed_searches.html.erb b/app/views/explore/posts/missed_searches.html.erb index df5e730bd..171017b87 100644 --- a/app/views/explore/posts/missed_searches.html.erb +++ b/app/views/explore/posts/missed_searches.html.erb @@ -15,7 +15,7 @@ </tr> </thead> <tbody> - <% @search_service.missed_search_rankings do |tags, count| %> + <% @search_service.missed_search_rankings.each do |tags, count| %> <tr class="tag-type-<%= Tag.category_for(tags) %>"> <td><%= link_to tags, posts_path(:tags => tags) %></td> <td> diff --git a/app/views/explore/posts/searches.html.erb b/app/views/explore/posts/searches.html.erb index 18d02f8bf..fb9e3446e 100644 --- a/app/views/explore/posts/searches.html.erb +++ b/app/views/explore/posts/searches.html.erb @@ -13,7 +13,7 @@ </tr> </thead> <tbody> - <% @search_service.post_search_rankings(@date) do |tags, count| %> + <% @search_service.post_search_rankings(@date).each do |tags, count| %> <tr class="tag-type-<%= Tag.category_for(tags) %>"> <td><%= link_to tags, posts_path(:tags => tags) %></td> <td style="text-align: right;"><%= count.to_i %></td> diff --git a/test/functional/explore/posts_controller_test.rb b/test/functional/explore/posts_controller_test.rb index 5fd79d5e1..f553bc594 100644 --- a/test/functional/explore/posts_controller_test.rb +++ b/test/functional/explore/posts_controller_test.rb @@ -41,6 +41,7 @@ module Explore mock_post_search_rankings(Date.today, [["1girl", 100], ["original", 50]]) get searches_explore_posts_path assert_response :success + assert_select "tbody tr", count: 2 end end @@ -49,6 +50,7 @@ module Explore mock_missed_search_rankings([["1girl", 100], ["original", 50]]) get missed_searches_explore_posts_path assert_response :success + assert_select "tbody tr", count: 2 end end end diff --git a/test/test_helpers/reportbooru_helper.rb b/test/test_helpers/reportbooru_helper.rb index 051c33395..6a99dc5d4 100644 --- a/test/test_helpers/reportbooru_helper.rb +++ b/test/test_helpers/reportbooru_helper.rb @@ -13,7 +13,8 @@ module ReportbooruHelper def mock_missed_search_rankings(date = Date.today, rankings) Danbooru.config.stubs(:reportbooru_server).returns("http://localhost:1234") url = "http://localhost:1234/missed_searches" - mock_request(url, body: rankings.to_json) + data = rankings.map { _1.join(" ") }.join("\n") + mock_request(url, body: data) end def mock_post_view_rankings(date = Date.today, rankings) From 3a71f610be388dd25f6b97be1c0dabfaa030e0f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2020 22:10:14 +0000 Subject: [PATCH 026/173] build(deps): bump sanitize from 5.2.0 to 5.2.1 Bumps [sanitize](https://github.com/rgrove/sanitize) from 5.2.0 to 5.2.1. - [Release notes](https://github.com/rgrove/sanitize/releases) - [Changelog](https://github.com/rgrove/sanitize/blob/master/HISTORY.md) - [Commits](https://github.com/rgrove/sanitize/compare/v5.2.0...v5.2.1) Signed-off-by: dependabot[bot] <support@github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8afa3be8f..b9cc72ac4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -346,7 +346,7 @@ GEM ruby2_keywords (0.0.2) rubyzip (2.3.0) safe_yaml (1.0.5) - sanitize (5.2.0) + sanitize (5.2.1) crass (~> 1.0.2) nokogiri (>= 1.8.0) nokogumbo (~> 2.0) From b551e3634f5ae21034bed02a19a83da254350ce8 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 16 Jun 2020 21:36:15 -0500 Subject: [PATCH 027/173] Fix misc rubocop warnings. --- app/logical/apng_inspector.rb | 8 +++--- app/logical/artist_finder.rb | 10 +++---- app/logical/bulk_update_request_processor.rb | 2 -- app/logical/concerns/has_bit_flags.rb | 2 +- app/logical/concerns/mentionable.rb | 2 -- app/logical/concerns/searchable.rb | 7 ++--- app/logical/d_text.rb | 8 +++--- app/logical/image_proxy.rb | 7 ++--- app/logical/ip_lookup.rb | 2 +- app/logical/media_file/flash.rb | 12 ++++----- app/logical/pagination_extension.rb | 7 ++--- app/logical/pawoo_api_client.rb | 6 ++--- app/logical/pixiv_web_agent.rb | 2 +- app/logical/post_sets/post.rb | 6 ++--- app/logical/spam_detector.rb | 9 ++++--- app/logical/storage_manager/sftp.rb | 4 +-- app/logical/tag_autocomplete.rb | 5 ++-- .../tag_relationship_retirement_service.rb | 16 +++-------- app/logical/upload_limit.rb | 4 +-- app/logical/upload_service.rb | 13 +++++---- .../upload_service/controller_helper.rb | 4 +-- app/logical/upload_service/preprocessor.rb | 27 +++++++++---------- app/logical/upload_service/replacer.rb | 4 +-- app/logical/upload_service/utils.rb | 2 +- app/logical/user_deletion.rb | 1 + 25 files changed, 75 insertions(+), 95 deletions(-) diff --git a/app/logical/apng_inspector.rb b/app/logical/apng_inspector.rb index 4921c226a..68cc62c06 100644 --- a/app/logical/apng_inspector.rb +++ b/app/logical/apng_inspector.rb @@ -57,7 +57,7 @@ class APNGInspector # if we did, file is probably maliciously formed # fail gracefully without marking the file as corrupt chunks += 1 - if chunks > 100000 + if chunks > 100_000 iend_reached = true break end @@ -66,7 +66,8 @@ class APNGInspector file.seek(current_pos + chunk_len + 4, IO::SEEK_SET) end end - return iend_reached + + iend_reached end def inspect! @@ -105,6 +106,7 @@ class APNGInspector if framedata.nil? || framedata.length != 4 return -1 end - return framedata.unpack1("N".freeze) + + framedata.unpack1("N".freeze) end end diff --git a/app/logical/artist_finder.rb b/app/logical/artist_finder.rb index 3cf5c9581..a91d0f5e1 100644 --- a/app/logical/artist_finder.rb +++ b/app/logical/artist_finder.rb @@ -6,7 +6,7 @@ module ArtistFinder SITE_BLACKLIST = [ "artstation.com/artist", # http://www.artstation.com/artist/serafleur/ "www.artstation.com", # http://www.artstation.com/serafleur/ - %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 + %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 "ask.fm", # http://ask.fm/mikuroko_396 "bcyimg.com", "bcyimg.com/drawer", # https://img9.bcyimg.com/drawer/32360/post/178vu/46229ec06e8111e79558c1b725ebc9e6.jpg @@ -52,7 +52,7 @@ module ArtistFinder "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/ + %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", @@ -68,7 +68,7 @@ module ArtistFinder "nicovideo.jp/user", # http://www.nicovideo.jp/user/317609 "nicovideo.jp/user/illust", # http://seiga.nicovideo.jp/user/illust/29075429 "nijie.info", # http://nijie.info/members.php?id=15235 - %r!nijie\.info/nijie_picture!i, # http://pic03.nijie.info/nijie_picture/32243_20150609224803_0.png + %r{nijie\.info/nijie_picture}i, # http://pic03.nijie.info/nijie_picture/32243_20150609224803_0.png "patreon.com", # http://patreon.com/serafleur "pawoo.net", # https://pawoo.net/@148nasuka "pawoo.net/web/accounts", # https://pawoo.net/web/accounts/228341 @@ -120,7 +120,7 @@ module ArtistFinder 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 + %r{\Ahttps?://(?:[a-zA-Z0-9_-]+\.)*#{domain}/\z}i end) def find_artists(url) @@ -128,7 +128,7 @@ module ArtistFinder artists = [] while artists.empty? && url.size > 10 - u = url.sub(/\/+$/, "") + "/" + u = url.sub(%r{/+$}, "") + "/" u = u.to_escaped_for_sql_like.gsub(/\*/, '%') + '%' artists += Artist.joins(:urls).where(["artists.is_deleted = FALSE AND artist_urls.normalized_url LIKE ? ESCAPE E'\\\\'", u]).limit(10).order("artists.name").all url = File.dirname(url) + "/" diff --git a/app/logical/bulk_update_request_processor.rb b/app/logical/bulk_update_request_processor.rb index 96212a72e..293320510 100644 --- a/app/logical/bulk_update_request_processor.rb +++ b/app/logical/bulk_update_request_processor.rb @@ -148,8 +148,6 @@ class BulkUpdateRequestProcessor end.join("\n") end - private - def self.is_tag_move_allowed?(antecedent_name, consequent_name) antecedent_tag = Tag.find_by_name(Tag.normalize_name(antecedent_name)) consequent_tag = Tag.find_by_name(Tag.normalize_name(consequent_name)) diff --git a/app/logical/concerns/has_bit_flags.rb b/app/logical/concerns/has_bit_flags.rb index 2b2e2a355..c05b6d54a 100644 --- a/app/logical/concerns/has_bit_flags.rb +++ b/app/logical/concerns/has_bit_flags.rb @@ -16,7 +16,7 @@ module HasBitFlags end define_method("#{attribute}=") do |val| - if val.to_s =~ /t|1|y/ + if val.to_s =~ /[t1y]/ send("#{field}=", send(field) | bit_flag) else send("#{field}=", send(field) & ~bit_flag) diff --git a/app/logical/concerns/mentionable.rb b/app/logical/concerns/mentionable.rb index f6dda15e9..f0a8e16ee 100644 --- a/app/logical/concerns/mentionable.rb +++ b/app/logical/concerns/mentionable.rb @@ -11,8 +11,6 @@ module Mentionable # - user_field def mentionable(options = {}) @mentionable_options = options - - message_field = mentionable_option(:message_field) after_save :queue_mention_messages end diff --git a/app/logical/concerns/searchable.rb b/app/logical/concerns/searchable.rb index 3b2996b54..640692263 100644 --- a/app/logical/concerns/searchable.rb +++ b/app/logical/concerns/searchable.rb @@ -89,7 +89,7 @@ module Searchable def where_array_count(attr, value) qualified_column = "cardinality(#{qualified_column_for(attr)})" range = PostQueryBuilder.new(nil).parse_range(value, :integer) - where_operator("cardinality(#{qualified_column_for(attr)})", *range) + where_operator(qualified_column, *range) end def search_boolean_attribute(attribute, params) @@ -170,7 +170,7 @@ module Searchable end end - def search_text_attribute(attr, params, **options) + def search_text_attribute(attr, params) if params[attr].present? where(attr => params[attr]) elsif params[:"#{attr}_eq"].present? @@ -279,7 +279,8 @@ module Searchable return find_ordered(parse_ids[1]) end end - return default_order + + default_order end def default_order diff --git a/app/logical/d_text.rb b/app/logical/d_text.rb index 9d37d74ec..26f451a47 100644 --- a/app/logical/d_text.rb +++ b/app/logical/d_text.rb @@ -11,7 +11,7 @@ class DText html = DTextRagel.parse(text, **options) html = postprocess(html, *data) html - rescue DTextRagel::Error => e + rescue DTextRagel::Error "" end @@ -135,7 +135,7 @@ class DText fragment = Nokogiri::HTML.fragment(html) titles = fragment.css("a.dtext-wiki-link").map do |node| - title = node["href"][%r!\A/wiki_pages/(.*)\z!i, 1] + title = node["href"][%r{\A/wiki_pages/(.*)\z}i, 1] title = CGI.unescape(title) title = WikiPage.normalize_title(title) title @@ -163,7 +163,7 @@ class DText string = string.dup string.gsub!(/\s*\[#{tag}\](?!\])\s*/mi, "\n\n[#{tag}]\n\n") - string.gsub!(/\s*\[\/#{tag}\]\s*/mi, "\n\n[/#{tag}]\n\n") + string.gsub!(%r{\s*\[/#{tag}\]\s*}mi, "\n\n[/#{tag}]\n\n") string.gsub!(/(?:\r?\n){3,}/, "\n\n") string.strip! @@ -203,7 +203,7 @@ class DText end end - text = text.gsub(/\A[[:space:]]+|[[:space:]]+\z/, "") + text.gsub(/\A[[:space:]]+|[[:space:]]+\z/, "") end def self.from_html(text, inline: false, &block) diff --git a/app/logical/image_proxy.rb b/app/logical/image_proxy.rb index f10fd53c4..80982c7cc 100644 --- a/app/logical/image_proxy.rb +++ b/app/logical/image_proxy.rb @@ -17,10 +17,7 @@ class ImageProxy end response = HTTParty.get(url, Danbooru.config.httparty_options.deep_merge(headers: {"Referer" => fake_referer_for(url)})) - if response.success? - return response - else - raise "HTTP error code: #{response.code} #{response.message}" - end + raise "HTTP error code: #{response.code} #{response.message}" unless response.success? + response end end diff --git a/app/logical/ip_lookup.rb b/app/logical/ip_lookup.rb index dc9f5bfce..7a6e3bf1a 100644 --- a/app/logical/ip_lookup.rb +++ b/app/logical/ip_lookup.rb @@ -16,7 +16,7 @@ class IpLookup end def info - return {} unless api_key.present? + return {} if api_key.blank? response = Danbooru::Http.cache(cache_duration).get("https://api.ipregistry.co/#{ip_addr}?key=#{api_key}") return {} if response.status != 200 json = response.parse.deep_symbolize_keys.with_indifferent_access diff --git a/app/logical/media_file/flash.rb b/app/logical/media_file/flash.rb index 6ff99c239..b3d3815ae 100644 --- a/app/logical/media_file/flash.rb +++ b/app/logical/media_file/flash.rb @@ -42,7 +42,7 @@ class MediaFile::Flash < MediaFile signature = contents[0..2] # SWF version - version = contents[3].unpack('C').join.to_i + _version = contents[3].unpack('C').join.to_i # Determine the length of the uncompressed stream length = contents[4..7].unpack('V').join.to_i @@ -50,7 +50,7 @@ class MediaFile::Flash < MediaFile # If we do, in fact, have compression if signature == 'CWS' # Decompress the body of the SWF - body = Zlib::Inflate.inflate( contents[8..length] ) + body = Zlib::Inflate.inflate(contents[8..length]) # And reconstruct the stream contents to the first 8 bytes (header) # Plus our decompressed body @@ -58,10 +58,10 @@ class MediaFile::Flash < MediaFile end # Determine the nbits of our dimensions rectangle - nbits = contents.unpack('C'*contents.length)[8] >> 3 + nbits = contents.unpack('C' * contents.length)[8] >> 3 # Determine how many bits long this entire RECT structure is - rectbits = 5 + nbits * 4 # 5 bits for nbits, as well as nbits * number of fields (4) + rectbits = 5 + nbits * 4 # 5 bits for nbits, as well as nbits * number of fields (4) # Determine how many bytes rectbits composes (ceil(rectbits/8)) rectbytes = (rectbits.to_f / 8).ceil @@ -70,11 +70,11 @@ class MediaFile::Flash < MediaFile rect = contents[8..(8 + rectbytes)].unpack("#{'B8' * rectbytes}").join # Read in nbits incremenets starting from 5 - dimensions = Array.new + dimensions = [] 4.times do |n| s = 5 + (n * nbits) # Calculate our start index e = s + (nbits - 1) # Calculate our end index - dimensions[n] = rect[s..e].to_i(2) # Read that range (binary) and convert it to an integer + dimensions[n] = rect[s..e].to_i(2) # Read that range (binary) and convert it to an integer end # The values we have here are in "twips" diff --git a/app/logical/pagination_extension.rb b/app/logical/pagination_extension.rb index 5d5113e8c..dcb89ee5b 100644 --- a/app/logical/pagination_extension.rb +++ b/app/logical/pagination_extension.rb @@ -106,10 +106,7 @@ module PaginationExtension def total_count @paginator_count ||= unscoped.from(except(:offset, :limit, :order).reorder(nil)).count rescue ActiveRecord::StatementInvalid => e - if e.to_s =~ /statement timeout/ - @paginator_count ||= 1_000_000 - else - raise - end + raise unless e.to_s =~ /statement timeout/ + @paginator_count ||= 1_000_000 end end diff --git a/app/logical/pawoo_api_client.rb b/app/logical/pawoo_api_client.rb index 20daf1fd7..f74d42e38 100644 --- a/app/logical/pawoo_api_client.rb +++ b/app/logical/pawoo_api_client.rb @@ -128,15 +128,15 @@ class PawooApiClient rescue data = {} end - return Account.new(data) + Account.new(data) end end private def fetch_access_token - raise MissingConfigurationError.new("missing pawoo client id") if Danbooru.config.pawoo_client_id.nil? - raise MissingConfigurationError.new("missing pawoo client secret") if Danbooru.config.pawoo_client_secret.nil? + raise MissingConfigurationError, "missing pawoo client id" if Danbooru.config.pawoo_client_id.nil? + raise MissingConfigurationError, "missing pawoo client secret" if Danbooru.config.pawoo_client_secret.nil? Cache.get("pawoo-token") do result = client.client_credentials.get_token diff --git a/app/logical/pixiv_web_agent.rb b/app/logical/pixiv_web_agent.rb index f48a20c50..1a181672b 100644 --- a/app/logical/pixiv_web_agent.rb +++ b/app/logical/pixiv_web_agent.rb @@ -58,7 +58,7 @@ class PixivWebAgent end begin - mech.get("https://comic.pixiv.net") do |page| + mech.get("https://comic.pixiv.net") do cookie = mech.cookies.select {|x| x.name == COMIC_SESSION_COOKIE_KEY}.first if cookie Cache.put(COMIC_SESSION_CACHE_KEY, cookie.value, 1.week) diff --git a/app/logical/post_sets/post.rb b/app/logical/post_sets/post.rb index 2858b9b64..30d76717e 100644 --- a/app/logical/post_sets/post.rb +++ b/app/logical/post_sets/post.rb @@ -94,7 +94,7 @@ module PostSets end def get_random_posts - per_page.times.inject([]) do |all, x| + per_page.times.inject([]) do |all, _| all << ::Post.user_tag_match(tag_string).random end.compact.uniq end @@ -104,9 +104,9 @@ module PostSets @post_count = get_post_count if is_random? - temp = get_random_posts + get_random_posts else - temp = normalized_query.build.paginate(page, count: post_count, search_count: !post_count.nil?, limit: per_page) + normalized_query.build.paginate(page, count: post_count, search_count: !post_count.nil?, limit: per_page) end end end diff --git a/app/logical/spam_detector.rb b/app/logical/spam_detector.rb index a6ca4c2b0..176cc40d6 100644 --- a/app/logical/spam_detector.rb +++ b/app/logical/spam_detector.rb @@ -7,10 +7,11 @@ class SpamDetector # if a person receives more than 10 automatic spam reports within a 1 hour # window, automatically ban them forever. AUTOBAN_THRESHOLD = 10 - AUTOBAN_WINDOW = 1.hours - AUTOBAN_DURATION = 999999 + AUTOBAN_WINDOW = 1.hour + AUTOBAN_DURATION = 999_999 attr_accessor :record, :user, :user_ip, :content, :comment_type + rakismet_attrs author: proc { user.name }, author_email: proc { user.email_address&.address }, blog_lang: "en", @@ -84,8 +85,8 @@ class SpamDetector end is_spam - rescue StandardError => exception - DanbooruLogger.log(exception) + rescue StandardError => e + DanbooruLogger.log(e) false end end diff --git a/app/logical/storage_manager/sftp.rb b/app/logical/storage_manager/sftp.rb index 1eeb1fdce..ef2a2de02 100644 --- a/app/logical/storage_manager/sftp.rb +++ b/app/logical/storage_manager/sftp.rb @@ -21,7 +21,7 @@ class StorageManager::SFTP < StorageManager temp_upload_path = dest_path + "-" + SecureRandom.uuid + ".tmp" dest_backup_path = dest_path + "-" + SecureRandom.uuid + ".bak" - each_host do |host, sftp| + each_host do |_host, sftp| sftp.upload!(file.path, temp_upload_path) sftp.setstat!(temp_upload_path, permissions: DEFAULT_PERMISSIONS) @@ -40,7 +40,7 @@ class StorageManager::SFTP < StorageManager end def delete(dest_path) - each_host do |host, sftp| + each_host do |_host, sftp| force { sftp.remove!(dest_path) } end end diff --git a/app/logical/tag_autocomplete.rb b/app/logical/tag_autocomplete.rb index 31f4c1566..ba34e658c 100644 --- a/app/logical/tag_autocomplete.rb +++ b/app/logical/tag_autocomplete.rb @@ -25,8 +25,7 @@ module TagAutocomplete def search(query) query = Tag.normalize_name(query) - candidates = count_sort( - query, + count_sort( search_exact(query, 8) + search_prefix(query, 4) + search_correct(query, 2) + @@ -34,7 +33,7 @@ module TagAutocomplete ) end - def count_sort(query, words) + def count_sort(words) words.uniq(&:name).sort_by do |x| x.post_count * x.weight end.reverse.slice(0, LIMIT) diff --git a/app/logical/tag_relationship_retirement_service.rb b/app/logical/tag_relationship_retirement_service.rb index e3ad7eda8..0f11ee931 100644 --- a/app/logical/tag_relationship_retirement_service.rb +++ b/app/logical/tag_relationship_retirement_service.rb @@ -4,11 +4,11 @@ module TagRelationshipRetirementService THRESHOLD = 2.years def forum_topic_title - return "Retired tag aliases & implications" + "Retired tag aliases & implications" end def forum_topic_body - return "This topic deals with tag relationships created two or more years ago that have not been used since. They will be retired. This topic will be updated as an automated system retires expired relationships." + "This topic deals with tag relationships created two or more years ago that have not been used since. They will be retired. This topic will be updated as an automated system retires expired relationships." end def dry_run @@ -27,7 +27,7 @@ module TagRelationshipRetirementService forum_post = ForumPost.create!(creator: User.system, body: forum_topic_body, topic: topic) end end - return topic + topic end def find_and_retire! @@ -50,16 +50,6 @@ module TagRelationshipRetirementService yield(rel) end end - - # model.active.where("created_at < ?", SMALL_THRESHOLD.ago).find_each do |rel| - # if is_underused?(rel.consequent_name) - # yield(rel) - # end - # end - end - - def is_underused?(name) - (Tag.find_by_name(name).try(:post_count) || 0) < COUNT_THRESHOLD end def is_unused?(name) diff --git a/app/logical/upload_limit.rb b/app/logical/upload_limit.rb index 014706210..752741d05 100644 --- a/app/logical/upload_limit.rb +++ b/app/logical/upload_limit.rb @@ -2,7 +2,7 @@ class UploadLimit extend Memoist INITIAL_POINTS = 1000 - MAXIMUM_POINTS = 10000 + MAXIMUM_POINTS = 10_000 attr_reader :user @@ -75,7 +75,7 @@ class UploadLimit points += upload_value(points, is_deleted) points = points.clamp(0, MAXIMUM_POINTS) - #warn "slots: %2d, points: %3d, value: %2d" % [UploadLimit.points_to_level(points) + 5, points, UploadLimit.upload_value(level, is_deleted)] + # warn "slots: %2d, points: %3d, value: %2d" % [UploadLimit.points_to_level(points) + 5, points, UploadLimit.upload_value(level, is_deleted)] end points diff --git a/app/logical/upload_service.rb b/app/logical/upload_service.rb index c2473284c..5a5d415b9 100644 --- a/app/logical/upload_service.rb +++ b/app/logical/upload_service.rb @@ -15,7 +15,6 @@ class UploadService start! end rescue ActiveRecord::RecordNotUnique - return end def start! @@ -31,8 +30,8 @@ class UploadService begin create_post_from_upload(@upload) - rescue Exception => x - @upload.update(status: "error: #{x.class} - #{x.message}", backtrace: x.backtrace.join("\n")) + rescue Exception => e + @upload.update(status: "error: #{e.class} - #{e.message}", backtrace: e.backtrace.join("\n")) end return @upload end @@ -53,16 +52,16 @@ class UploadService @upload.save! @post = create_post_from_upload(@upload) - return @upload - rescue Exception => x - @upload.update(status: "error: #{x.class} - #{x.message}", backtrace: x.backtrace.join("\n")) + @upload + rescue Exception => e + @upload.update(status: "error: #{e.class} - #{e.message}", backtrace: e.backtrace.join("\n")) @upload end end def warnings return [] if @post.nil? - return @post.warnings.full_messages + @post.warnings.full_messages end def create_post_from_upload(upload) diff --git a/app/logical/upload_service/controller_helper.rb b/app/logical/upload_service/controller_helper.rb index d311ca19b..f074c8069 100644 --- a/app/logical/upload_service/controller_helper.rb +++ b/app/logical/upload_service/controller_helper.rb @@ -13,7 +13,7 @@ class UploadService rescue Exception end - return [upload, remote_size] + [upload, remote_size] end if file @@ -21,7 +21,7 @@ class UploadService Preprocessor.new(file: file).delayed_start(CurrentUser.id) end - return [upload] + [upload] end end end diff --git a/app/logical/upload_service/preprocessor.rb b/app/logical/upload_service/preprocessor.rb index a0b411818..042c50cc6 100644 --- a/app/logical/upload_service/preprocessor.rb +++ b/app/logical/upload_service/preprocessor.rb @@ -46,11 +46,9 @@ class UploadService def predecessor if md5.present? - return Upload.where(status: ["preprocessed", "preprocessing"], md5: md5).first - end - - if Utils.is_downloadable?(source) - return Upload.where(status: ["preprocessed", "preprocessing"], source: source).first + Upload.where(status: ["preprocessed", "preprocessing"], md5: md5).first + elsif Utils.is_downloadable?(source) + Upload.where(status: ["preprocessed", "preprocessing"], source: source).first end end @@ -63,21 +61,20 @@ class UploadService start! end rescue ActiveRecord::RecordNotUnique - return end def start! if Utils.is_downloadable?(source) if Post.system_tag_match("source:#{canonical_source}").where.not(id: original_post_id).exists? - raise ActiveRecord::RecordNotUnique.new("A post with source #{canonical_source} already exists") + raise ActiveRecord::RecordNotUnique, "A post with source #{canonical_source} already exists" end if Upload.where(source: source, status: "completed").exists? - raise ActiveRecord::RecordNotUnique.new("A completed upload with source #{source} already exists") + raise ActiveRecord::RecordNotUnique, "A completed upload with source #{source} already exists" end if Upload.where(source: source).where("status like ?", "error%").exists? - raise ActiveRecord::RecordNotUnique.new("An errored upload with source #{source} already exists") + raise ActiveRecord::RecordNotUnique, "An errored upload with source #{source} already exists" end end @@ -95,21 +92,21 @@ class UploadService upload.tag_string = params[:tag_string] upload.status = "preprocessed" upload.save! - rescue Exception => x - upload.update(file_ext: nil, status: "error: #{x.class} - #{x.message}", backtrace: x.backtrace.join("\n")) + rescue Exception => e + upload.update(file_ext: nil, status: "error: #{e.class} - #{e.message}", backtrace: e.backtrace.join("\n")) end - return upload + upload end def finish!(upload = nil) - pred = upload || self.predecessor + pred = upload || predecessor # regardless of who initialized the upload, credit should # goto whoever submitted the form pred.initialize_attributes - pred.attributes = self.params + pred.attributes = params # if a file was uploaded after the preprocessing occurred, # then process the file and overwrite whatever the preprocessor @@ -118,7 +115,7 @@ class UploadService pred.status = "completed" pred.save - return pred + pred end end end diff --git a/app/logical/upload_service/replacer.rb b/app/logical/upload_service/replacer.rb index b1ac345d6..f64198c5a 100644 --- a/app/logical/upload_service/replacer.rb +++ b/app/logical/upload_service/replacer.rb @@ -62,7 +62,7 @@ class UploadService end def source_strategy(upload) - return Sources::Strategies.find(upload.source, upload.referer_url) + Sources::Strategies.find(upload.source, upload.referer_url) end def find_replacement_url(repl, upload) @@ -78,7 +78,7 @@ class UploadService return source_strategy(upload).canonical_url end - return upload.source + upload.source end def process! diff --git a/app/logical/upload_service/utils.rb b/app/logical/upload_service/utils.rb index 09c2e47e6..7482d43b6 100644 --- a/app/logical/upload_service/utils.rb +++ b/app/logical/upload_service/utils.rb @@ -83,7 +83,7 @@ class UploadService } end - return file + file end end end diff --git a/app/logical/user_deletion.rb b/app/logical/user_deletion.rb index b87e6297f..8577f19a1 100644 --- a/app/logical/user_deletion.rb +++ b/app/logical/user_deletion.rb @@ -2,6 +2,7 @@ class UserDeletion include ActiveModel::Validations attr_reader :user, :password + validate :validate_deletion def initialize(user, password) From c23fee846fe4347c424ba8d3dfacfc720f4d5f98 Mon Sep 17 00:00:00 2001 From: nonamethanks <hellafrickingepic@gmail.com> Date: Wed, 17 Jun 2020 07:24:34 +0200 Subject: [PATCH 028/173] Add more tags to post replacements filtering and modqueue highlighting. --- config/danbooru_default_config.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb index c3f22f050..b82014e0a 100644 --- a/config/danbooru_default_config.rb +++ b/config/danbooru_default_config.rb @@ -317,13 +317,15 @@ module Danbooru # A list of tags that should be removed when a post is replaced. Regexes allowed. def post_replacement_tag_removals %w[replaceme .*_sample resized upscaled downscaled md5_mismatch - jpeg_artifacts corrupted_image source_request non-web_source] + jpeg_artifacts corrupted_image missing_image missing_sample missing_thumbnail + resolution_mismatch source_larger source_smaller source_request non-web_source] end # Posts with these tags will be highlighted in the modqueue. def modqueue_warning_tags %w[hard_translated self_upload nude_filter third-party_edit screencap - duplicate image_sample md5_mismatch resized upscaled downscaled] + duplicate image_sample md5_mismatch resized upscaled downscaled + resolution_mismatch source_larger source_smaller] end def stripe_secret_key From 91beb288b68db2b846563c94d0f2cbb2f354e60f Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 17 Jun 2020 00:57:55 -0500 Subject: [PATCH 029/173] reportbooru: cache post views endpoint. Cache most viewed posts endpoint for /explore/posts/viewed page. --- app/logical/reportbooru_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logical/reportbooru_service.rb b/app/logical/reportbooru_service.rb index 5da79d724..5ad3cd7f6 100644 --- a/app/logical/reportbooru_service.rb +++ b/app/logical/reportbooru_service.rb @@ -31,7 +31,7 @@ class ReportbooruService def post_view_rankings(date = Date.today, expires_in: 1.minutes) return [] unless enabled? - response = http.get("#{reportbooru_server}/post_views/rank?date=#{date}") + response = http.cache(expires_in).get("#{reportbooru_server}/post_views/rank?date=#{date}") return [] if response.status != 200 JSON.parse(response.to_s.force_encoding("utf-8")) end From 05f9b78ee35d6b90f7cd3b8fd1d35eb1ce9e08f0 Mon Sep 17 00:00:00 2001 From: BrokenEagle <brokeneagle98@yahoo.com> Date: Wed, 17 Jun 2020 07:00:59 +0000 Subject: [PATCH 030/173] Distinctly separate and label explicit/guro content in Pixiv test This helps discern why these tests might be failing and serve as a reminder to set the permissions for the Pixiv account correctly. --- test/unit/sources/pixiv_test.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/unit/sources/pixiv_test.rb b/test/unit/sources/pixiv_test.rb index cb7a3d883..a22e5799d 100644 --- a/test/unit/sources/pixiv_test.rb +++ b/test/unit/sources/pixiv_test.rb @@ -300,17 +300,11 @@ module Sources context "parsing illust ids" do should "parse ids from illust urls" do - assert_illust_id(46324488, "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=46324488") - assert_illust_id(46324488, "https://www.pixiv.net/member_illust.php?mode=manga_big&illust_id=46324488&page=0") - assert_illust_id(46324488, "https://i.pximg.net/img-original/img/2014/10/03/18/10/20/46324488_p0.png") - assert_illust_id(46324488, "https://i.pximg.net/img-master/img/2014/10/03/18/10/20/46324488_p0_master1200.jpg") - assert_illust_id(65015428, "https://tc-pximg01.techorus-cdn.com/img-original/img/2017/09/18/03/18/24/65015428_p4.png") assert_illust_id(46785915, "https://i.pximg.net/c/250x250_80_a2/img-master/img/2014/10/29/09/27/19/46785915_p0_square1200.jpg") assert_illust_id(79584713, "https://i-f.pximg.net/img-original/img/2020/02/19/00/40/18/79584713_p0.png") - assert_illust_id(46323924, "http://i1.pixiv.net/img-zip-ugoira/img/2014/10/03/17/29/16/46323924_ugoira1920x1080.zip") assert_illust_id(46304396, "http://i1.pixiv.net/img-original/img/2014/10/02/13/51/23/46304396_p0.png") assert_illust_id(46304396, "http://i1.pixiv.net/c/600x600/img-master/img/2014/10/02/13/51/23/46304396_p0_master1200.jpg") @@ -329,6 +323,15 @@ module Sources assert_illust_id(18557054, "http://www.pixiv.net/artworks/18557054") end + should "parse ids from expicit/guro illust urls" do + assert_illust_id(46324488, "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=46324488") + assert_illust_id(46324488, "https://www.pixiv.net/member_illust.php?mode=manga_big&illust_id=46324488&page=0") + assert_illust_id(46324488, "https://i.pximg.net/img-original/img/2014/10/03/18/10/20/46324488_p0.png") + assert_illust_id(46324488, "https://i.pximg.net/img-master/img/2014/10/03/18/10/20/46324488_p0_master1200.jpg") + + assert_illust_id(46323924, "http://i1.pixiv.net/img-zip-ugoira/img/2014/10/03/17/29/16/46323924_ugoira1920x1080.zip") + end + should "not misparse ids from fanbox urls" do assert_nil_illust_id("https://fanbox.pixiv.net/images/post/39714/JvjJal8v1yLgc5DPyEI05YpT.png") assert_nil_illust_id("https://pixiv.pximg.net/fanbox/public/images/creator/1566167/profile/Ix6bnJmTaOAFZhXHLbWyIY1e.jpeg") From 158a4aa91686d6921a46336706f0e00a63619b87 Mon Sep 17 00:00:00 2001 From: BrokenEagle <brokeneagle98@yahoo.com> Date: Wed, 17 Jun 2020 06:22:29 +0000 Subject: [PATCH 031/173] Fix Pixiv user profile URL to use the latest format This will only affect new artist and commentary records going forward. --- app/logical/sources/strategies/pixiv.rb | 4 ++-- test/unit/sources/pixiv_test.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/logical/sources/strategies/pixiv.rb b/app/logical/sources/strategies/pixiv.rb index ad7cd7eb1..fdc66c2bf 100644 --- a/app/logical/sources/strategies/pixiv.rb +++ b/app/logical/sources/strategies/pixiv.rb @@ -80,7 +80,7 @@ module Sources text = text.gsub(%r{https?://www\.pixiv\.net/member\.php\?id=([0-9]+)}i) do |_match| member_id = $1 - profile_url = "https://www.pixiv.net/member.php?id=#{member_id}" + profile_url = "https://www.pixiv.net/users/#{member_id}" search_params = {"search[url_matches]" => profile_url}.to_param %("user/#{member_id}":[#{profile_url}] "»":[/artists?#{search_params}]) @@ -155,7 +155,7 @@ module Sources end end - "https://www.pixiv.net/member.php?id=#{metadata.user_id}" + "https://www.pixiv.net/users/#{metadata.user_id}" rescue PixivApiClient::BadIDError nil end diff --git a/test/unit/sources/pixiv_test.rb b/test/unit/sources/pixiv_test.rb index a22e5799d..0cdb5ea62 100644 --- a/test/unit/sources/pixiv_test.rb +++ b/test/unit/sources/pixiv_test.rb @@ -78,7 +78,7 @@ module Sources @site = Sources::Strategies.find("http://www.pixiv.net/fanbox/creator/554149/post/82555") assert_equal("TYONE(お仕事募集中)", @site.artist_name) - assert_equal("https://www.pixiv.net/member.php?id=554149", @site.profile_url) + assert_equal("https://www.pixiv.net/users/554149", @site.profile_url) assert_equal("https://fanbox.pixiv.net/images/post/82555/Lyyeb6dDLcQZmy09nqLZapuS.jpeg", @site.image_url) assert_nothing_raised { @site.to_h } end @@ -104,7 +104,7 @@ module Sources end should "get the profile" do - assert_equal("https://www.pixiv.net/member.php?id=696859", @site.profile_url) + assert_equal("https://www.pixiv.net/users/696859", @site.profile_url) end should "get the artist name" do @@ -205,7 +205,7 @@ module Sources should "convert illust links and member links to dtext" do get_source("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=63421642") - dtext_desc = %(foo 【pixiv #46337015 "»":[/posts?tags=pixiv:46337015]】bar 【pixiv #14901720 "»":[/posts?tags=pixiv:14901720]】\n\nbaz【"user/83739":[https://www.pixiv.net/member.php?id=83739] "»":[/artists?search%5Burl_matches%5D=https%3A%2F%2Fwww.pixiv.net%2Fmember.php%3Fid%3D83739]】) + dtext_desc = %(foo 【pixiv #46337015 "»":[/posts?tags=pixiv:46337015]】bar 【pixiv #14901720 "»":[/posts?tags=pixiv:14901720]】\n\nbaz【"user/83739":[https://www.pixiv.net/users/83739] "»":[/artists?search%5Burl_matches%5D=https%3A%2F%2Fwww.pixiv.net%2Fusers%2F83739]】) assert_equal(dtext_desc, @site.dtext_artist_commentary_desc) end end @@ -293,7 +293,7 @@ module Sources assert_equal("uroobnad", source.tag_name) assert_equal(["uroobnad"], source.other_names) - assert_includes(source.profile_urls, "https://www.pixiv.net/member.php?id=696859") + assert_includes(source.profile_urls, "https://www.pixiv.net/users/696859") assert_includes(source.profile_urls, "https://www.pixiv.net/stacc/uroobnad") end end From b8b5c8d6a0b1eb5eec591615c0062a6e8c970b6d Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 17 Jun 2020 02:28:16 -0500 Subject: [PATCH 032/173] iqdb: fix error with file uploads. Fix "cannot determine size of body" errors on upload page. Caused by exception during IQDB lookup. We were posting the form data wrong, we need to wrap the file with HTTP::FormData::File and pass it through the `form` parameter. --- app/logical/iqdb_proxy.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/logical/iqdb_proxy.rb b/app/logical/iqdb_proxy.rb index e1b1f195f..a61dd3c2c 100644 --- a/app/logical/iqdb_proxy.rb +++ b/app/logical/iqdb_proxy.rb @@ -50,9 +50,12 @@ class IqdbProxy file.try(:close) end - def query(params) + def query(file: nil, url: nil, limit: 20) raise NotImplementedError, "the IQDBs service isn't configured" unless enabled? - response = http.post("#{iqdbs_server}/similar", body: params) + + file = HTTP::FormData::File.new(file) if file + form = { file: file, url: url, limit: limit }.compact + response = http.post("#{iqdbs_server}/similar", form: form) raise Error, "IQDB error: #{response.status}" if response.status != 200 raise Error, "IQDB error: #{response.parse["error"]}" if response.parse.is_a?(Hash) From d5a7eef53d9d521b9dd75e6f927a658ca5c61430 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 17 Jun 2020 04:13:16 -0500 Subject: [PATCH 033/173] uploads: fix remote file size not appearing. --- app/logical/upload_service/controller_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logical/upload_service/controller_helper.rb b/app/logical/upload_service/controller_helper.rb index f074c8069..7342168fd 100644 --- a/app/logical/upload_service/controller_helper.rb +++ b/app/logical/upload_service/controller_helper.rb @@ -13,7 +13,7 @@ class UploadService rescue Exception end - [upload, remote_size] + return [upload, remote_size] end if file From fd6ba473a541996ea570ab5faa29914dc090d9fb Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 17 Jun 2020 12:30:37 -0500 Subject: [PATCH 034/173] tests: possible fix for images getting nuked by tests. --- test/test_helper.rb | 5 ++-- test/unit/storage_manager_test.rb | 44 ++++++++++++------------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index 700bf1e6d..7832ec36c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -45,13 +45,14 @@ class ActiveSupport::TestCase Socket.stubs(:gethostname).returns("www.example.com") WebMock.allow_net_connect! - storage_manager = StorageManager::Local.new(base_dir: Dir.mktmpdir("uploads-test-storage-")) + @temp_dir = Dir.mktmpdir("danbooru-temp-") + storage_manager = StorageManager::Local.new(base_dir: @temp_dir) Danbooru.config.stubs(:storage_manager).returns(storage_manager) Danbooru.config.stubs(:backup_storage_manager).returns(StorageManager::Null.new) end teardown do - FileUtils.rm_rf(Danbooru.config.storage_manager.base_dir) + FileUtils.rm_rf(@temp_dir) Cache.clear end diff --git a/test/unit/storage_manager_test.rb b/test/unit/storage_manager_test.rb index 9759a2918..9d39b03e5 100644 --- a/test/unit/storage_manager_test.rb +++ b/test/unit/storage_manager_test.rb @@ -1,8 +1,6 @@ require 'test_helper' class StorageManagerTest < ActiveSupport::TestCase - BASE_DIR = "#{Rails.root}/tmp/test-storage" - setup do CurrentUser.ip_addr = "127.0.0.1" end @@ -45,25 +43,21 @@ class StorageManagerTest < ActiveSupport::TestCase context "StorageManager::Local" do setup do - @storage_manager = StorageManager::Local.new(base_dir: BASE_DIR, base_url: "/data") - end - - teardown do - FileUtils.rm_rf(BASE_DIR) + @storage_manager = StorageManager::Local.new(base_dir: @temp_dir, base_url: "/data") end context "#store method" do should "store the file" do - @storage_manager.store(StringIO.new("data"), "#{BASE_DIR}/test.txt") + @storage_manager.store(StringIO.new("data"), "#{@temp_dir}/test.txt") - assert("data", File.read("#{BASE_DIR}/test.txt")) + assert("data", File.read("#{@temp_dir}/test.txt")) end should "overwrite the file if it already exists" do - @storage_manager.store(StringIO.new("foo"), "#{BASE_DIR}/test.txt") - @storage_manager.store(StringIO.new("bar"), "#{BASE_DIR}/test.txt") + @storage_manager.store(StringIO.new("foo"), "#{@temp_dir}/test.txt") + @storage_manager.store(StringIO.new("bar"), "#{@temp_dir}/test.txt") - assert("bar", File.read("#{BASE_DIR}/test.txt")) + assert("bar", File.read("#{@temp_dir}/test.txt")) end end @@ -72,7 +66,7 @@ class StorageManagerTest < ActiveSupport::TestCase @storage_manager.store(StringIO.new("data"), "test.txt") @storage_manager.delete("test.txt") - assert_not(File.exist?("#{BASE_DIR}/test.txt")) + assert_not(File.exist?("#{@temp_dir}/test.txt")) end should "not fail if the file doesn't exist" do @@ -88,9 +82,9 @@ class StorageManagerTest < ActiveSupport::TestCase @storage_manager.store_file(StringIO.new("data"), @post, :large) @storage_manager.store_file(StringIO.new("data"), @post, :original) - @file_path = "#{BASE_DIR}/preview/#{@post.md5}.jpg" - @large_file_path = "#{BASE_DIR}/sample/sample-#{@post.md5}.jpg" - @preview_file_path = "#{BASE_DIR}/#{@post.md5}.#{@post.file_ext}" + @file_path = "#{@temp_dir}/preview/#{@post.md5}.jpg" + @large_file_path = "#{@temp_dir}/sample/sample-#{@post.md5}.jpg" + @preview_file_path = "#{@temp_dir}/#{@post.md5}.#{@post.file_ext}" end should "store the files at the correct path" do @@ -134,12 +128,12 @@ class StorageManagerTest < ActiveSupport::TestCase context "when the original_subdir option is used" do should "store original files at the correct path" do @post = FactoryBot.create(:post, file_ext: "png") - @storage_manager = StorageManager::Local.new(base_dir: BASE_DIR, base_url: "/data", original_subdir: "original/") + @storage_manager = StorageManager::Local.new(base_dir: @temp_dir, base_url: "/data", original_subdir: "original/") - assert_equal("#{BASE_DIR}/original/#{@post.md5}.png", @storage_manager.file_path(@post, @post.file_ext, :original)) + assert_equal("#{@temp_dir}/original/#{@post.md5}.png", @storage_manager.file_path(@post, @post.file_ext, :original)) @storage_manager.store_file(StringIO.new("data"), @post, :original) - assert_equal(true, File.exist?("#{BASE_DIR}/original/#{@post.md5}.png")) + assert_equal(true, File.exist?("#{@temp_dir}/original/#{@post.md5}.png")) end end end @@ -151,24 +145,20 @@ class StorageManagerTest < ActiveSupport::TestCase @storage_manager = StorageManager::Hybrid.new do |id, md5, file_ext, type| if id.odd? - StorageManager::Local.new(base_dir: "#{BASE_DIR}/i1", base_url: "/i1") + StorageManager::Local.new(base_dir: "#{@temp_dir}/i1", base_url: "/i1") else - StorageManager::Local.new(base_dir: "#{BASE_DIR}/i2", base_url: "/i2") + StorageManager::Local.new(base_dir: "#{@temp_dir}/i2", base_url: "/i2") end end end - teardown do - FileUtils.rm_rf(BASE_DIR) - end - context "#store_file method" do should "store odd-numbered posts under /i1 and even-numbered posts under /i2" do @storage_manager.store_file(StringIO.new("post1"), @post1, :original) @storage_manager.store_file(StringIO.new("post2"), @post2, :original) - assert(File.exist?("#{BASE_DIR}/i1/#{@post1.md5}.png")) - assert(File.exist?("#{BASE_DIR}/i2/#{@post2.md5}.png")) + assert(File.exist?("#{@temp_dir}/i1/#{@post1.md5}.png")) + assert(File.exist?("#{@temp_dir}/i2/#{@post2.md5}.png")) end end From 7a41ee9c34c591cbe951d73147dee70b061e248a Mon Sep 17 00:00:00 2001 From: nonamethanks <hellafrickingepic@gmail.com> Date: Mon, 1 Jun 2020 19:57:08 +0200 Subject: [PATCH 035/173] Add NewGrounds support --- app/logical/artist_finder.rb | 2 + app/logical/sources/strategies.rb | 3 +- app/logical/sources/strategies/newgrounds.rb | 112 +++++++++++++++++++ test/unit/sources/newgrounds_test.rb | 112 +++++++++++++++++++ 4 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 app/logical/sources/strategies/newgrounds.rb create mode 100644 test/unit/sources/newgrounds_test.rb diff --git a/app/logical/artist_finder.rb b/app/logical/artist_finder.rb index 3cf5c9581..826f380e7 100644 --- a/app/logical/artist_finder.rb +++ b/app/logical/artist_finder.rb @@ -62,6 +62,8 @@ module ArtistFinder "monappy.jp", "monappy.jp/u", # https://monappy.jp/u/abara_bone "mstdn.jp", # https://mstdn.jp/@oneb + "www.newgrounds.com", # https://jessxjess.newgrounds.com/ + "newgrounds.com/art/view/", # https://www.newgrounds.com/art/view/jessxjess/avatar-korra "nicoseiga.jp", "nicoseiga.jp/priv", # http://lohas.nicoseiga.jp/priv/2017365fb6cfbdf47ad26c7b6039feb218c5e2d4/1498430264/6820259 "nicovideo.jp", diff --git a/app/logical/sources/strategies.rb b/app/logical/sources/strategies.rb index 92232e59e..05a0ef2d5 100644 --- a/app/logical/sources/strategies.rb +++ b/app/logical/sources/strategies.rb @@ -13,7 +13,8 @@ module Sources Strategies::Pawoo, Strategies::Moebooru, Strategies::HentaiFoundry, - Strategies::Weibo + Strategies::Weibo, + Strategies::Newgrounds ] end diff --git a/app/logical/sources/strategies/newgrounds.rb b/app/logical/sources/strategies/newgrounds.rb new file mode 100644 index 000000000..69d5d737a --- /dev/null +++ b/app/logical/sources/strategies/newgrounds.rb @@ -0,0 +1,112 @@ +# Image Urls +# * https://art.ngfiles.com/images/1254000/1254722_natthelich_pandora.jpg +# * https://art.ngfiles.com/images/1033000/1033622_natthelich_fire-emblem-marth-plus-progress-pic.png?f1569487181 +# * https://art.ngfiles.com/comments/57000/iu_57615_7115981.jpg +# +# Page URLs +# * https://www.newgrounds.com/art/view/puddbytes/costanza-at-bat +# * https://www.newgrounds.com/art/view/natthelich/fire-emblem-marth-plus-progress-pic (multiple) +# +# +# Profile URLs +# * https://natthelich.newgrounds.com/ + +module Sources + module Strategies + class Newgrounds < Base + IMAGE_URL = %r{https?://art\.ngfiles\.com/images/\d+/\d+_(?<user_name>[0-9a-z-]+)_(?<illust_title>[0-9a-z-]+)\.\w+}i + COMMENT_URL = %r{https?://art\.ngfiles\.com/comments/\d+/\w+\.\w+}i + + PAGE_URL = %r{https?://(?:www\.)?newgrounds\.com/art/view/(?<user_name>[0-9a-z-]+)/(?<illust_title>[0-9a-z-]+)(?:\?.*)?}i + + PROFILE_URL = %r{https?://(?<artist_name>(?!www)[0-9a-z-]+)\.newgrounds\.com(?:/.*)?}i + + def domains + ["newgrounds.com", "ngfiles.com"] + end + + def site_name + "NewGrounds" + end + + def image_urls + if url =~ COMMENT_URL || url =~ IMAGE_URL + [url] + else + urls = [] + + urls += page&.css(".image img").to_a.map { |img| img["src"] } + urls += page&.css("#author_comments img[data-user-image='1']").to_a.map { |img| img["data-smartload-src"] || img["src"] } + + urls.compact + end + end + + def page_url + return nil if illust_title.blank? || user_name.blank? + + "https://www.newgrounds.com/art/view/#{user_name}/#{illust_title}" + end + + def page + return nil if page_url.blank? + doc = Danbooru::Http.cache(1.minute).get(page_url) + + return if doc.code == 404 + + Nokogiri::HTML(doc.body) + end + memoize :page + + def tags + page&.css("#sidestats .tags a").to_a.map do |tag| + [tag.text, "https://www.newgrounds.com/search/conduct/art?match=tags&tags=" + tag.text] + end + end + + def normalize_tag(tag) + tag = tag.tr("-", "_") + super(tag) + end + + def artist_name + name = page&.css(".item-user .item-details h4 a")&.text&.strip || user_name + name&.downcase + end + + def other_names + [artist_name, user_name].compact.uniq + end + + def profile_url + # user names are not mutable, artist names are. + # However we need the latest name for normalization + "https://#{artist_name}.newgrounds.com" + end + + def artist_commentary_title + page&.css(".pod-head > [itemprop='name']")&.text + end + + def artist_commentary_desc + page&.css("#author_comments")&.to_html + end + + def dtext_artist_commentary_desc + DText.from_html(artist_commentary_desc) + end + + def normalize_for_source + page_url + end + + def user_name + urls.map { |u| url[PROFILE_URL, :artist_name] || u[IMAGE_URL, :user_name] || u[PAGE_URL, :user_name] }.compact.first + end + + def illust_title + urls.map { |u| u[IMAGE_URL, :illust_title] || u[PAGE_URL, :illust_title] }.compact.first + end + end + end +end diff --git a/test/unit/sources/newgrounds_test.rb b/test/unit/sources/newgrounds_test.rb new file mode 100644 index 000000000..8c7c57fcb --- /dev/null +++ b/test/unit/sources/newgrounds_test.rb @@ -0,0 +1,112 @@ +require 'test_helper' + +module Sources + class NewGroundsTest < ActiveSupport::TestCase + context "The source for a newgrounds picture" do + setup do + @url = "https://www.newgrounds.com/art/view/natthelich/fire-emblem-marth-plus-progress-pic" + @comment = "https://art.ngfiles.com/comments/57000/iu_57615_7115981.jpg" + @image_1 = Sources::Strategies.find(@url) + @image_2 = Sources::Strategies.find("https://art.ngfiles.com/images/1033000/1033622_natthelich_fire-emblem-marth-plus-progress-pic.png?f1569487181") + @image_3 = Sources::Strategies.find(@comment, @url) + end + + should "get the artist name" do + assert_equal("natthelich", @image_1.artist_name) + assert_equal("natthelich", @image_2.artist_name) + assert_equal("natthelich", @image_3.artist_name) + end + + should "get the artist commentary title" do + assert_equal("Fire Emblem - Marth (plus progress pic)", @image_1.artist_commentary_title) + assert_equal("Fire Emblem - Marth (plus progress pic)", @image_2.artist_commentary_title) + assert_equal("Fire Emblem - Marth (plus progress pic)", @image_3.artist_commentary_title) + end + + should "get profile url" do + assert_equal("https://natthelich.newgrounds.com", @image_1.profile_url) + assert_equal("https://natthelich.newgrounds.com", @image_2.profile_url) + assert_equal("https://natthelich.newgrounds.com", @image_3.profile_url) + end + + should "get the image urls" do + assert_match(%r{https?://art\.ngfiles\.com/images/1033000/1033622_natthelich_fire-emblem-marth-plus-progress-pic\.png(?:\?\w+)?}i, @image_1.image_url) + assert_includes(@image_1.image_urls, @comment) + + assert_match(%r{https?://art\.ngfiles\.com/images/1033000/1033622_natthelich_fire-emblem-marth-plus-progress-pic\.png(?:\?\w+)?}i, @image_2.image_url) + assert_equal(@comment, @image_3.image_url) + end + + should "get the canonical url" do + assert_equal(@url, @image_1.canonical_url) + assert_equal(@url, @image_2.canonical_url) + assert_equal(@url, @image_3.canonical_url) + end + + should "download an image" do + assert_downloaded(630365, @image_1.image_url) + assert_downloaded(630365, @image_2.image_url) + assert_downloaded(129033, @image_3.image_url) + end + + should "get the tags" do + tags = [ + %w[fire-emblem https://www.newgrounds.com/search/conduct/art?match=tags&tags=fire-emblem], + %w[marth https://www.newgrounds.com/search/conduct/art?match=tags&tags=marth ] + ] + assert_equal(tags, @image_1.tags) + assert_equal(tags, @image_2.tags) + assert_equal(tags, @image_3.tags) + end + + should "find the right artist" do + artist_1 = FactoryBot.create(:artist, name: "natthelich1", url_string: "https://natthelich.newgrounds.com/art") + artist_2 = FactoryBot.create(:artist, name: "natthelich2", url_string: "https://www.newgrounds.com/art/view/natthelich/fire-emblem-marth-plus-progress-pic") + artist_3 = FactoryBot.create(:artist, name: "bad_artist", url_string: "https://www.newgrounds.com/art") + + assert_equal([artist_1, artist_2], @image_1.artists) + assert_equal([artist_1, artist_2], @image_2.artists) + assert_equal([artist_1, artist_2], @image_3.artists) + + assert_not_equal([artist_3], @image_1.artists) + end + end + + context "A deleted or not existing picture" do + setup do + @fake_1 = Sources::Strategies.find("https://www.newgrounds.com/art/view/ThisUser/DoesNotExist") + @artist_1 = FactoryBot.create(:artist, name: "thisuser", url_string: "https://thisuser.newgrounds.com") + + @fake_2 = Sources::Strategies.find("https://www.newgrounds.com/art/view/natthelich/nopicture") + @artist_2 = FactoryBot.create(:artist, name: "natthelich", url_string: "https://natthelich.newgrounds.com") + + @fake_3 = Sources::Strategies.find("https://www.newgrounds.com/art/view/theolebrave/sensitive-pochaco") + @artist_3 = FactoryBot.create(:artist, name: "taffytoad", url_string: "https://taffytoad.newgrounds.com") + end + + should "still find the artist name" do + assert_equal("thisuser", @fake_1.artist_name) + assert_equal([@artist_1], @fake_1.artists) + assert_equal("https://thisuser.newgrounds.com", @fake_1.profile_url) + + assert_equal("natthelich", @fake_2.artist_name) + assert_equal([@artist_2], @fake_2.artists) + + assert_equal([@artist_3], @fake_3.artists) + end + end + + context "normalizing for source" do + should "normalize correctly" do + source = "https://art.ngfiles.com/images/1033000/1033622_natthelich_fire-emblem-marth-plus-progress-pic.png?f1569487181" + + assert_equal("https://www.newgrounds.com/art/view/natthelich/fire-emblem-marth-plus-progress-pic", Sources::Strategies.normalize_source(source)) + end + + should "avoid normalizing unnormalizable urls" do + bad_source = "https://art.ngfiles.com/comments/57000/iu_57615_7115981.jpg" + assert_equal(bad_source, Sources::Strategies.normalize_source(bad_source)) + end + end + end +end From f790a1aeedae7965e0d75bc9340c2f4c997c4eab Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Thu, 18 Jun 2020 00:56:42 -0500 Subject: [PATCH 036/173] http: increase default timeout to 10 seconds. Three seconds was little tight and might have caused timeouts in source strategies sometimes. --- app/logical/danbooru/http.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index 343d8d886..666a67f2c 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -1,6 +1,6 @@ module Danbooru class Http - DEFAULT_TIMEOUT = 3 + DEFAULT_TIMEOUT = 10 MAX_REDIRECTS = 5 attr_writer :cache, :http From 459f67c431699e68bc7ba5aa7274be39098a5658 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Thu, 18 Jun 2020 00:57:51 -0500 Subject: [PATCH 037/173] iqdb: fix 599 timeout errors. Increase timeout to 30 seconds when uploading files to IQDB. Previously we used the default timeout of 3 seconds, which could cause 599 timeout errors sometimes if the upload took too long. --- app/logical/iqdb_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logical/iqdb_proxy.rb b/app/logical/iqdb_proxy.rb index a61dd3c2c..67e27bd90 100644 --- a/app/logical/iqdb_proxy.rb +++ b/app/logical/iqdb_proxy.rb @@ -55,7 +55,7 @@ class IqdbProxy file = HTTP::FormData::File.new(file) if file form = { file: file, url: url, limit: limit }.compact - response = http.post("#{iqdbs_server}/similar", form: form) + response = http.timeout(30).post("#{iqdbs_server}/similar", form: form) raise Error, "IQDB error: #{response.status}" if response.status != 200 raise Error, "IQDB error: #{response.parse["error"]}" if response.parse.is_a?(Hash) From 8a06d3a7442646c118584a3177aab0c967a347cb Mon Sep 17 00:00:00 2001 From: nonamethanks <hellafrickingepic@gmail.com> Date: Thu, 18 Jun 2020 08:07:45 +0200 Subject: [PATCH 038/173] Zerochan: Normalize png links --- app/logical/sources/strategies/null.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logical/sources/strategies/null.rb b/app/logical/sources/strategies/null.rb index 4ce04c30c..4dd02ebe6 100644 --- a/app/logical/sources/strategies/null.rb +++ b/app/logical/sources/strategies/null.rb @@ -47,7 +47,7 @@ module Sources when %r{\Ahttps?://c(?:s|han|[1-4])\.sankakucomplex\.com/data(?:/sample)?/(?:[a-f0-9]{2}/){2}(?:sample-|preview)?([a-f0-9]{32})}i "https://chan.sankakucomplex.com/en/post/show?md5=#{$1}" - when %r{\Ahttps?://(?:www|s(?:tatic|[1-4]))\.zerochan\.net/.+(?:\.|\/)(\d+)(?:\.(?:jpe?g?))?\z}i + when %r{\Ahttps?://(?:www|s(?:tatic|[1-4]))\.zerochan\.net/.+(?:\.|\/)(\d+)(?:\.(?:jpe?g?|png))?\z}i "https://www.zerochan.net/#{$1}#full" when %r{\Ahttps?://static[1-6]?\.minitokyo\.net/(?:downloads|view)/(?:\d{2}/){2}(\d+)}i From 213766fac92eca61ed648467ef7ecd182d74f41f Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Thu, 18 Jun 2020 12:24:32 -0500 Subject: [PATCH 039/173] posts: fix "view original" not working on mobile. ref: https://github.com/danbooru/danbooru/commit/38f385d1ca93dba6d7de9f228f61c1f9a1eb4c27#r40000777 --- app/javascript/src/javascripts/posts.js.erb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/javascript/src/javascripts/posts.js.erb b/app/javascript/src/javascripts/posts.js.erb index cdf7b9aaf..9eef63e88 100644 --- a/app/javascript/src/javascripts/posts.js.erb +++ b/app/javascript/src/javascripts/posts.js.erb @@ -300,10 +300,10 @@ Post.initialize_favlist = function() { }); } -Post.view_original = function(e) { +Post.view_original = function(e = null) { if (Utility.test_max_width(660)) { // Do the default behavior (navigate to image) - return false; + return; } var $image = $("#image"); @@ -316,13 +316,13 @@ Post.view_original = function(e) { }); Note.Box.scale_all(); $("body").attr("data-post-current-image-size", "original"); - return false; + e?.preventDefault(); } -Post.view_large = function(e) { +Post.view_large = function(e = null) { if (Utility.test_max_width(660)) { // Do the default behavior (navigate to image) - return false; + return; } var $image = $("#image"); @@ -335,7 +335,7 @@ Post.view_large = function(e) { }); Note.Box.scale_all(); $("body").attr("data-post-current-image-size", "large"); - return false; + e?.preventDefault(); } Post.toggle_fit_window = function(e) { From 0a396c8b95b925e40c2bf87e0666f1613a0fd4fd Mon Sep 17 00:00:00 2001 From: nonamethanks <hellafrickingepic@gmail.com> Date: Fri, 19 Jun 2020 08:08:01 +0200 Subject: [PATCH 040/173] Revert "Pixiv: don't blacklist digital tools" This reverts commit e83d07ea7baa4cd03990d2d701a3cd95f1494500. It was worth a try, but unfortunately it seems that once someone sets tools in a Pixiv upload, they become defaults and are applied to all of their subsequent uploads, so we get some posts with two or three different digital tags. --- app/logical/pixiv_api_client.rb | 17 ++++++++++++++++- test/unit/sources/pixiv_test.rb | 6 +++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/logical/pixiv_api_client.rb b/app/logical/pixiv_api_client.rb index c742e250e..f1d55c981 100644 --- a/app/logical/pixiv_api_client.rb +++ b/app/logical/pixiv_api_client.rb @@ -8,6 +8,21 @@ class PixivApiClient CLIENT_SECRET = "HP3RmkgAmEGro0gn1x9ioawQE8WMfvLXDz3ZqxpK" CLIENT_HASH_SALT = "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c" + # Tools to not include in the tags list. We don't tag digital media, so + # including these results in bad translated tags suggestions. + TOOLS_BLACKLIST = %w[ + Photoshop Illustrator Fireworks Flash Painter PaintShopPro pixiv\ Sketch + CLIP\ STUDIO\ PAINT IllustStudio ComicStudio RETAS\ STUDIO SAI PhotoStudio + Pixia NekoPaint PictBear openCanvas ArtRage Expression Inkscape GIMP + CGillust COMICWORKS MS_Paint EDGE AzPainter AzPainter2 AzDrawing + PicturePublisher SketchBookPro Processing 4thPaint GraphicsGale mdiapp + Paintgraphic AfterEffects drawr CLIP\ PAINT\ Lab FireAlpaca Pixelmator + AzDrawing2 MediBang\ Paint Krita ibisPaint Procreate Live2D + Lightwave3D Shade Poser STRATA AnimationMaster XSI CARRARA CINEMA4D Maya + 3dsMax Blender ZBrush Metasequoia Sunny3D Bryce Vue Hexagon\ King SketchUp + VistaPro Sculptris Comi\ Po! modo DAZ\ Studio 3D-Coat + ] + class Error < StandardError; end class BadIDError < Error; end @@ -24,7 +39,7 @@ class PixivApiClient @artist_commentary_title = json["title"].to_s @artist_commentary_desc = json["caption"].to_s @tags = json["tags"].reject {|x| x =~ /^http:/} - @tags += json["tools"] + @tags += json["tools"] - TOOLS_BLACKLIST if json["metadata"] if json["metadata"]["zip_urls"] diff --git a/test/unit/sources/pixiv_test.rb b/test/unit/sources/pixiv_test.rb index cb7a3d883..f4778615e 100644 --- a/test/unit/sources/pixiv_test.rb +++ b/test/unit/sources/pixiv_test.rb @@ -249,6 +249,10 @@ module Sources assert_includes(@translated_tags, "foo") end + should "not translate tags for digital media" do + assert_equal(false, @tags.include?("Photoshop")) + end + should "normalize 10users入り tags" do assert_includes(@tags, "風景10users入り") assert_includes(@translated_tags, "scenery") @@ -280,7 +284,7 @@ module Sources should "not translate '1000users入り' to '1'" do FactoryBot.create(:tag, name: "1", post_count: 1) source = get_source("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=60665428") - tags = %w[1000users入り Fate/GrandOrder アルジュナ(Fate) アルトリア・ペンドラゴン イシュタル(Fate) グランブルーファンタジー マシュ・キリエライト マーリン(Fate) 両儀式 手袋 CLIP\ STUDIO\ PAINT Photoshop] + tags = %w[1000users入り Fate/GrandOrder アルジュナ(Fate) アルトリア・ペンドラゴン イシュタル(Fate) グランブルーファンタジー マシュ・キリエライト マーリン(Fate) 両儀式 手袋] assert_equal(tags.sort, source.tags.map(&:first).sort) assert_equal(["fate/grand_order"], source.translated_tags.map(&:name)) From ae7fc7d1bc64f5ec3025778d6623ff7a99b47bae Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Thu, 18 Jun 2020 22:12:47 -0500 Subject: [PATCH 041/173] Update eslint to 7.0, add babel-eslint plugin. Add babel-eslint plugin so that eslint can recognize optional chaining syntax (a?.b). --- .eslintrc.yml | 6 +- package.json | 4 +- yarn.lock | 396 ++++++++++++++++++++++++++------------------------ 3 files changed, 213 insertions(+), 193 deletions(-) diff --git a/.eslintrc.yml b/.eslintrc.yml index 1bb08c0bc..df6581f1c 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -8,9 +8,13 @@ parserOptions: globals: $: false require: false +parser: babel-eslint +plugins: + - babel rules: # https://eslint.org/docs/rules/ array-callback-return: error + babel/no-unused-expressions: error block-scoped-var: error consistent-return: error default-case: error @@ -32,7 +36,7 @@ rules: no-sequences: error no-shadow: error no-shadow-restricted-names: error - no-unused-expressions: error + #no-unused-expressions: error no-unused-vars: - error - argsIgnorePattern: "^_" diff --git a/package.json b/package.json index 28fa41a81..e715e9198 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,10 @@ "webpack-cli": "^3.3.0" }, "devDependencies": { - "eslint": "^6.0.0", + "babel-eslint": "^10.1.0", + "eslint": "^7.0.0", "eslint-loader": "^4.0.0", + "eslint-plugin-babel": "^5.3.0", "eslint-plugin-ignore-erb": "^0.1.1", "stylelint": "^13.0.0", "stylelint-config-standard": "^20.0.0", diff --git a/yarn.lock b/yarn.lock index 54b157113..f062816ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -252,7 +252,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.10.1", "@babel/parser@^7.10.2": +"@babel/parser@^7.10.1", "@babel/parser@^7.10.2", "@babel/parser@^7.7.0": version "7.10.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.2.tgz#871807f10442b92ff97e4783b9b54f6a0ca812d0" integrity sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ== @@ -775,7 +775,7 @@ "@babel/parser" "^7.10.1" "@babel/types" "^7.10.1" -"@babel/traverse@^7.10.1": +"@babel/traverse@^7.10.1", "@babel/traverse@^7.7.0": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.1.tgz#bbcef3031e4152a6c0b50147f4958df54ca0dd27" integrity sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ== @@ -790,7 +790,7 @@ globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.10.1", "@babel/types@^7.10.2", "@babel/types@^7.4.4": +"@babel/types@^7.10.1", "@babel/types@^7.10.2", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.10.2" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.2.tgz#30283be31cad0dbf6fb00bd40641ca0ea675172d" integrity sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng== @@ -805,9 +805,9 @@ integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== "@fortawesome/fontawesome-free@^5.11.2": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.13.0.tgz#fcb113d1aca4b471b709e8c9c168674fbd6e06d9" - integrity sha512-xKOeQEl5O47GPZYIMToj6uuA2syyFlq9EMSl2ui0uytjY9xbe8XS0pexNWmxrdcCyNGyDmLyYw5FtKsalBUeOg== + version "5.13.1" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.13.1.tgz#c53b4066edae16cd1fd669f687baf031b45fb9d6" + integrity sha512-D819f34FLHeBN/4xvw0HR0u7U2G7RqjPSggXqf7LktsxWQ48VAfGwvMrhcVuaZV2fF069c/619RdgCCms0DHhw== "@nodelib/fs.scandir@2.1.3": version "2.1.3" @@ -1125,7 +1125,7 @@ acorn@^6.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== -acorn@^7.1.1: +acorn@^7.2.0: version "7.3.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== @@ -1422,6 +1422,18 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== +babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + babel-loader@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" @@ -1883,9 +1895,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061: - version "1.0.30001083" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001083.tgz#52410c20c6f029f604f0d45eca0439a82e712442" - integrity sha512-CnYJ27awX4h7yj5glfK7r1TOI13LBytpLzEgfj0s4mY75/F8pnQcYjL+oVpmS38FB59+vU0gscQ9D8tc+lIXvA== + version "1.0.30001084" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001084.tgz#00e471931eaefbeef54f46aa2203914d3c165669" + integrity sha512-ftdc5oGmhEbLUuMZ/Qp3mOpzfZLCxPYKcvGv6v2dJJ+8EdqcvZRbAGOiLmkM/PV1QGta/uwBs8/nCl6sokDW6w== case-sensitive-paths-webpack-plugin@^2.3.0: version "2.3.0" @@ -1911,15 +1923,6 @@ chalk@2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@2.4.2, chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chalk@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -1931,6 +1934,15 @@ chalk@^1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" +chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -1939,7 +1951,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== @@ -2374,7 +2386,15 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: +cross-spawn@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" + integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI= + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + +cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -2385,13 +2405,14 @@ cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" - integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI= +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: - lru-cache "^4.0.1" - which "^1.2.9" + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" crypto-browserify@^3.11.0: version "3.12.0" @@ -2623,7 +2644,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@^3.0.0, debug@^3.1.1, debug@^3.2.5: +debug@^3.1.1, debug@^3.2.5: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -2720,7 +2741,7 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" -deep-is@~0.1.3: +deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= @@ -2935,14 +2956,14 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.413: - version "1.3.474" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.474.tgz#161af012e11f96795eade84bf03b8ddc039621b9" - integrity sha512-fPkSgT9IBKmVJz02XioNsIpg0WYmkPrvU1lUJblMMJALxyE7/32NGvbJQKKxpNokozPvqfqkuUqVClYsvetcLw== + version "1.3.477" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.477.tgz#7e6b931d0c1a2572101a6e9a835128c50fd49323" + integrity sha512-81p6DZ/XmHDD7O0ITJMa7ESo9bSCfE+v3Fny3MIYR0y77xmhoriu2ShNOLXcPS4eowF6dkxw6d2QqxTkS3DjBg== elliptic@^6.0.0, elliptic@^6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" - integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== + version "6.5.3" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" + integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -2984,16 +3005,7 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - tapable "^1.0.0" - -enhanced-resolve@^4.1.0: +enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz#5d43bda4a0fd447cb0ebbe71bef8deff8805ad0d" integrity sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ== @@ -3078,6 +3090,13 @@ eslint-loader@^4.0.0: object-hash "^2.0.3" schema-utils "^2.6.5" +eslint-plugin-babel@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-5.3.0.tgz#2e7f251ccc249326da760c1a4c948a91c32d0023" + integrity sha512-HPuNzSPE75O+SnxHIafbW5QB45r2w78fxqwK3HmjqIUoPfPzVrq6rD+CINU3yzoDSzEhUkX07VUphbF73Lth/w== + dependencies: + eslint-rule-composer "^0.3.0" + eslint-plugin-ignore-erb@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/eslint-plugin-ignore-erb/-/eslint-plugin-ignore-erb-0.1.1.tgz#951497d935c7d8a713a67f6bbbaa92e29bf0826d" @@ -3085,6 +3104,11 @@ eslint-plugin-ignore-erb@^0.1.1: dependencies: requireindex "~1.1.0" +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -3093,7 +3117,7 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^5.0.0: +eslint-scope@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== @@ -3101,34 +3125,34 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== +eslint-utils@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" -eslint-visitor-keys@^1.1.0: +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa" integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ== -eslint@^6.0.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" - integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== +eslint@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.2.0.tgz#d41b2e47804b30dbabb093a967fb283d560082e6" + integrity sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" - chalk "^2.1.0" - cross-spawn "^6.0.5" + chalk "^4.0.0" + cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" - eslint-scope "^5.0.0" - eslint-utils "^1.4.3" - eslint-visitor-keys "^1.1.0" - espree "^6.1.2" - esquery "^1.0.1" + eslint-scope "^5.1.0" + eslint-utils "^2.0.0" + eslint-visitor-keys "^1.2.0" + espree "^7.1.0" + esquery "^1.2.0" esutils "^2.0.2" file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" @@ -3141,36 +3165,35 @@ eslint@^6.0.0: is-glob "^4.0.0" js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" + levn "^0.4.1" lodash "^4.17.14" minimatch "^3.0.4" - mkdirp "^0.5.1" natural-compare "^1.4.0" - optionator "^0.8.3" + optionator "^0.9.1" progress "^2.0.0" - regexpp "^2.0.1" - semver "^6.1.2" - strip-ansi "^5.2.0" - strip-json-comments "^3.0.1" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" table "^5.2.3" text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^6.1.2: - version "6.2.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" - integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== +espree@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.1.0.tgz#a9c7f18a752056735bf1ba14cb1b70adc3a5ce1c" + integrity sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw== dependencies: - acorn "^7.1.1" + acorn "^7.2.0" acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.1.0" + eslint-visitor-keys "^1.2.0" esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.0.1: +esquery@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== @@ -3374,9 +3397,9 @@ fast-deep-equal@^3.1.1: integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.1.1: - version "3.2.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.3.tgz#64d6bf0e32f1195ab45ac8896e4adbe267ccd798" - integrity sha512-fWSEEcoqcYqlFJrpSH5dJTwv6o0r+2bLAmnlne8OQMbFhpSTQXA8Ngp6q1DGA4B+eewHeuH5ndZeiV2qyXXNsA== + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -3390,7 +3413,7 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= @@ -3548,7 +3571,7 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -findup-sync@3.0.0: +findup-sync@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== @@ -3586,11 +3609,9 @@ flush-write-stream@^1.0.0: readable-stream "^2.3.6" follow-redirects@^1.0.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb" - integrity sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA== - dependencies: - debug "^3.0.0" + version "1.12.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.12.1.tgz#de54a6205311b93d60398ebc01cf7015682312b6" + integrity sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg== for-in@^1.0.2: version "1.0.2" @@ -3828,7 +3849,7 @@ global-modules@1.0.0, global-modules@^1.0.0: is-windows "^1.0.1" resolve-dir "^1.0.0" -global-modules@2.0.0, global-modules@^2.0.0: +global-modules@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== @@ -4255,7 +4276,7 @@ import-lazy@^4.0.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== -import-local@2.0.0, import-local@^2.0.0: +import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== @@ -4350,12 +4371,7 @@ internal-ip@^4.3.0: default-gateway "^4.2.0" ipaddr.js "^1.9.0" -interpret@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" - integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== - -interpret@^1.0.0: +interpret@^1.0.0, interpret@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== @@ -4932,13 +4948,13 @@ levenary@^1.1.1: dependencies: leven "^3.1.0" -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" + prelude-ls "^1.2.1" + type-check "~0.4.0" lines-and-columns@^1.1.6: version "1.1.6" @@ -4961,15 +4977,6 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" - integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== - dependencies: - big.js "^5.2.2" - emojis-list "^2.0.0" - json5 "^1.0.1" - loader-utils@^0.2.12: version "0.2.17" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" @@ -5233,7 +5240,7 @@ mem@^4.0.0: mimic-fn "^2.0.0" p-is-promise "^2.0.0" -memory-fs@^0.4.0, memory-fs@^0.4.1: +memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -5902,17 +5909,17 @@ optimize-css-assets-webpack-plugin@^5.0.3: cssnano "^4.1.10" last-call-webpack-plugin "^3.0.0" -optionator@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" original@^1.0.0: version "1.0.2" @@ -5931,7 +5938,7 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-locale@^3.0.0, os-locale@^3.1.0: +os-locale@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== @@ -6164,6 +6171,11 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -6981,10 +6993,10 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.1 source-map "^0.6.1" supports-color "^6.1.0" -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prepend-http@^1.0.0: version "1.0.4" @@ -7335,10 +7347,10 @@ regexp.prototype.flags@^1.2.0: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== +regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== regexpu-core@^4.7.0: version "4.7.0" @@ -7757,11 +7769,16 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.1.2, semver@^6.3.0: +semver@^6.0.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.2.1: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -7873,11 +7890,23 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + shelljs@^0.8.1: version "0.8.4" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" @@ -8343,7 +8372,7 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@^3.0.1: +strip-json-comments@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== @@ -8388,15 +8417,15 @@ stylelint-config-standard@^20.0.0: stylelint-config-recommended "^3.0.0" stylelint@^13.0.0: - version "13.6.0" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.6.0.tgz#3528bc402a71f2af2a3de32fa4e9f1c24e49666d" - integrity sha512-55gG2pNjVr183JJM/tlr3KAua6vTVX7Ho/lgKKuCIWszTZ1gmrXjX4Wok53SI8wRYFPbwKAcJGULQ77OJxTcNw== + version "13.6.1" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.6.1.tgz#cc1d76338116d55e8ff2be94c4a4386c1239b878" + integrity sha512-XyvKyNE7eyrqkuZ85Citd/Uv3ljGiuYHC6UiztTR6sWS9rza8j3UeQv/eGcQS9NZz/imiC4GKdk1EVL3wst5vw== dependencies: "@stylelint/postcss-css-in-js" "^0.37.1" "@stylelint/postcss-markdown" "^0.36.1" autoprefixer "^9.8.0" balanced-match "^1.0.0" - chalk "^4.0.0" + chalk "^4.1.0" cosmiconfig "^6.0.0" debug "^4.1.1" execall "^2.0.0" @@ -8448,13 +8477,6 @@ sugarss@^2.0.0: dependencies: postcss "^7.0.2" -supports-color@6.1.0, supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -8467,6 +8489,13 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + supports-color@^7.0.0, supports-color@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" @@ -8566,9 +8595,9 @@ terser-webpack-plugin@^2.3.5: webpack-sources "^1.4.3" terser@^4.1.2, terser@^4.6.12: - version "4.7.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.7.0.tgz#15852cf1a08e3256a80428e865a2fa893ffba006" - integrity sha512-Lfb0RiZcjRDXCC3OSHJpEkxJ9Qeqs6mp2v4jf2MHfy8vGERmVDuvjXdd/EnP5Deme5F2yBRBymKmKHCBg2echw== + version "4.8.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" + integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -8587,9 +8616,9 @@ thenify-all@^1.0.0: thenify ">= 3.1.0 < 4" "thenify@>= 3.1.0 < 4": - version "3.3.0" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" - integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk= + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== dependencies: any-promise "^1.0.0" @@ -8762,12 +8791,12 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: - prelude-ls "~1.1.2" + prelude-ls "^1.2.1" type-fest@^0.11.0: version "0.11.0" @@ -9085,11 +9114,6 @@ uuid@^3.3.2, uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -v8-compile-cache@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" - integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== - v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" @@ -9190,21 +9214,21 @@ webpack-assets-manifest@^3.1.1: webpack-sources "^1.0.0" webpack-cli@^3.3.0, webpack-cli@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.11.tgz#3bf21889bf597b5d82c38f215135a411edfdc631" - integrity sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g== + version "3.3.12" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" + integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== dependencies: - chalk "2.4.2" - cross-spawn "6.0.5" - enhanced-resolve "4.1.0" - findup-sync "3.0.0" - global-modules "2.0.0" - import-local "2.0.0" - interpret "1.2.0" - loader-utils "1.2.3" - supports-color "6.1.0" - v8-compile-cache "2.0.3" - yargs "13.2.4" + chalk "^2.4.2" + cross-spawn "^6.0.5" + enhanced-resolve "^4.1.1" + findup-sync "^3.0.0" + global-modules "^2.0.0" + import-local "^2.0.0" + interpret "^1.4.0" + loader-utils "^1.4.0" + supports-color "^6.1.0" + v8-compile-cache "^2.1.1" + yargs "^13.3.2" webpack-dev-middleware@^3.7.2: version "3.7.2" @@ -9334,6 +9358,13 @@ which@1, which@^1.2.14, which@^1.2.9, which@^1.3.1: dependencies: isexe "^2.0.0" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -9341,7 +9372,7 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" -word-wrap@~1.2.3: +word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== @@ -9437,7 +9468,7 @@ yargs-parser@^11.1.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^13.1.0, yargs-parser@^13.1.2: +yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== @@ -9471,23 +9502,6 @@ yargs@12.0.5: y18n "^3.2.1 || ^4.0.0" yargs-parser "^11.1.1" -yargs@13.2.4: - version "13.2.4" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" - integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - os-locale "^3.1.0" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.0" - yargs@^13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" From 491a53ffe3848060090dd6e439401aee5c86531b Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Thu, 18 Jun 2020 22:07:43 -0500 Subject: [PATCH 042/173] Update mock_redis. --- Gemfile.lock | 2 +- app/models/saved_search.rb | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b9cc72ac4..c671469e6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -221,7 +221,7 @@ GEM minitest (>= 5.0) ruby-progressbar mocha (1.11.2) - mock_redis (0.23.0) + mock_redis (0.24.0) msgpack (1.3.3) msgpack (1.3.3-x64-mingw32) multi_json (1.14.1) diff --git a/app/models/saved_search.rb b/app/models/saved_search.rb index 36478284f..722f47eb1 100644 --- a/app/models/saved_search.rb +++ b/app/models/saved_search.rb @@ -18,8 +18,7 @@ class SavedSearch < ApplicationRecord post_ids = Set.new queries.each do |query| redis_key = "search:#{query}" - # XXX change to `exists?` (ref: https://github.com-sds/mock_redis/pull/188 - if redis.exists(redis_key) + if redis.exists?(redis_key) sub_ids = redis.smembers(redis_key).map(&:to_i) post_ids.merge(sub_ids) else From 440bbbb2889770e107209cb610d71af8a1913c3e Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Thu, 18 Jun 2020 14:24:41 -0500 Subject: [PATCH 043/173] Update nokogiri gem. Fix gem version conflicts described in 20abd8a5f. Nokogiri couldn't be upgraded past 1.10.9 because 1.11.0 causes a build failure in Nokogumbo 2.0.2, but we couldn't stay on 1.10.9 either because it has a hard requirement on Ruby <2.7 and we require Ruby >=2.7. This made `bundle update` fail with a Gemfile conflict. The fix is to disable libxml2 support when building Nokogumbo. Nokogumbo wants to use the same version of libxml2 as Nokogiri, but Nokogiri 1.11.0 changed how it reports which version of libxml2 it's using, which causes Nokogumbo's build to fail. Disabling libxml2 may reduce performance of Nokogumbo ([1]). While we're at it, we also make Nokogiri use the system version of libxml2 instead of its own bundled version. Nokogiri really wants us to use its own patched version of libxml2 instead of the system version, but the patches it applies look relatively minor and don't seem relevant to us ([2]). Using the system version reduces build time during CI. This adds libxml2 and libxslt as OS-level dependencies of Danbooru. You may need to do `sudo apt-get install libxml2-dev libxslt-dev` to install these libraries after this commit. [1]: https://github.com/rubys/nokogumbo#flavors-of-nokogumbo [2]: https://github.com/sparklemotion/nokogiri/tree/master/patches/libxml2 --- .bundle/config | 3 +++ .github/workflows/test.yaml | 2 +- .gitignore | 1 - Gemfile | 4 +--- Gemfile.lock | 11 +++++------ config/docker/Dockerfile.danbooru | 10 +++++++++- 6 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 .bundle/config diff --git a/.bundle/config b/.bundle/config new file mode 100644 index 000000000..b64ad2111 --- /dev/null +++ b/.bundle/config @@ -0,0 +1,3 @@ +--- +BUNDLE_BUILD__NOKOGIRI: "--use-system-libraries" +BUNDLE_BUILD__NOKOGUMBO: "--without-libxml2" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 53485a29a..592dac48b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -64,7 +64,7 @@ jobs: - name: Install OS dependencies run: | apt-get update - apt-get -y install --no-install-recommends build-essential ruby ruby-dev ruby-bundler git nodejs yarnpkg webpack ffmpeg mkvtoolnix libvips-dev libxml2-dev postgresql-server-dev-all wget + apt-get -y install --no-install-recommends build-essential ruby ruby-dev ruby-bundler git nodejs yarnpkg webpack ffmpeg mkvtoolnix libvips-dev libxml2-dev libxslt-dev zlib1g-dev postgresql-server-dev-all wget ln -sf /usr/bin/yarnpkg /usr/bin/yarn - name: Install Ruby dependencies diff --git a/.gitignore b/.gitignore index 8f4292733..a51a3e83b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ .yarn-integrity .gitconfig .git/ -.bundle/ config/database.yml config/danbooru_local_config.rb config/deploy/*.rb diff --git a/Gemfile b/Gemfile index ad30bc238..4304f26cd 100644 --- a/Gemfile +++ b/Gemfile @@ -47,9 +47,7 @@ gem 'http' gem 'activerecord-hierarchical_query' gem 'pundit' gem 'mail' - -# locked to 1.10.9 to workaround an incompatibility with nokogumbo 2.0.2. -gem 'nokogiri', '~> 1.10.9' +gem 'nokogiri' group :production, :staging do gem 'unicorn', :platforms => :ruby diff --git a/Gemfile.lock b/Gemfile.lock index c671469e6..338a3929b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -211,7 +211,7 @@ GEM mime-types-data (3.2020.0512) mimemagic (0.3.5) mini_mime (1.0.2) - mini_portile2 (2.4.0) + mini_portile2 (2.5.0) minitest (5.14.1) minitest-ci (3.4.0) minitest (>= 5.0.6) @@ -239,10 +239,9 @@ GEM net-ssh (6.1.0) newrelic_rpm (6.11.0.365) nio4r (2.5.2) - nokogiri (1.10.9) - mini_portile2 (~> 2.4.0) - nokogiri (1.10.9-x64-mingw32) - mini_portile2 (~> 2.4.0) + nokogiri (1.11.0.rc2) + mini_portile2 (~> 2.5.0) + nokogiri (1.11.0.rc2-x64-mingw32) nokogumbo (2.0.2) nokogiri (~> 1.8, >= 1.8.4) ntlm-http (0.1.1) @@ -465,7 +464,7 @@ DEPENDENCIES mock_redis net-sftp newrelic_rpm - nokogiri (~> 1.10.9) + nokogiri oauth2 pg pry-byebug diff --git a/config/docker/Dockerfile.danbooru b/config/docker/Dockerfile.danbooru index df2af68bd..013644a89 100644 --- a/config/docker/Dockerfile.danbooru +++ b/config/docker/Dockerfile.danbooru @@ -13,14 +13,20 @@ RUN \ webpack \ libvips-dev \ libxml2-dev \ + libxslt-dev \ + zlib1g-dev \ postgresql-server-dev-all && \ # webpacker expects the binary to be called `yarn`, but debian/ubuntu installs it as `yarnpkg`. ln -sf /usr/bin/yarnpkg /usr/bin/yarn WORKDIR /build +COPY .bundle .bundle COPY Gemfile Gemfile.lock ./ -RUN BUNDLE_DEPLOYMENT=true bundle install --jobs 4 +RUN \ + bundle config set deployment true --local && \ + bundle config set path vendor/bundle && \ + bundle install --jobs 4 COPY package.json yarn.lock ./ RUN yarn install @@ -44,6 +50,8 @@ RUN \ mkvtoolnix \ libvips \ libxml2 \ + libxslt1.1 \ + zlib1g \ postgresql-client USER danbooru From cd9e3e7f3d2319b603bb9a7b6608e6e14c9feeb5 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 19 Jun 2020 03:14:25 -0500 Subject: [PATCH 044/173] Update Rails to 6.0.3.2. Fixes CVE-2020-8185: Untrusted users able to run pending migrations in production. --- Gemfile.lock | 102 +++++++++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 338a3929b..7933ca1f5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,63 +8,63 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (6.0.3.1) - actionpack (= 6.0.3.1) + actioncable (6.0.3.2) + actionpack (= 6.0.3.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.3.1) - actionpack (= 6.0.3.1) - activejob (= 6.0.3.1) - activerecord (= 6.0.3.1) - activestorage (= 6.0.3.1) - activesupport (= 6.0.3.1) + actionmailbox (6.0.3.2) + actionpack (= 6.0.3.2) + activejob (= 6.0.3.2) + activerecord (= 6.0.3.2) + activestorage (= 6.0.3.2) + activesupport (= 6.0.3.2) mail (>= 2.7.1) - actionmailer (6.0.3.1) - actionpack (= 6.0.3.1) - actionview (= 6.0.3.1) - activejob (= 6.0.3.1) + actionmailer (6.0.3.2) + actionpack (= 6.0.3.2) + actionview (= 6.0.3.2) + activejob (= 6.0.3.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.3.1) - actionview (= 6.0.3.1) - activesupport (= 6.0.3.1) + actionpack (6.0.3.2) + actionview (= 6.0.3.2) + activesupport (= 6.0.3.2) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.3.1) - actionpack (= 6.0.3.1) - activerecord (= 6.0.3.1) - activestorage (= 6.0.3.1) - activesupport (= 6.0.3.1) + actiontext (6.0.3.2) + actionpack (= 6.0.3.2) + activerecord (= 6.0.3.2) + activestorage (= 6.0.3.2) + activesupport (= 6.0.3.2) nokogiri (>= 1.8.5) - actionview (6.0.3.1) - activesupport (= 6.0.3.1) + actionview (6.0.3.2) + activesupport (= 6.0.3.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.3.1) - activesupport (= 6.0.3.1) + activejob (6.0.3.2) + activesupport (= 6.0.3.2) globalid (>= 0.3.6) - activemodel (6.0.3.1) - activesupport (= 6.0.3.1) + activemodel (6.0.3.2) + activesupport (= 6.0.3.2) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (6.0.3.1) - activemodel (= 6.0.3.1) - activesupport (= 6.0.3.1) + activerecord (6.0.3.2) + activemodel (= 6.0.3.2) + activesupport (= 6.0.3.2) activerecord-hierarchical_query (1.2.3) activerecord (>= 5.0, < 6.1) pg (>= 0.21, < 1.3) - activestorage (6.0.3.1) - actionpack (= 6.0.3.1) - activejob (= 6.0.3.1) - activerecord (= 6.0.3.1) + activestorage (6.0.3.2) + actionpack (= 6.0.3.2) + activejob (= 6.0.3.2) + activerecord (= 6.0.3.2) marcel (~> 0.3.1) - activesupport (6.0.3.1) + activesupport (6.0.3.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -184,7 +184,7 @@ GEM listen (3.2.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.5.0) + loofah (2.6.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -280,29 +280,29 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.0.3.1) - actioncable (= 6.0.3.1) - actionmailbox (= 6.0.3.1) - actionmailer (= 6.0.3.1) - actionpack (= 6.0.3.1) - actiontext (= 6.0.3.1) - actionview (= 6.0.3.1) - activejob (= 6.0.3.1) - activemodel (= 6.0.3.1) - activerecord (= 6.0.3.1) - activestorage (= 6.0.3.1) - activesupport (= 6.0.3.1) + rails (6.0.3.2) + actioncable (= 6.0.3.2) + actionmailbox (= 6.0.3.2) + actionmailer (= 6.0.3.2) + actionpack (= 6.0.3.2) + actiontext (= 6.0.3.2) + actionview (= 6.0.3.2) + activejob (= 6.0.3.2) + activemodel (= 6.0.3.2) + activerecord (= 6.0.3.2) + activestorage (= 6.0.3.2) + activesupport (= 6.0.3.2) bundler (>= 1.3.0) - railties (= 6.0.3.1) + railties (= 6.0.3.2) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (6.0.3.1) - actionpack (= 6.0.3.1) - activesupport (= 6.0.3.1) + railties (6.0.3.2) + actionpack (= 6.0.3.2) + activesupport (= 6.0.3.2) method_source rake (>= 0.8.7) thor (>= 0.20.3, < 2.0) From 05f78f9beff248600ab16088dad1b948e765f497 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 19 Jun 2020 12:13:56 -0500 Subject: [PATCH 045/173] saved searches: fix call to redis.exists? https://github.com/redis/redis-rb/blob/master/CHANGELOG.md#421 --- app/models/saved_search.rb | 2 +- test/unit/post_query_builder_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/saved_search.rb b/app/models/saved_search.rb index 722f47eb1..d992bf11d 100644 --- a/app/models/saved_search.rb +++ b/app/models/saved_search.rb @@ -115,7 +115,7 @@ class SavedSearch < ApplicationRecord def populate(query, timeout: 10_000) redis_key = "search:#{query}" - return if redis.exists(redis_key) + return if redis.exists?(redis_key) post_ids = Post.with_timeout(timeout, [], query: query) do Post.system_tag_match(query).limit(QUERY_LIMIT).pluck(:id) diff --git a/test/unit/post_query_builder_test.rb b/test/unit/post_query_builder_test.rb index d74b08e8e..055e6e765 100644 --- a/test/unit/post_query_builder_test.rb +++ b/test/unit/post_query_builder_test.rb @@ -757,8 +757,8 @@ class PostQueryBuilderTest < ActiveSupport::TestCase create(:saved_search, query: "aaa", labels: ["zzz"], user: CurrentUser.user) create(:saved_search, query: "bbb", user: CurrentUser.user) - Redis.any_instance.stubs(:exists).with("search:aaa").returns(true) - Redis.any_instance.stubs(:exists).with("search:bbb").returns(true) + Redis.any_instance.stubs(:exists?).with("search:aaa").returns(true) + Redis.any_instance.stubs(:exists?).with("search:bbb").returns(true) Redis.any_instance.stubs(:smembers).with("search:aaa").returns([@post1.id]) Redis.any_instance.stubs(:smembers).with("search:bbb").returns([@post2.id]) From 67a52dbc2d9ca82962a470d073c00d821836e4b3 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 19 Jun 2020 12:43:41 -0500 Subject: [PATCH 046/173] tumblr: support new va.media.tumblr.com urls. --- app/logical/sources/strategies/tumblr.rb | 2 +- test/unit/sources/tumblr_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/logical/sources/strategies/tumblr.rb b/app/logical/sources/strategies/tumblr.rb index 6123f9f8b..b82a1f77d 100644 --- a/app/logical/sources/strategies/tumblr.rb +++ b/app/logical/sources/strategies/tumblr.rb @@ -23,7 +23,7 @@ module Sources::Strategies OLD_IMAGE = %r{\Ahttps?://#{DOMAIN}/(?<dir>#{MD5}/)?#{FILENAME}_(?<size>\w+)\.#{EXT}\z}i IMAGE = %r{\Ahttps?://#{DOMAIN}/}i - VIDEO = %r{\Ahttps?://(?:vtt|ve\.media)\.tumblr\.com/}i + VIDEO = %r{\Ahttps?://(?:vtt|ve|va\.media)\.tumblr\.com/}i POST = %r{\Ahttps?://(?<blog_name>[^.]+)\.tumblr\.com/(?:post|image)/(?<post_id>\d+)}i def self.enabled? diff --git a/test/unit/sources/tumblr_test.rb b/test/unit/sources/tumblr_test.rb index 3b774f934..fdba14560 100644 --- a/test/unit/sources/tumblr_test.rb +++ b/test/unit/sources/tumblr_test.rb @@ -169,7 +169,7 @@ module Sources context "The source for a 'http://ve.media.tumblr.com/*' video post with inline images" do setup do - @url = "https://ve.media.tumblr.com/tumblr_os31dkexhK1wsfqep.mp4" + @url = "https://va.media.tumblr.com/tumblr_os31dkexhK1wsfqep.mp4" @ref = "https://noizave.tumblr.com/post/162222617101" end @@ -177,7 +177,7 @@ module Sources should "get the video and inline images" do site = Sources::Strategies.find(@url, @ref) urls = %w[ - https://ve.media.tumblr.com/tumblr_os31dkexhK1wsfqep.mp4 + https://va.media.tumblr.com/tumblr_os31dkexhK1wsfqep.mp4 https://media.tumblr.com/afed9f5b3c33c39dc8c967e262955de2/tumblr_inline_os31dclyCR1v11u29_1280.png ] From 7a1efc2744f0f9ba3e21632b5c7779f29116982b Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 19 Jun 2020 13:49:22 -0500 Subject: [PATCH 047/173] Fix #4522: Sidebar doesn't show most searched tags at certain times of day. Revert back to previous workaround of fetching previous day if current day returns no result. A terrible hack, really we should convert dates to Reportbooru's timezone, but that has other complications. --- app/controllers/explore/posts_controller.rb | 4 +-- app/logical/reportbooru_service.rb | 29 ++++++++++--------- .../explore/posts/missed_searches.html.erb | 2 +- app/views/explore/posts/searches.html.erb | 2 +- test/unit/reportbooru_service_test.rb | 8 ++--- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/app/controllers/explore/posts_controller.rb b/app/controllers/explore/posts_controller.rb index e40d6c5b3..a3a47423c 100644 --- a/app/controllers/explore/posts_controller.rb +++ b/app/controllers/explore/posts_controller.rb @@ -28,11 +28,11 @@ module Explore def searches @date, @scale, @min_date, @max_date = parse_date(params) - @search_service = ReportbooruService.new + @searches = ReportbooruService.new.popular_searches(@date) end def missed_searches - @search_service = ReportbooruService.new + @missed_searches = ReportbooruService.new.missed_search_rankings end private diff --git a/app/logical/reportbooru_service.rb b/app/logical/reportbooru_service.rb index 5ad3cd7f6..9ae703367 100644 --- a/app/logical/reportbooru_service.rb +++ b/app/logical/reportbooru_service.rb @@ -20,29 +20,30 @@ class ReportbooruService body.lines.map(&:split).map { [_1, _2.to_i] } end - def post_search_rankings(date = Date.today, expires_in: 1.minutes) - return [] unless enabled? - - response = http.cache(expires_in).get("#{reportbooru_server}/post_searches/rank?date=#{date}") - return [] if response.status != 200 - JSON.parse(response.to_s.force_encoding("utf-8")) + def post_search_rankings(date, expires_in: 1.minutes) + request("#{reportbooru_server}/post_searches/rank?date=#{date}", expires_in) end - def post_view_rankings(date = Date.today, expires_in: 1.minutes) - return [] unless enabled? - - response = http.cache(expires_in).get("#{reportbooru_server}/post_views/rank?date=#{date}") - return [] if response.status != 200 - JSON.parse(response.to_s.force_encoding("utf-8")) + def post_view_rankings(date, expires_in: 1.minutes) + request("#{reportbooru_server}/post_views/rank?date=#{date}", expires_in) end - def popular_searches(date = Date.today, limit: 100) + def popular_searches(date, limit: 100) ranking = post_search_rankings(date) ranking.take(limit).map(&:first) end - def popular_posts(date = Date.today, limit: 100) + def popular_posts(date, limit: 100) ranking = post_view_rankings(date) + ranking = post_view_rankings(date.yesterday) if ranking.blank? ranking.take(limit).map { |x| Post.find(x[0]) } end + + def request(url, expires_in) + return [] unless enabled? + + response = http.cache(expires_in).get(url) + return [] if response.status != 200 + JSON.parse(response.to_s.force_encoding("utf-8")) + end end diff --git a/app/views/explore/posts/missed_searches.html.erb b/app/views/explore/posts/missed_searches.html.erb index 171017b87..329b66c43 100644 --- a/app/views/explore/posts/missed_searches.html.erb +++ b/app/views/explore/posts/missed_searches.html.erb @@ -15,7 +15,7 @@ </tr> </thead> <tbody> - <% @search_service.missed_search_rankings.each do |tags, count| %> + <% @missed_searches.each do |tags, count| %> <tr class="tag-type-<%= Tag.category_for(tags) %>"> <td><%= link_to tags, posts_path(:tags => tags) %></td> <td> diff --git a/app/views/explore/posts/searches.html.erb b/app/views/explore/posts/searches.html.erb index fb9e3446e..5256427eb 100644 --- a/app/views/explore/posts/searches.html.erb +++ b/app/views/explore/posts/searches.html.erb @@ -13,7 +13,7 @@ </tr> </thead> <tbody> - <% @search_service.post_search_rankings(@date).each do |tags, count| %> + <% @searches.each do |tags, count| %> <tr class="tag-type-<%= Tag.category_for(tags) %>"> <td><%= link_to tags, posts_path(:tags => tags) %></td> <td style="text-align: right;"><%= count.to_i %></td> diff --git a/test/unit/reportbooru_service_test.rb b/test/unit/reportbooru_service_test.rb index 3d795d130..d31e340f6 100644 --- a/test/unit/reportbooru_service_test.rb +++ b/test/unit/reportbooru_service_test.rb @@ -4,20 +4,20 @@ class ReportbooruServiceTest < ActiveSupport::TestCase def setup @service = ReportbooruService.new(reportbooru_server: "http://localhost:1234") @post = create(:post) - @date = "2000-01-01" + @date = Date.parse("2000-01-01") end context "#popular_posts" do should "return the list of popular posts on success" do - body = "[[#{@post.id},100.0]]" - @service.http.expects(:get).with("http://localhost:1234/post_views/rank?date=#{@date}").returns(HTTP::Response.new(status: 200, body: body, version: "1.1")) + mock_post_view_rankings(@date, [[@post.id, 100]]) posts = @service.popular_posts(@date) assert_equal([@post], posts) end should "return nothing on failure" do - @service.http.expects(:get).with("http://localhost:1234/post_views/rank?date=#{@date}").returns(HTTP::Response.new(status: 500, body: "", version: "1.1")) + Danbooru::Http.any_instance.expects(:get).with("http://localhost:1234/post_views/rank?date=#{@date}").returns(HTTP::Response.new(status: 500, body: "", version: "1.1")) + Danbooru::Http.any_instance.expects(:get).with("http://localhost:1234/post_views/rank?date=#{@date.yesterday}").returns(HTTP::Response.new(status: 500, body: "", version: "1.1")) assert_equal([], @service.popular_posts(@date)) end From 10b7a534499e88c90086c3b0ba53c07dde17e50a Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 19 Jun 2020 15:09:43 -0500 Subject: [PATCH 048/173] unicorn: increase socket backlog. --- config/unicorn/production.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/unicorn/production.rb b/config/unicorn/production.rb index e53d8a24f..7ac586706 100644 --- a/config/unicorn/production.rb +++ b/config/unicorn/production.rb @@ -6,7 +6,7 @@ worker_processes 20 timeout 180 # listen "127.0.0.1:9000", :tcp_nopush => true -listen "/tmp/.unicorn.sock", :backlog => 512 +listen "/tmp/.unicorn.sock", backlog: 1024 # Spawn unicorn master worker for user apps (group: apps) user 'danbooru', 'danbooru' From 26ad844bbe9be28eead133701f42036f7fd8bb62 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Mon, 15 Jun 2020 04:41:42 -0500 Subject: [PATCH 049/173] downloads: refactor Downloads::File into Danbooru::Http. Remove the Downloads::File class. Move download methods to Danbooru::Http instead. This means that: * HTTParty has been replaced with http.rb for downloading files. * Downloading is no longer tightly coupled to source strategies. Before Downloads::File tried to automatically look up the source and download the full size image instead if we gave it a sample url. Now we can do plain downloads without source strategies altering the url. * The Cloudflare Polish check has been changed from checking for a Cloudflare IP to checking for the CF-Polished header. Looking up the list of Cloudflare IPs was slow and flaky during testing. * The SSRF protection code has been factored out so it can be used for normal http requests, not just for downloads. * The Webmock gem can be removed, since it was only used for stubbing out certain HTTParty requests in the download tests. The Webmock gem is buggy and caused certain tests to fail during CI. * The retriable gem can be removed, since we no longer autoretry failed downloads. We assume that if a download fails once then retrying probably won't help. --- app/logical/cloudflare_service.rb | 9 -- app/logical/danbooru/http.rb | 66 +++++++++- app/logical/downloads/file.rb | 123 ------------------ app/logical/iqdb_proxy.rb | 7 +- app/logical/sources/strategies/base.rb | 20 ++- .../upload_service/controller_helper.rb | 7 +- app/logical/upload_service/utils.rb | 8 +- app/logical/validating_socket.rb | 27 ++++ test/test_helpers/download_test_helper.rb | 6 +- test/unit/cloudflare_service_test.rb | 6 - test/unit/danbooru_http_test.rb | 48 ++++++- test/unit/downloads/art_station_test.rb | 34 +---- test/unit/downloads/file_test.rb | 81 ------------ test/unit/downloads/pixiv_test.rb | 14 +- test/unit/upload_service_test.rb | 2 +- 15 files changed, 173 insertions(+), 285 deletions(-) delete mode 100644 app/logical/downloads/file.rb create mode 100644 app/logical/validating_socket.rb delete mode 100644 test/unit/downloads/file_test.rb diff --git a/app/logical/cloudflare_service.rb b/app/logical/cloudflare_service.rb index 6a1161502..8cc1be195 100644 --- a/app/logical/cloudflare_service.rb +++ b/app/logical/cloudflare_service.rb @@ -9,15 +9,6 @@ class CloudflareService api_token.present? && zone.present? end - def ips(expiry: 24.hours) - response = Danbooru::Http.cache(expiry).get("https://api.cloudflare.com/client/v4/ips") - return [] if response.code != 200 - - result = response.parse["result"] - ips = result["ipv4_cidrs"] + result["ipv6_cidrs"] - ips.map { |ip| IPAddr.new(ip) } - end - def purge_cache(urls) return unless enabled? diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index 666a67f2c..b87f5e60c 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -1,18 +1,25 @@ module Danbooru class Http + class DownloadError < StandardError; end + class FileTooLargeError < StandardError; end + DEFAULT_TIMEOUT = 10 MAX_REDIRECTS = 5 - attr_writer :cache, :http + attr_writer :cache, :max_size, :http class << self - delegate :get, :put, :post, :delete, :cache, :follow, :timeout, :auth, :basic_auth, :headers, to: :new + delegate :get, :head, :put, :post, :delete, :cache, :follow, :max_size, :timeout, :auth, :basic_auth, :headers, :public_only, :download_media, to: :new end def get(url, **options) request(:get, url, **options) end + def head(url, **options) + request(:head, url, **options) + end + def put(url, **options) request(:get, url, **options) end @@ -33,6 +40,10 @@ module Danbooru dup.tap { |o| o.http = o.http.follow(*args) } end + def max_size(size) + dup.tap { |o| o.max_size = size } + end + def timeout(*args) dup.tap { |o| o.http = o.http.timeout(*args) } end @@ -49,6 +60,46 @@ module Danbooru dup.tap { |o| o.http = o.http.headers(*args) } end + # allow requests only to public IPs, not to local or private networks. + def public_only + dup.tap do |o| + o.http = o.http.dup.tap do |http| + http.default_options = http.default_options.with_socket_class(ValidatingSocket) + end + end + end + + concerning :DownloadMethods do + def download_media(url, no_polish: true, **options) + url = Addressable::URI.heuristic_parse(url) + response = headers(Referer: url.origin).get(url) + + # prevent Cloudflare Polish from modifying images. + if no_polish && response.headers["CF-Polished"].present? + url.query_values = url.query_values.to_h.merge(danbooru_no_polish: SecureRandom.uuid) + return download_media(url, no_polish: false) + end + + file = download_response(response, **options) + [response, MediaFile.open(file)] + end + + def download_response(response, file: Tempfile.new("danbooru-download-", binmode: true)) + raise DownloadError, response if response.status != 200 + raise FileTooLargeError, response if @max_size && response.content_length.to_i > @max_size + + size = 0 + response.body.each do |chunk| + size += chunk.size + raise FileTooLargeError if @max_size && size > @max_size + file.write(chunk) + end + + file.rewind + file + end + end + protected def request(method, url, **options) @@ -57,11 +108,12 @@ module Danbooru else raw_request(method, url, **options) end + rescue ValidatingSocket::ProhibitedIpError + fake_response(597, "") rescue HTTP::Redirector::TooManyRedirectsError - ::HTTP::Response.new(status: 598, body: "", version: "1.1") + fake_response(598, "") rescue HTTP::TimeoutError - # return a synthetic http error on connection timeouts - ::HTTP::Response.new(status: 599, body: "", version: "1.1") + fake_response(599, "") end def cached_request(method, url, **options) @@ -79,6 +131,10 @@ module Danbooru http.send(method, url, **options) end + def fake_response(status, body) + ::HTTP::Response.new(status: status, version: "1.1", body: ::HTTP::Response::Body.new(body)) + end + def http @http ||= ::HTTP. follow(strict: false, max_hops: MAX_REDIRECTS). diff --git a/app/logical/downloads/file.rb b/app/logical/downloads/file.rb deleted file mode 100644 index 8bbab504b..000000000 --- a/app/logical/downloads/file.rb +++ /dev/null @@ -1,123 +0,0 @@ -require 'resolv' - -module Downloads - class File - include ActiveModel::Validations - class Error < StandardError; end - - RETRIABLE_ERRORS = [Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EIO, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Timeout::Error, IOError] - - delegate :data, to: :strategy - attr_reader :url, :referer - - validate :validate_url - - def initialize(url, referer = nil) - @url = Addressable::URI.parse(url) rescue nil - @referer = referer - validate! - end - - def size - res = HTTParty.head(uncached_url, **httparty_options, timeout: 3) - - if res.success? - res.content_length - else - raise HTTParty::ResponseError.new(res) - end - end - - def download!(url: uncached_url, tries: 3, **options) - Retriable.retriable(on: RETRIABLE_ERRORS, tries: tries, base_interval: 0) do - file = http_get_streaming(url, headers: strategy.headers, **options) - return [file, strategy] - end - end - - def validate_url - errors[:base] << "URL must not be blank" if url.blank? - errors[:base] << "'#{url}' is not a valid url" if !url.host.present? - errors[:base] << "'#{url}' is not a valid url. Did you mean 'http://#{url}'?" if !url.scheme.in?(%w[http https]) - end - - def http_get_streaming(url, file: Tempfile.new(binmode: true), headers: {}, max_size: Danbooru.config.max_file_size) - size = 0 - - res = HTTParty.get(url, httparty_options) do |chunk| - next if chunk.code == 302 - - size += chunk.size - raise Error.new("File is too large (max size: #{max_size})") if size > max_size && max_size > 0 - - file.write(chunk) - end - - if res.success? - file.rewind - return file - else - raise Error.new("HTTP error code: #{res.code} #{res.message}") - end - end - - # Prevent Cloudflare from potentially mangling the image. See issue #3528. - def uncached_url - return file_url unless is_cloudflare?(file_url) - - url = file_url.dup - url.query_values = url.query_values.to_h.merge(danbooru_no_cache: SecureRandom.uuid) - url - end - - def preview_url - @preview_url ||= Addressable::URI.parse(strategy.preview_url) - end - - def file_url - @file_url ||= Addressable::URI.parse(strategy.image_url) - end - - def strategy - @strategy ||= Sources::Strategies.find(url.to_s, referer) - end - - def httparty_options - { - timeout: 10, - stream_body: true, - headers: strategy.headers, - connection_adapter: ValidatingConnectionAdapter - }.deep_merge(Danbooru.config.httparty_options) - end - - def is_cloudflare?(url) - ip_addr = IPAddr.new(Resolv.getaddress(url.hostname)) - CloudflareService.new.ips.any? { |subnet| subnet.include?(ip_addr) } - end - - def self.banned_ip?(ip) - ip = IPAddress.parse(ip.to_s) unless ip.is_a?(IPAddress) - - if ip.ipv4? - ip.loopback? || ip.link_local? || ip.multicast? || ip.private? - elsif ip.ipv6? - ip.loopback? || ip.link_local? || ip.unique_local? || ip.unspecified? - end - end - end - - # Hook into HTTParty to validate the IP before following redirects. - # https://www.rubydoc.info/github/jnunemaker/httparty/HTTParty/ConnectionAdapter - class ValidatingConnectionAdapter < HTTParty::ConnectionAdapter - def self.call(uri, options) - ip_addr = IPAddress.parse(::Resolv.getaddress(uri.hostname)) - - if Downloads::File.banned_ip?(ip_addr) - raise Downloads::File::Error, "Downloads from #{ip_addr} are not allowed" - end - - super(uri, options) - end - end -end diff --git a/app/logical/iqdb_proxy.rb b/app/logical/iqdb_proxy.rb index 67e27bd90..41dc5cd63 100644 --- a/app/logical/iqdb_proxy.rb +++ b/app/logical/iqdb_proxy.rb @@ -12,8 +12,9 @@ class IqdbProxy end def download(url, type) - download = Downloads::File.new(url) - file, strategy = download.download!(url: download.send(type)) + strategy = Sources::Strategies.find(url) + download_url = strategy.send(type) + file = strategy.download_file!(download_url) file end @@ -32,7 +33,7 @@ class IqdbProxy file = download(params[:image_url], :url) results = query(file: file, limit: limit) elsif params[:file_url].present? - file = download(params[:file_url], :file_url) + file = download(params[:file_url], :image_url) results = query(file: file, limit: limit) elsif params[:post_id].present? url = Post.find(params[:post_id]).preview_file_url diff --git a/app/logical/sources/strategies/base.rb b/app/logical/sources/strategies/base.rb index 0ee78f365..fcc0ba6e6 100644 --- a/app/logical/sources/strategies/base.rb +++ b/app/logical/sources/strategies/base.rb @@ -14,6 +14,8 @@ module Sources module Strategies class Base + class DownloadError < StandardError; end + attr_reader :url, :referer_url, :urls, :parsed_url, :parsed_referer, :parsed_urls extend Memoist @@ -35,8 +37,8 @@ module Sources # <tt>referrer_url</tt> so the strategy can discover the HTML # page and other information. def initialize(url, referer_url = nil) - @url = url - @referer_url = referer_url + @url = url.to_s + @referer_url = referer_url&.to_s @urls = [url, referer_url].select(&:present?) @parsed_url = Addressable::URI.heuristic_parse(url) rescue nil @@ -144,10 +146,22 @@ module Sources # Returns the size of the image resource without actually downloading the file. def size - Downloads::File.new(image_url).size + http.head(image_url).content_length.to_i end memoize :size + # Download the file at the given url, or at the main image url by default. + def download_file!(download_url = image_url) + response, file = http.download_media(download_url) + raise DownloadError, "Download failed: #{download_url} returned error #{response.status}" if response.status != 200 + file + end + + def http + Danbooru::Http.public_only.timeout(30).max_size(Danbooru.config.max_file_size) + end + memoize :http + # The url to use for artist finding purposes. This will be stored in the # artist entry. Normally this will be the profile url. def normalize_for_artist_finder diff --git a/app/logical/upload_service/controller_helper.rb b/app/logical/upload_service/controller_helper.rb index 7342168fd..9eeb6d442 100644 --- a/app/logical/upload_service/controller_helper.rb +++ b/app/logical/upload_service/controller_helper.rb @@ -7,11 +7,8 @@ class UploadService # this gets called from UploadsController#new so we need to preprocess async UploadPreprocessorDelayedStartJob.perform_later(url, ref, CurrentUser.user) - begin - download = Downloads::File.new(url, ref) - remote_size = download.size - rescue Exception - end + strategy = Sources::Strategies.find(url, ref) + remote_size = strategy.size return [upload, remote_size] end diff --git a/app/logical/upload_service/utils.rb b/app/logical/upload_service/utils.rb index 7482d43b6..e4316cd90 100644 --- a/app/logical/upload_service/utils.rb +++ b/app/logical/upload_service/utils.rb @@ -71,13 +71,13 @@ class UploadService return file if file.present? raise "No file or source URL provided" if upload.source_url.blank? - download = Downloads::File.new(upload.source_url, upload.referer_url) - file, strategy = download.download! + strategy = Sources::Strategies.find(upload.source_url, upload.referer_url) + file = strategy.download_file! - if download.data[:ugoira_frame_data].present? + if strategy.data[:ugoira_frame_data].present? upload.context = { "ugoira" => { - "frame_data" => download.data[:ugoira_frame_data], + "frame_data" => strategy.data[:ugoira_frame_data], "content_type" => "image/jpeg" } } diff --git a/app/logical/validating_socket.rb b/app/logical/validating_socket.rb new file mode 100644 index 000000000..446609073 --- /dev/null +++ b/app/logical/validating_socket.rb @@ -0,0 +1,27 @@ +# A TCPSocket wrapper that disallows connections to local or private IPs. Used for SSRF protection. +# https://owasp.org/www-community/attacks/Server_Side_Request_Forgery + +require "resolv" + +class ValidatingSocket < TCPSocket + class ProhibitedIpError < StandardError; end + + def initialize(hostname, port) + ip = validate_hostname!(hostname) + super(ip, port) + end + + def validate_hostname!(hostname) + ip = IPAddress.parse(::Resolv.getaddress(hostname)) + raise ProhibitedIpError, "Connection to #{hostname} failed; #{ip} is a prohibited IP" if prohibited_ip?(ip) + ip.to_s + end + + def prohibited_ip?(ip) + if ip.ipv4? + ip.loopback? || ip.link_local? || ip.multicast? || ip.private? + elsif ip.ipv6? + ip.loopback? || ip.link_local? || ip.unique_local? || ip.unspecified? + end + end +end diff --git a/test/test_helpers/download_test_helper.rb b/test/test_helpers/download_test_helper.rb index 72805cf9b..eea711588 100644 --- a/test/test_helpers/download_test_helper.rb +++ b/test/test_helpers/download_test_helper.rb @@ -1,8 +1,8 @@ module DownloadTestHelper def assert_downloaded(expected_filesize, source, referer = nil) - download = Downloads::File.new(source, referer) - tempfile, strategy = download.download! - assert_equal(expected_filesize, tempfile.size, "Tested source URL: #{source}") + strategy = Sources::Strategies.find(source, referer) + file = strategy.download_file! + assert_equal(expected_filesize, file.size, "Tested source URL: #{source}") rescue Net::OpenTimeout skip "Remote connection to #{source} failed" end diff --git a/test/unit/cloudflare_service_test.rb b/test/unit/cloudflare_service_test.rb index faced9207..146068a6d 100644 --- a/test/unit/cloudflare_service_test.rb +++ b/test/unit/cloudflare_service_test.rb @@ -14,10 +14,4 @@ class CloudflareServiceTest < ActiveSupport::TestCase assert_requested(:delete, "https://api.cloudflare.com/client/v4/zones/123/purge_cache", times: 1) end end - - context "#ips" do - should "work" do - refute_empty(@cloudflare.ips) - end - end end diff --git a/test/unit/danbooru_http_test.rb b/test/unit/danbooru_http_test.rb index 42e585bef..f7529491b 100644 --- a/test/unit/danbooru_http_test.rb +++ b/test/unit/danbooru_http_test.rb @@ -4,7 +4,7 @@ class DanbooruHttpTest < ActiveSupport::TestCase context "Danbooru::Http" do context "#get method" do should "work for all basic methods" do - %i[get put post delete].each do |method| + %i[get head put post delete].each do |method| response = Danbooru::Http.send(method, "https://httpbin.org/status/200") assert_equal(200, response.status) end @@ -26,8 +26,10 @@ class DanbooruHttpTest < ActiveSupport::TestCase end should "fail if the request takes too long to download" do - response = Danbooru::Http.timeout(1).get("https://httpbin.org/drip?duration=5&numbytes=5") - assert_equal(599, response.status) + # XXX should return status 599 instead + assert_raises(HTTP::TimeoutError) do + response = Danbooru::Http.timeout(1).get("https://httpbin.org/drip?duration=10&numbytes=10").flush + end end should "automatically decompress gzipped responses" do @@ -45,5 +47,45 @@ class DanbooruHttpTest < ActiveSupport::TestCase assert_equal(response2.body, response1.body) end end + + context "#download method" do + should "download files" do + response, file = Danbooru::Http.download_media("https://httpbin.org/bytes/1000") + + assert_equal(200, response.status) + assert_equal(1000, file.size) + end + + should "follow redirects when downloading files" do + response, file = Danbooru::Http.download_media("https://httpbin.org/redirect-to?url=https://httpbin.org/bytes/1000") + + assert_equal(200, response.status) + assert_equal(1000, file.size) + end + + should "fail if the url points to a private IP" do + assert_raises(Danbooru::Http::DownloadError) do + Danbooru::Http.public_only.download_media("https://127.0.0.1.xip.io") + end + end + + should "fail if the url redirects to a private IP" do + assert_raises(Danbooru::Http::DownloadError) do + Danbooru::Http.public_only.download_media("https://httpbin.org/redirect-to?url=https://127.0.0.1.xip.io") + end + end + + should "fail if a download is too large" do + assert_raises(Danbooru::Http::FileTooLargeError) do + response, file = Danbooru::Http.max_size(500).download_media("https://httpbin.org/bytes/1000") + end + end + + should "fail if a streaming download is too large" do + assert_raises(Danbooru::Http::FileTooLargeError) do + response, file = Danbooru::Http.max_size(500).download_media("https://httpbin.org/stream-bytes/1000") + end + end + end end end diff --git a/test/unit/downloads/art_station_test.rb b/test/unit/downloads/art_station_test.rb index 0ebacd585..83b1710ca 100644 --- a/test/unit/downloads/art_station_test.rb +++ b/test/unit/downloads/art_station_test.rb @@ -3,53 +3,27 @@ require 'test_helper' module Downloads class ArtStationTest < ActiveSupport::TestCase context "a download for a (small) artstation image" do - setup do - @asset = "https://cdnb3.artstation.com/p/assets/images/images/003/716/071/small/aoi-ogata-hate-city.jpg?1476754974" - @download = Downloads::File.new(@asset) - end - should "download the /4k/ image instead" do - file, strategy = @download.download! - assert_equal(1_880_910, ::File.size(file.path)) + assert_downloaded(1_880_910, "https://cdnb3.artstation.com/p/assets/images/images/003/716/071/small/aoi-ogata-hate-city.jpg?1476754974") end end context "for an image where an original does not exist" do - setup do - @asset = "https://cdna.artstation.com/p/assets/images/images/004/730/278/large/mendel-oh-dragonll.jpg" - @download = Downloads::File.new(@asset) - end - should "not try to download the original" do - file, strategy = @download.download! - assert_equal(483_192, ::File.size(file.path)) + assert_downloaded(483_192, "https://cdna.artstation.com/p/assets/images/images/004/730/278/large/mendel-oh-dragonll.jpg") end end context "a download for an ArtStation image hosted on CloudFlare" do - setup do - @asset = "https://cdnb.artstation.com/p/assets/images/images/003/716/071/large/aoi-ogata-hate-city.jpg?1476754974" - end - should "return the original file, not the polished file" do + @asset = "https://cdnb.artstation.com/p/assets/images/images/003/716/071/large/aoi-ogata-hate-city.jpg?1476754974" assert_downloaded(1_880_910, @asset) end - - should "return the original filesize, not the polished filesize" do - assert_equal(1_880_910, Downloads::File.new(@asset).size) - end end context "a download for a https://$artist.artstation.com/projects/$id page" do - setup do - @source = "https://dantewontdie.artstation.com/projects/YZK5q" - @download = Downloads::File.new(@source) - end - should "download the original image instead" do - file, strategy = @download.download! - - assert_equal(247_350, ::File.size(file.path)) + assert_downloaded(247_350, "https://dantewontdie.artstation.com/projects/YZK5q") end end end diff --git a/test/unit/downloads/file_test.rb b/test/unit/downloads/file_test.rb deleted file mode 100644 index 7e37787dd..000000000 --- a/test/unit/downloads/file_test.rb +++ /dev/null @@ -1,81 +0,0 @@ -require 'test_helper' - -module Downloads - class FileTest < ActiveSupport::TestCase - context "A post download" do - setup do - @source = "http://www.google.com/intl/en_ALL/images/logo.gif" - @download = Downloads::File.new(@source) - end - - context "for a banned IP" do - setup do - Resolv.expects(:getaddress).returns("127.0.0.1").at_least_once - end - - should "not try to download the file" do - assert_raise(Downloads::File::Error) { Downloads::File.new("http://evil.com").download! } - end - - should "not try to fetch the size" do - assert_raise(Downloads::File::Error) { Downloads::File.new("http://evil.com").size } - end - - should "not follow redirects to banned IPs" do - url = "http://httpbin.org/redirect-to?url=http://127.0.0.1" - stub_request(:get, url).to_return(status: 301, headers: { "Location": "http://127.0.0.1" }) - - assert_raise(Downloads::File::Error) { Downloads::File.new(url).download! } - end - - should "not follow redirects that resolve to a banned IP" do - url = "http://httpbin.org/redirect-to?url=http://127.0.0.1.nip.io" - stub_request(:get, url).to_return(status: 301, headers: { "Location": "http://127.0.0.1.xip.io" }) - - assert_raise(Downloads::File::Error) { Downloads::File.new(url).download! } - end - end - - context "that fails" do - should "retry three times before giving up" do - HTTParty.expects(:get).times(3).raises(Errno::ETIMEDOUT) - assert_raises(Errno::ETIMEDOUT) { @download.download! } - end - - should "return an uncorrupted file on the second try" do - bomb = stub("bomb") - bomb.stubs(:code).raises(IOError) - resp = stub("resp", success?: true) - - chunk = stub("a") - chunk.stubs(:code).returns(200) - chunk.stubs(:size).returns(1) - chunk.stubs(:to_s).returns("a") - - HTTParty.expects(:get).twice.multiple_yields(chunk, bomb).then.multiple_yields(chunk, chunk).returns(resp) - @download.stubs(:is_cloudflare?).returns(false) - tempfile, _strategy = @download.download! - - assert_equal("aa", tempfile.read) - end - end - - should "throw an exception when the file is larger than the maximum" do - assert_raise(Downloads::File::Error) do - @download.download!(max_size: 1) - end - end - - should "store the file in the tempfile path" do - tempfile, strategy = @download.download! - assert_operator(tempfile.size, :>, 0, "should have data") - end - - should "correctly save the file when following 302 redirects" do - download = Downloads::File.new("https://yande.re/post/show/578014") - file, strategy = download.download!(url: download.preview_url) - assert_equal(19134, file.size) - end - end - end -end diff --git a/test/unit/downloads/pixiv_test.rb b/test/unit/downloads/pixiv_test.rb index a22f8a700..ddc32e14d 100644 --- a/test/unit/downloads/pixiv_test.rb +++ b/test/unit/downloads/pixiv_test.rb @@ -136,18 +136,14 @@ module Downloads end context "An ugoira site for pixiv" do - setup do - @download = Downloads::File.new("http://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364") - @tempfile, strategy = @download.download! - @tempfile.close! - end - should "capture the data" do - assert_equal(2, @download.data[:ugoira_frame_data].size) - if @download.data[:ugoira_frame_data][0]["file"] + @strategy = Sources::Strategies.find("http://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364") + + assert_equal(2, @strategy.data[:ugoira_frame_data].size) + if @strategy.data[:ugoira_frame_data][0]["file"] assert_equal([{"file" => "000000.jpg", "delay" => 125}, {"file" => "000001.jpg", "delay" => 125}], @download.data[:ugoira_frame_data]) else - assert_equal([{"delay_msec" => 125}, {"delay_msec" => 125}], @download.data[:ugoira_frame_data]) + assert_equal([{"delay_msec" => 125}, {"delay_msec" => 125}], @strategy.data[:ugoira_frame_data]) end end end diff --git a/test/unit/upload_service_test.rb b/test/unit/upload_service_test.rb index b6b7bbf15..b15a399a5 100644 --- a/test/unit/upload_service_test.rb +++ b/test/unit/upload_service_test.rb @@ -212,7 +212,7 @@ class UploadServiceTest < ActiveSupport::TestCase context "on timeout errors" do setup do @source = "https://cdn.donmai.us/original/93/f4/93f4dd66ef1eb11a89e56d31f9adc8d0.jpg" - HTTParty.stubs(:get).raises(Net::ReadTimeout) + Danbooru::Http.any_instance.stubs(:get).raises(HTTP::TimeoutError) end should "leave the upload in an error state" do From f730951e7f454ee22720665d1d8ab5e493b30ad2 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Mon, 15 Jun 2020 13:26:20 -0500 Subject: [PATCH 050/173] gems: drop webmock. --- Gemfile | 1 - Gemfile.lock | 8 -------- test/test_helper.rb | 1 - test/test_helpers/reportbooru_helper.rb | 4 ++-- test/unit/cloudflare_service_test.rb | 8 ++++---- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/Gemfile b/Gemfile index 4304f26cd..70656ec6b 100644 --- a/Gemfile +++ b/Gemfile @@ -84,7 +84,6 @@ group :test do gem "mocha", require: "mocha/minitest" gem "ffaker" gem "simplecov", "~> 0.17.0", require: false - gem "webmock", require: "webmock/minitest" gem "minitest-ci" gem "minitest-reporters", require: "minitest/reporters" gem "mock_redis" diff --git a/Gemfile.lock b/Gemfile.lock index 7933ca1f5..222928350 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -123,8 +123,6 @@ GEM coderay (1.1.3) concurrent-ruby (1.1.6) connection_pool (2.2.3) - crack (0.4.3) - safe_yaml (~> 1.0.0) crass (1.0.6) daemons (1.3.1) delayed_job (4.1.8) @@ -156,7 +154,6 @@ GEM ffi (~> 1.0) globalid (0.4.2) activesupport (>= 4.2.0) - hashdiff (1.0.1) http (4.4.1) addressable (~> 2.3) http-cookie (~> 1.0) @@ -402,10 +399,6 @@ GEM unicorn-worker-killer (0.4.4) get_process_mem (~> 0) unicorn (>= 4, < 6) - webmock (3.8.3) - addressable (>= 2.3.6) - crack (>= 0.3.2) - hashdiff (>= 0.4.0, < 2.0.0) webpacker (5.1.1) activesupport (>= 5.2) rack-proxy (>= 0.6.1) @@ -497,7 +490,6 @@ DEPENDENCIES stripe unicorn unicorn-worker-killer - webmock webpacker (>= 4.0.x) whenever diff --git a/test/test_helper.rb b/test/test_helper.rb index 7832ec36c..1d81bdb12 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -43,7 +43,6 @@ class ActiveSupport::TestCase setup do Socket.stubs(:gethostname).returns("www.example.com") - WebMock.allow_net_connect! @temp_dir = Dir.mktmpdir("danbooru-temp-") storage_manager = StorageManager::Local.new(base_dir: @temp_dir) diff --git a/test/test_helpers/reportbooru_helper.rb b/test/test_helpers/reportbooru_helper.rb index 6a99dc5d4..3ec926202 100644 --- a/test/test_helpers/reportbooru_helper.rb +++ b/test/test_helpers/reportbooru_helper.rb @@ -1,7 +1,7 @@ module ReportbooruHelper - def mock_request(url, method: :get, status: 200, body: nil, http: Danbooru::Http.any_instance) + def mock_request(url, method: :get, status: 200, body: nil, http: Danbooru::Http.any_instance, **options) response = HTTP::Response.new(status: status, body: body, version: "1.1") - http.stubs(method).with(url).returns(response) + http.stubs(method).with(url, **options).returns(response) end def mock_post_search_rankings(date = Date.today, rankings) diff --git a/test/unit/cloudflare_service_test.rb b/test/unit/cloudflare_service_test.rb index 146068a6d..cb9bc86be 100644 --- a/test/unit/cloudflare_service_test.rb +++ b/test/unit/cloudflare_service_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'webmock/minitest' class CloudflareServiceTest < ActiveSupport::TestCase def setup @@ -8,10 +7,11 @@ class CloudflareServiceTest < ActiveSupport::TestCase context "#purge_cache" do should "make calls to cloudflare's api" do - stub_request(:any, "api.cloudflare.com") - @cloudflare.purge_cache(["http://localhost/file.txt"]) + url = "http://www.example.com/file.jpg" + mock_request("https://api.cloudflare.com/client/v4/zones/123/purge_cache", method: :delete, json: { files: [url] }) - assert_requested(:delete, "https://api.cloudflare.com/client/v4/zones/123/purge_cache", times: 1) + response = @cloudflare.purge_cache([url]) + assert_equal(200, response.status) end end end From 11d0539ea17d6b63a36f2164a3f5075adf6daf10 Mon Sep 17 00:00:00 2001 From: BrokenEagle <brokeneagle98@yahoo.com> Date: Sat, 20 Jun 2020 05:29:16 +0000 Subject: [PATCH 051/173] Prevent creators from voting on their own BURs --- app/policies/forum_post_policy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/policies/forum_post_policy.rb b/app/policies/forum_post_policy.rb index 14e315e9c..546991f09 100644 --- a/app/policies/forum_post_policy.rb +++ b/app/policies/forum_post_policy.rb @@ -24,7 +24,7 @@ class ForumPostPolicy < ApplicationPolicy end def votable? - unbanned? && show? && record.bulk_update_request.present? && record.bulk_update_request.is_pending? + unbanned? && show? && record.bulk_update_request.present? && record.bulk_update_request.is_pending? && record.bulk_update_request.user_id != user.id end def reportable? From 33577e2186944cc9d58362ca546bef2a4816bf10 Mon Sep 17 00:00:00 2001 From: BrokenEagle <brokeneagle98@yahoo.com> Date: Sat, 20 Jun 2020 08:53:19 +0000 Subject: [PATCH 052/173] Don't display artist wiki when it is deleted, and vice-versa --- app/views/artists/_show.html.erb | 2 +- app/views/wiki_pages/show.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/artists/_show.html.erb b/app/views/artists/_show.html.erb index 3e7a65bec..5352fc333 100644 --- a/app/views/artists/_show.html.erb +++ b/app/views/artists/_show.html.erb @@ -8,7 +8,7 @@ <% if @artist.is_banned? && !policy(@artist).can_view_banned? %> <p>The artist requested removal of this page.</p> <% else %> - <% if @artist.wiki_page.present? %> + <% if @artist.wiki_page.present? && !@artist.wiki_page.is_deleted %> <div class="prose"> <%= format_text(@artist.wiki_page.body, :disable_mentions => true) %> </div> diff --git a/app/views/wiki_pages/show.html.erb b/app/views/wiki_pages/show.html.erb index de2863565..9596e045b 100644 --- a/app/views/wiki_pages/show.html.erb +++ b/app/views/wiki_pages/show.html.erb @@ -28,7 +28,7 @@ <%= format_text(@wiki_page.body) %> <% end %> - <% if @wiki_page.artist %> + <% if @wiki_page.artist.present? && !@wiki_page.artist.is_deleted %> <p><%= link_to "View artist", @wiki_page.artist %></p> <% end %> From 79a59e52ece855b2d636894376d537a8a744340b Mon Sep 17 00:00:00 2001 From: nonamethanks <hellafrickingepic@gmail.com> Date: Sat, 20 Jun 2020 15:08:49 +0200 Subject: [PATCH 053/173] Twitter: don't get api without a status id --- app/logical/sources/strategies/twitter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logical/sources/strategies/twitter.rb b/app/logical/sources/strategies/twitter.rb index a51e162ea..503a96df0 100644 --- a/app/logical/sources/strategies/twitter.rb +++ b/app/logical/sources/strategies/twitter.rb @@ -200,7 +200,7 @@ module Sources::Strategies end def api_response - return {} unless self.class.enabled? + return {} unless self.class.enabled? && status_id.present? api_client.status(status_id) end From a929f3134e97c502cc09b37696255d60ddec8f97 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 20 Jun 2020 16:45:50 -0500 Subject: [PATCH 054/173] danbooru::http: parse html responses. --- app/logical/danbooru/http.rb | 3 +++ app/logical/danbooru/http/html_adapter.rb | 12 ++++++++++++ app/logical/danbooru/http/xml_adapter.rb | 12 ++++++++++++ test/unit/danbooru_http_test.rb | 13 +++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 app/logical/danbooru/http/html_adapter.rb create mode 100644 app/logical/danbooru/http/xml_adapter.rb diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index b87f5e60c..d6bad229c 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -1,3 +1,6 @@ +require "danbooru/http/html_adapter" +require "danbooru/http/xml_adapter" + module Danbooru class Http class DownloadError < StandardError; end diff --git a/app/logical/danbooru/http/html_adapter.rb b/app/logical/danbooru/http/html_adapter.rb new file mode 100644 index 000000000..733208e17 --- /dev/null +++ b/app/logical/danbooru/http/html_adapter.rb @@ -0,0 +1,12 @@ +module Danbooru + class Http + class HtmlAdapter < HTTP::MimeType::Adapter + HTTP::MimeType.register_adapter "text/html", self + HTTP::MimeType.register_alias "text/html", :html + + def decode(str) + Nokogiri::HTML5(str) + end + end + end +end diff --git a/app/logical/danbooru/http/xml_adapter.rb b/app/logical/danbooru/http/xml_adapter.rb new file mode 100644 index 000000000..70c901c07 --- /dev/null +++ b/app/logical/danbooru/http/xml_adapter.rb @@ -0,0 +1,12 @@ +module Danbooru + class Http + class XmlAdapter < HTTP::MimeType::Adapter + HTTP::MimeType.register_adapter "application/xml", self + HTTP::MimeType.register_alias "application/xml", :xml + + def decode(str) + Hash.from_xml(str).with_indifferent_access + end + end + end +end diff --git a/test/unit/danbooru_http_test.rb b/test/unit/danbooru_http_test.rb index f7529491b..50fc67d32 100644 --- a/test/unit/danbooru_http_test.rb +++ b/test/unit/danbooru_http_test.rb @@ -38,6 +38,19 @@ class DanbooruHttpTest < ActiveSupport::TestCase assert_equal(true, response.parse["gzipped"]) end + should "automatically parse html responses" do + response = Danbooru::Http.get("https://httpbin.org/html") + assert_equal(200, response.status) + assert_instance_of(Nokogiri::HTML5::Document, response.parse) + assert_equal("Herman Melville - Moby-Dick", response.parse.css("h1").text) + end + + should "automatically parse xml responses" do + response = Danbooru::Http.get("https://httpbin.org/xml") + assert_equal(200, response.status) + assert_equal(true, response.parse[:slideshow].present?) + end + should "cache requests" do response1 = Danbooru::Http.cache(1.minute).get("https://httpbin.org/uuid") assert_equal(200, response1.status) From 87ed882234ea4639d07614eee202f432631af715 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 20 Jun 2020 22:38:58 -0500 Subject: [PATCH 055/173] danbooru::http: support automatically retrying 429 errors. --- app/logical/danbooru/http.rb | 14 ++--- .../danbooru/http/application_client.rb | 31 +++++++++++ app/logical/danbooru/http/retriable.rb | 52 +++++++++++++++++++ test/unit/danbooru_http_test.rb | 29 +++++++++++ 4 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 app/logical/danbooru/http/application_client.rb create mode 100644 app/logical/danbooru/http/retriable.rb diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index d6bad229c..c57f85f0b 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -1,5 +1,6 @@ require "danbooru/http/html_adapter" require "danbooru/http/xml_adapter" +require "danbooru/http/retriable" module Danbooru class Http @@ -139,12 +140,13 @@ module Danbooru end def http - @http ||= ::HTTP. - follow(strict: false, max_hops: MAX_REDIRECTS). - timeout(DEFAULT_TIMEOUT). - use(:auto_inflate). - headers(Danbooru.config.http_headers). - headers("Accept-Encoding" => "gzip") + @http ||= + ::Danbooru::Http::ApplicationClient.new + .follow(strict: false, max_hops: MAX_REDIRECTS) + .timeout(DEFAULT_TIMEOUT) + .use(:auto_inflate) + .headers(Danbooru.config.http_headers) + .headers("Accept-Encoding" => "gzip") end end end diff --git a/app/logical/danbooru/http/application_client.rb b/app/logical/danbooru/http/application_client.rb new file mode 100644 index 000000000..27ac6a6d1 --- /dev/null +++ b/app/logical/danbooru/http/application_client.rb @@ -0,0 +1,31 @@ +# An extension to HTTP::Client that lets us write Rack-style middlewares that +# hook into the request/response cycle and override how requests are made. This +# works by extending http.rb's concept of features (HTTP::Feature) to give them +# a `perform` method that takes a http request and returns a http response. +# This can be used to intercept and modify requests and return arbitrary responses. + +module Danbooru + class Http + class ApplicationClient < HTTP::Client + # Override `perform` to call the `perform` method on features first. + def perform(request, options) + features = options.features.values.reverse.select do |feature| + feature.respond_to?(:perform) + end + + perform = proc { |req| super(req, options) } + callback_chain = features.reduce(perform) do |callback_chain, feature| + proc { |req| feature.perform(req, &callback_chain) } + end + + callback_chain.call(request) + end + + # Override `branch` to return an ApplicationClient instead of a + # HTTP::Client so that chaining works. + def branch(...) + ApplicationClient.new(...) + end + end + end +end diff --git a/app/logical/danbooru/http/retriable.rb b/app/logical/danbooru/http/retriable.rb new file mode 100644 index 000000000..681dd8bbe --- /dev/null +++ b/app/logical/danbooru/http/retriable.rb @@ -0,0 +1,52 @@ +# A HTTP::Feature that automatically retries requests that return a 429 error +# or a Retry-After header. Usage: `Danbooru::Http.use(:retriable).get(url)`. +# +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + +module Danbooru + class Http + class Retriable < HTTP::Feature + HTTP::Options.register_feature :retriable, self + + attr_reader :max_retries, :max_delay + + def initialize(max_retries: 2, max_delay: 5.seconds) + @max_retries = max_retries + @max_delay = max_delay + end + + def perform(request, &block) + response = yield request + + retries = max_retries + while retriable?(response) && retries > 0 && retry_delay(response) <= max_delay + retries -= 1 + sleep(retry_delay(response)) + response = yield request + end + + response + end + + def retriable?(response) + response.status == 429 || response.headers["Retry-After"].present? + end + + def retry_delay(response, current_time: Time.zone.now) + retry_after = response.headers["Retry-After"] + + if retry_after.blank? + 0.seconds + elsif retry_after =~ /\A\d+\z/ + retry_after.to_i.seconds + else + retry_at = Time.zone.parse(retry_after) + return 0.seconds if retry_at.blank? + + [retry_at - current_time, 0].max.seconds + end + end + end + end +end diff --git a/test/unit/danbooru_http_test.rb b/test/unit/danbooru_http_test.rb index 50fc67d32..ae347bfd8 100644 --- a/test/unit/danbooru_http_test.rb +++ b/test/unit/danbooru_http_test.rb @@ -61,6 +61,35 @@ class DanbooruHttpTest < ActiveSupport::TestCase end end + context "retriable feature" do + should "retry immediately if no Retry-After header is sent" do + response_429 = ::HTTP::Response.new(status: 429, version: "1.1", body: "") + response_200 = ::HTTP::Response.new(status: 200, version: "1.1", body: "") + HTTP::Client.any_instance.expects(:perform).times(2).returns(response_429, response_200) + + response = Danbooru::Http.use(:retriable).get("https://httpbin.org/status/429") + assert_equal(200, response.status) + end + + should "retry if the Retry-After header is an integer" do + response_503 = ::HTTP::Response.new(status: 503, version: "1.1", headers: { "Retry-After": "1" }, body: "") + response_200 = ::HTTP::Response.new(status: 200, version: "1.1", body: "") + HTTP::Client.any_instance.expects(:perform).times(2).returns(response_503, response_200) + + response = Danbooru::Http.use(:retriable).get("https://httpbin.org/status/503") + assert_equal(200, response.status) + end + + should "retry if the Retry-After header is a date" do + response_503 = ::HTTP::Response.new(status: 503, version: "1.1", headers: { "Retry-After": 2.seconds.from_now.httpdate }, body: "") + response_200 = ::HTTP::Response.new(status: 200, version: "1.1", body: "") + HTTP::Client.any_instance.expects(:perform).times(2).returns(response_503, response_200) + + response = Danbooru::Http.use(:retriable).get("https://httpbin.org/status/503") + assert_equal(200, response.status) + end + end + context "#download method" do should "download files" do response, file = Danbooru::Http.download_media("https://httpbin.org/bytes/1000") From 71b0bc6c0f03b714bbd4cc9ae73a94a878431971 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 20 Jun 2020 16:48:00 -0500 Subject: [PATCH 056/173] danbooru::http: support tracking cookies between requests. Allow cookies to be saved and sent back when making several requests in a row. Usage: http = Danbooru::Http.use(:session) # saves the foo=42 cookie sent by the response. http.get("https://httpbin.org/cookies/set/foo/42") # sends back the foo=42 cookie from the previous request. http.get("https://httpbin.org/cookies") --- app/logical/danbooru/http.rb | 12 ++++++++- app/logical/danbooru/http/session.rb | 37 ++++++++++++++++++++++++++++ test/unit/danbooru_http_test.rb | 12 +++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 app/logical/danbooru/http/session.rb diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index c57f85f0b..8fe06a0d1 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -1,6 +1,7 @@ require "danbooru/http/html_adapter" require "danbooru/http/xml_adapter" require "danbooru/http/retriable" +require "danbooru/http/session" module Danbooru class Http @@ -13,7 +14,7 @@ module Danbooru attr_writer :cache, :max_size, :http class << self - delegate :get, :head, :put, :post, :delete, :cache, :follow, :max_size, :timeout, :auth, :basic_auth, :headers, :public_only, :download_media, to: :new + delegate :get, :head, :put, :post, :delete, :cache, :follow, :max_size, :timeout, :auth, :basic_auth, :headers, :cookies, :use, :public_only, :download_media, to: :new end def get(url, **options) @@ -64,6 +65,14 @@ module Danbooru dup.tap { |o| o.http = o.http.headers(*args) } end + def cookies(*args) + dup.tap { |o| o.http = o.http.cookies(*args) } + end + + def use(*args) + dup.tap { |o| o.http = o.http.use(*args) } + end + # allow requests only to public IPs, not to local or private networks. def public_only dup.tap do |o| @@ -143,6 +152,7 @@ module Danbooru @http ||= ::Danbooru::Http::ApplicationClient.new .follow(strict: false, max_hops: MAX_REDIRECTS) + .use(:session) .timeout(DEFAULT_TIMEOUT) .use(:auto_inflate) .headers(Danbooru.config.http_headers) diff --git a/app/logical/danbooru/http/session.rb b/app/logical/danbooru/http/session.rb new file mode 100644 index 000000000..3bb532928 --- /dev/null +++ b/app/logical/danbooru/http/session.rb @@ -0,0 +1,37 @@ +module Danbooru + class Http + class Session < HTTP::Feature + HTTP::Options.register_feature :session, self + + attr_reader :cookie_jar + + def initialize(cookie_jar: HTTP::CookieJar.new) + @cookie_jar = cookie_jar + end + + def perform(request) + add_cookies(request) + response = yield request + save_cookies(response) + response + end + + def add_cookies(request) + cookies = cookies_for_request(request) + request.headers["Cookie"] = cookies if cookies.present? + end + + def cookies_for_request(request) + saved_cookies = cookie_jar.each(request.uri).map { |c| [c.name, c.value] }.to_h + request_cookies = HTTP::Cookie.cookie_value_to_hash(request.headers["Cookie"].to_s) + saved_cookies.merge(request_cookies).map { |name, value| "#{name}=#{value}" }.join("; ") + end + + def save_cookies(response) + response.cookies.each do |cookie| + cookie_jar.add(cookie) + end + end + end + end +end diff --git a/test/unit/danbooru_http_test.rb b/test/unit/danbooru_http_test.rb index ae347bfd8..b6ef81e7c 100644 --- a/test/unit/danbooru_http_test.rb +++ b/test/unit/danbooru_http_test.rb @@ -51,6 +51,18 @@ class DanbooruHttpTest < ActiveSupport::TestCase assert_equal(true, response.parse[:slideshow].present?) end + should "track cookies between requests" do + http = Danbooru::Http.use(:session) + + resp1 = http.get("https://httpbin.org/cookies/set/abc/1") + resp2 = http.get("https://httpbin.org/cookies/set/def/2") + resp3 = http.get("https://httpbin.org/cookies") + assert_equal({ abc: "1", def: "2" }, resp3.parse["cookies"].symbolize_keys) + + resp4 = http.cookies(def: 3, ghi: 4).get("https://httpbin.org/cookies") + assert_equal({ abc: "1", def: "3", ghi: "4" }, resp4.parse["cookies"].symbolize_keys) + end + should "cache requests" do response1 = Danbooru::Http.cache(1.minute).get("https://httpbin.org/uuid") assert_equal(200, response1.status) From 05d7355ebbfa7ef8139578c8359827abea832f2e Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 03:57:14 -0500 Subject: [PATCH 057/173] danbooru::http: support automatically following redirects. Replace http.rb's builtin redirect following option with our own redirect follower. This fixes an issue with http.rb losing cookies after following a redirect. --- app/logical/danbooru/http.rb | 26 ++++++++-------- app/logical/danbooru/http/redirector.rb | 40 +++++++++++++++++++++++++ app/logical/nico_seiga_api_client.rb | 5 ++-- 3 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 app/logical/danbooru/http/redirector.rb diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index 8fe06a0d1..dfe6558f8 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -1,5 +1,6 @@ require "danbooru/http/html_adapter" require "danbooru/http/xml_adapter" +require "danbooru/http/redirector" require "danbooru/http/retriable" require "danbooru/http/session" @@ -11,12 +12,24 @@ module Danbooru DEFAULT_TIMEOUT = 10 MAX_REDIRECTS = 5 - attr_writer :cache, :max_size, :http + attr_accessor :cache, :max_size, :http class << self delegate :get, :head, :put, :post, :delete, :cache, :follow, :max_size, :timeout, :auth, :basic_auth, :headers, :cookies, :use, :public_only, :download_media, to: :new end + def initialize + @http ||= + ::Danbooru::Http::ApplicationClient.new + .timeout(DEFAULT_TIMEOUT) + .headers(Danbooru.config.http_headers) + .headers("Accept-Encoding" => "gzip") + .use(:auto_inflate) + .use(:retriable) + .use(redirector: { max_redirects: MAX_REDIRECTS }) + .use(:session) + end + def get(url, **options) request(:get, url, **options) end @@ -147,16 +160,5 @@ module Danbooru def fake_response(status, body) ::HTTP::Response.new(status: status, version: "1.1", body: ::HTTP::Response::Body.new(body)) end - - def http - @http ||= - ::Danbooru::Http::ApplicationClient.new - .follow(strict: false, max_hops: MAX_REDIRECTS) - .use(:session) - .timeout(DEFAULT_TIMEOUT) - .use(:auto_inflate) - .headers(Danbooru.config.http_headers) - .headers("Accept-Encoding" => "gzip") - end end end diff --git a/app/logical/danbooru/http/redirector.rb b/app/logical/danbooru/http/redirector.rb new file mode 100644 index 000000000..26331226e --- /dev/null +++ b/app/logical/danbooru/http/redirector.rb @@ -0,0 +1,40 @@ +# A HTTP::Feature that automatically follows HTTP redirects. +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections + +module Danbooru + class Http + class Redirector < HTTP::Feature + HTTP::Options.register_feature :redirector, self + + attr_reader :max_redirects + + def initialize(max_redirects: 5) + @max_redirects = max_redirects + end + + def perform(request, &block) + response = yield request + + redirects = max_redirects + while response.status.redirect? + raise HTTP::Redirector::TooManyRedirectsError if redirects <= 0 + + response = yield build_redirect(request, response) + redirects -= 1 + end + + response + end + + def build_redirect(request, response) + location = response.headers["Location"].to_s + uri = HTTP::URI.parse(location) + + verb = request.verb + verb = :get if response.status == 303 && !request.verb.in?([:get, :head]) + + request.redirect(uri, verb) + end + end + end +end diff --git a/app/logical/nico_seiga_api_client.rb b/app/logical/nico_seiga_api_client.rb index f5418ac76..2b67bbde0 100644 --- a/app/logical/nico_seiga_api_client.rb +++ b/app/logical/nico_seiga_api_client.rb @@ -5,10 +5,11 @@ class NicoSeigaApiClient attr_reader :http # XXX temp disable following redirects. - def initialize(work_id:, type:, http: Danbooru::Http.follow(nil)) + def initialize(work_id:, type:) @work_id = work_id @work_type = type - @http = http + @http = Danbooru::Http.new + @http.http.default_options.features.reject! { |name, _| name == :redirector } end def image_ids From 6e6ce6e62f79f0c97251d4e1012009f8c138e021 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 20 Jun 2020 23:08:38 -0500 Subject: [PATCH 058/173] nijie: replace Mechanize with Danbooru::Http. The Nijie login process works like this: * First we submit our `email` and `password` to `https://nijie.info/login_int.php`. * Then we save the NIJIEIEID session cookie from the response. * We optionally retry if login failed. Nijie returns 429 errors with a `Retry-After: 5` header if we send too many login requests. This can happen during parallel testing. * We cache the login cookies for only 1 hour so we don't have to worry about them becoming invalid if we cache them too long. Cookies and retrying errors on failure are handled transparently by Danbooru::Http. --- app/logical/sources/strategies/nijie.rb | 52 ++++--------------------- test/unit/sources/nijie_test.rb | 2 - 2 files changed, 8 insertions(+), 46 deletions(-) diff --git a/app/logical/sources/strategies/nijie.rb b/app/logical/sources/strategies/nijie.rb index b24605168..f3a15b576 100644 --- a/app/logical/sources/strategies/nijie.rb +++ b/app/logical/sources/strategies/nijie.rb @@ -178,54 +178,18 @@ module Sources def page return nil if page_url.blank? - doc = agent.get(page_url) + http = Danbooru::Http.new + form = { email: Danbooru.config.nijie_login, password: Danbooru.config.nijie_password } + response = http.cache(1.hour).post("https://nijie.info/login_int.php", form: form) + return nil unless response.status == 200 - if doc.search("div#header-login-container").any? - # Session cache is invalid, clear it and log in normally. - Cache.delete("nijie-session") - doc = agent.get(page_url) - end + response = http.cookies(R18: 1).cache(1.minute).get(page_url) + return nil unless response.status == 200 - doc - rescue Mechanize::ResponseCodeError => e - return nil if e.response_code.to_i == 404 - raise + response&.parse end + memoize :page - - def agent - mech = Mechanize.new - - session = Cache.get("nijie-session") - if session - cookie = Mechanize::Cookie.new("NIJIEIJIEID", session) - cookie.domain = ".nijie.info" - cookie.path = "/" - mech.cookie_jar.add(cookie) - else - mech.get("https://nijie.info/login.php") do |page| - page.form_with(:action => "/login_int.php") do |form| - form['email'] = Danbooru.config.nijie_login - form['password'] = Danbooru.config.nijie_password - end.click_button - end - session = mech.cookie_jar.cookies.select {|c| c.name == "NIJIEIJIEID"}.first - Cache.put("nijie-session", session.value, 1.day) if session - end - - # This cookie needs to be set to allow viewing of adult works while anonymous - cookie = Mechanize::Cookie.new("R18", "1") - cookie.domain = ".nijie.info" - cookie.path = "/" - mech.cookie_jar.add(cookie) - - mech - rescue Mechanize::ResponseCodeError => e - raise unless e.response_code.to_i == 429 - sleep(5) - retry - end - memoize :agent end end end diff --git a/test/unit/sources/nijie_test.rb b/test/unit/sources/nijie_test.rb index 796bcfd46..7ca695c90 100644 --- a/test/unit/sources/nijie_test.rb +++ b/test/unit/sources/nijie_test.rb @@ -187,8 +187,6 @@ module Sources desc = <<-EOS.strip_heredoc.chomp foo [b]bold[/b] [i]italics[/i] [s]strike[/s] red - - <http://nijie.info/view.php?id=218944> EOS From 2da8174ce239510531a8d3ce8c66b9a4b72f8035 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 05:12:09 -0500 Subject: [PATCH 059/173] hentai foundry: replace HTTParty with Danbooru::Http. --- app/logical/sources/strategies/hentai_foundry.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/logical/sources/strategies/hentai_foundry.rb b/app/logical/sources/strategies/hentai_foundry.rb index 74f570288..041fb8df2 100644 --- a/app/logical/sources/strategies/hentai_foundry.rb +++ b/app/logical/sources/strategies/hentai_foundry.rb @@ -64,11 +64,10 @@ module Sources def page return nil if page_url.blank? - doc = Cache.get("hentai-foundry:#{page_url}", 1.minute) do - HTTParty.get("#{page_url}?enterAgree=1").body - end + response = Danbooru::Http.new.cache(1.minute).get("#{page_url}?enterAgree=1") + return nil unless response.status == 200 - Nokogiri::HTML(doc) + response.parse end def tags From 5604ab0079c3d9b0dcfae339bcf2050aaa21cb20 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 11:56:37 -0500 Subject: [PATCH 060/173] pixiv: remove fanbox support. This is broken and it needs to be rewritten as a separate source strategy anyway. --- app/logical/pixiv_api_client.rb | 62 --------------------- app/logical/pixiv_web_agent.rb | 74 ------------------------- app/logical/sources/strategies/pixiv.rb | 64 +-------------------- test/unit/downloads/pixiv_test.rb | 11 ---- test/unit/sources/pixiv_test.rb | 11 ---- 5 files changed, 2 insertions(+), 220 deletions(-) delete mode 100644 app/logical/pixiv_web_agent.rb diff --git a/app/logical/pixiv_api_client.rb b/app/logical/pixiv_api_client.rb index c742e250e..a28010f21 100644 --- a/app/logical/pixiv_api_client.rb +++ b/app/logical/pixiv_api_client.rb @@ -99,50 +99,6 @@ class PixivApiClient end end - class FanboxResponse - attr_reader :json - - def initialize(json) - @json = json - end - - def name - json["body"]["user"]["name"] - end - - def user_id - json["body"]["user"]["userId"] - end - - def moniker - "" - end - - def page_count - json["body"]["body"]["images"].size - end - - def artist_commentary_title - json["body"]["title"] - end - - def artist_commentary_desc - json["body"]["body"]["text"] - end - - def tags - [] - end - - def pages - if json["body"]["body"] - json["body"]["body"]["images"].map {|x| x["originalUrl"]} - else - [] - end - end - end - def work(illust_id) headers = Danbooru.config.http_headers.merge( "Referer" => "http://www.pixiv.net", @@ -169,19 +125,6 @@ class PixivApiClient raise Error.new("Pixiv API call failed (status=#{response.code} body=#{response.body})") end - def fanbox(fanbox_id) - url = "https://www.pixiv.net/ajax/fanbox/post?postId=#{fanbox_id.to_i}" - resp = agent.get(url) - json = JSON.parse(resp.body) - if resp.code == "200" - FanboxResponse.new(json) - elsif json["status"] == "failure" - raise Error.new("Pixiv API call failed (status=#{resp.code} body=#{body})") - end - rescue JSON::ParserError - raise Error.new("Pixiv API call failed (status=#{resp.code} body=#{body})") - end - def novel(novel_id) headers = Danbooru.config.http_headers.merge( "Referer" => "http://www.pixiv.net", @@ -237,9 +180,4 @@ class PixivApiClient access_token end end - - def agent - PixivWebAgent.build - end - memoize :agent end diff --git a/app/logical/pixiv_web_agent.rb b/app/logical/pixiv_web_agent.rb deleted file mode 100644 index 1a181672b..000000000 --- a/app/logical/pixiv_web_agent.rb +++ /dev/null @@ -1,74 +0,0 @@ -class PixivWebAgent - SESSION_CACHE_KEY = "pixiv-phpsessid" - COMIC_SESSION_CACHE_KEY = "pixiv-comicsessid" - SESSION_COOKIE_KEY = "PHPSESSID" - COMIC_SESSION_COOKIE_KEY = "_pixiv-comic_session" - - def self.phpsessid(agent) - agent.cookies.select { |cookie| cookie.name == SESSION_COOKIE_KEY }.first.try(:value) - end - - def self.build - mech = Mechanize.new - mech.keep_alive = false - - phpsessid = Cache.get(SESSION_CACHE_KEY) - comicsessid = Cache.get(COMIC_SESSION_CACHE_KEY) - - if phpsessid - cookie = Mechanize::Cookie.new(SESSION_COOKIE_KEY, phpsessid) - cookie.domain = ".pixiv.net" - cookie.path = "/" - mech.cookie_jar.add(cookie) - - if comicsessid - cookie = Mechanize::Cookie.new(COMIC_SESSION_COOKIE_KEY, comicsessid) - cookie.domain = ".pixiv.net" - cookie.path = "/" - mech.cookie_jar.add(cookie) - end - else - headers = { - "Origin" => "https://accounts.pixiv.net", - "Referer" => "https://accounts.pixiv.net/login?lang=en^source=pc&view_type=page&ref=wwwtop_accounts_index" - } - - params = { - pixiv_id: Danbooru.config.pixiv_login, - password: Danbooru.config.pixiv_password, - captcha: nil, - g_captcha_response: nil, - source: "pc", - post_key: nil - } - - mech.get("https://accounts.pixiv.net/login?lang=en&source=pc&view_type=page&ref=wwwtop_accounts_index") do |page| - json = page.search("input#init-config").first.attr("value") - if json =~ /pixivAccount\.postKey":"([a-f0-9]+)/ - params[:post_key] = $1 - end - end - - mech.post("https://accounts.pixiv.net/api/login?lang=en", params, headers) - if mech.current_page.body =~ /"error":false/ - cookie = mech.cookies.select {|x| x.name == SESSION_COOKIE_KEY}.first - if cookie - Cache.put(SESSION_CACHE_KEY, cookie.value, 1.week) - end - end - - begin - mech.get("https://comic.pixiv.net") do - cookie = mech.cookies.select {|x| x.name == COMIC_SESSION_COOKIE_KEY}.first - if cookie - Cache.put(COMIC_SESSION_CACHE_KEY, cookie.value, 1.week) - end - end - rescue Net::HTTPServiceUnavailable - # ignore - end - end - - mech - end -end diff --git a/app/logical/sources/strategies/pixiv.rb b/app/logical/sources/strategies/pixiv.rb index ad7cd7eb1..53475f150 100644 --- a/app/logical/sources/strategies/pixiv.rb +++ b/app/logical/sources/strategies/pixiv.rb @@ -64,9 +64,6 @@ module Sources ORIG_IMAGE = %r{#{PXIMG}/img-original/img/#{DATE}/(?<illust_id>\d+)_p(?<page>\d+)\.#{EXT}\z}i STACC_PAGE = %r{\A#{WEB}/stacc/#{MONIKER}/?\z}i NOVEL_PAGE = %r{(?:\Ahttps?://www\.pixiv\.net/novel/show\.php\?id=(\d+))} - FANBOX_ACCOUNT = %r{(?:\Ahttps?://www\.pixiv\.net/fanbox/creator/\d+\z)} - FANBOX_IMAGE = %r{(?:\Ahttps?://fanbox\.pixiv\.net/images/post/(\d+))} - FANBOX_PAGE = %r{(?:\Ahttps?://www\.pixiv\.net/fanbox/creator/\d+/post/(\d+))} def self.to_dtext(text) if text.nil? @@ -127,14 +124,6 @@ module Sources return "https://www.pixiv.net/novel/show.php?id=#{novel_id}&mode=cover" end - if fanbox_id.present? - return "https://www.pixiv.net/fanbox/creator/#{metadata.user_id}/post/#{fanbox_id}" - end - - if fanbox_account_id.present? - return "https://www.pixiv.net/fanbox/creator/#{fanbox_account_id}" - end - if illust_id.present? return "https://www.pixiv.net/artworks/#{illust_id}" end @@ -192,17 +181,7 @@ module Sources end def headers - if fanbox_id.present? - # need the session to download fanbox images - return { - "Referer" => "https://www.pixiv.net/fanbox", - "Cookie" => HTTP::Cookie.cookie_value(agent.cookies) - } - end - - { - "Referer" => "https://www.pixiv.net" - } + { "Referer" => "https://www.pixiv.net" } end def normalize_for_source @@ -242,10 +221,6 @@ module Sources end def image_urls_sub - if url =~ FANBOX_IMAGE - return [url] - end - # there's too much normalization bullshit we have to deal with # raw urls, so just fetch the canonical url from the api every # time. @@ -265,7 +240,7 @@ module Sources # even though it makes sense to reference page_url here, it will only look # at (url, referer_url). def illust_id - return nil if novel_id.present? || fanbox_id.present? + return nil if novel_id.present? parsed_urls.each do |url| # http://www.pixiv.net/member_illust.php?mode=medium&illust_id=18557054 @@ -328,46 +303,11 @@ module Sources end memoize :novel_id - def fanbox_id - [url, referer_url].each do |x| - if x =~ FANBOX_PAGE - return $1 - end - - if x =~ FANBOX_IMAGE - return $1 - end - end - - nil - end - memoize :fanbox_id - - def fanbox_account_id - [url, referer_url].each do |x| - if x =~ FANBOX_ACCOUNT - return x - end - end - - nil - end - memoize :fanbox_account_id - - def agent - PixivWebAgent.build - end - memoize :agent - def metadata if novel_id.present? return PixivApiClient.new.novel(novel_id) end - if fanbox_id.present? - return PixivApiClient.new.fanbox(fanbox_id) - end - PixivApiClient.new.work(illust_id) end memoize :metadata diff --git a/test/unit/downloads/pixiv_test.rb b/test/unit/downloads/pixiv_test.rb index ddc32e14d..02d923c02 100644 --- a/test/unit/downloads/pixiv_test.rb +++ b/test/unit/downloads/pixiv_test.rb @@ -122,17 +122,6 @@ module Downloads assert_downloaded(@file_size, @file_url, @ref) end end - - context "downloading a pixiv fanbox image" do - should_eventually "work" do - @source = "https://www.pixiv.net/fanbox/creator/12491073/post/82406" - @file_url = "https://fanbox.pixiv.net/images/post/82406/D833IKA7FIesJXL8xx39rrG0.jpeg" - @file_size = 873_387 - - assert_not_rewritten(@file_url, @source) - assert_downloaded(@file_size, @file_url, @source) - end - end end context "An ugoira site for pixiv" do diff --git a/test/unit/sources/pixiv_test.rb b/test/unit/sources/pixiv_test.rb index cb7a3d883..a268fa640 100644 --- a/test/unit/sources/pixiv_test.rb +++ b/test/unit/sources/pixiv_test.rb @@ -73,17 +73,6 @@ module Sources end end - context "A https://www.pixiv.net/fanbox/creator/*/post/* source" do - should_eventually "work" do - @site = Sources::Strategies.find("http://www.pixiv.net/fanbox/creator/554149/post/82555") - - assert_equal("TYONE(お仕事募集中)", @site.artist_name) - assert_equal("https://www.pixiv.net/member.php?id=554149", @site.profile_url) - assert_equal("https://fanbox.pixiv.net/images/post/82555/Lyyeb6dDLcQZmy09nqLZapuS.jpeg", @site.image_url) - assert_nothing_raised { @site.to_h } - end - end - context "A https://www.pixiv.net/*/artworks/* source" do should "work" do @site = Sources::Strategies.find("https://www.pixiv.net/en/artworks/64476642") From 3ad8c708c5466757ad50abcaaed5e8d502984667 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 12:49:48 -0500 Subject: [PATCH 061/173] pixiv: replace HTTParty with Danbooru::Http. --- app/logical/pixiv_api_client.rb | 91 ++++++++++++++------------------- 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/app/logical/pixiv_api_client.rb b/app/logical/pixiv_api_client.rb index a28010f21..7d3d00156 100644 --- a/app/logical/pixiv_api_client.rb +++ b/app/logical/pixiv_api_client.rb @@ -1,5 +1,3 @@ -require 'resolv-replace' - class PixivApiClient extend Memoist @@ -100,21 +98,12 @@ class PixivApiClient end def work(illust_id) - headers = Danbooru.config.http_headers.merge( - "Referer" => "http://www.pixiv.net", - "Content-Type" => "application/x-www-form-urlencoded", - "Authorization" => "Bearer #{access_token}" - ) - params = { - "image_sizes" => "large", - "include_stats" => "true" - } - + params = { image_sizes: "large", include_stats: "true" } url = "https://public-api.secure.pixiv.net/v#{API_VERSION}/works/#{illust_id.to_i}.json" - response = Danbooru::Http.cache(1.minute).headers(headers).get(url, params: params) + response = api_client.cache(1.minute).get(url, params: params) json = response.parse - if response.code == 200 + if response.status == 200 WorkResponse.new(json["response"][0]) elsif json["status"] == "failure" && json.dig("errors", "system", "message") =~ /対象のイラストは見つかりませんでした。/ raise BadIDError.new("Pixiv ##{illust_id} not found: work was deleted, made private, or ID is invalid.") @@ -126,18 +115,11 @@ class PixivApiClient end def novel(novel_id) - headers = Danbooru.config.http_headers.merge( - "Referer" => "http://www.pixiv.net", - "Content-Type" => "application/x-www-form-urlencoded", - "Authorization" => "Bearer #{access_token}" - ) - url = "https://public-api.secure.pixiv.net/v#{API_VERSION}/novels/#{novel_id.to_i}.json" - resp = HTTParty.get(url, Danbooru.config.httparty_options.deep_merge(headers: headers)) - body = resp.body.force_encoding("utf-8") - json = JSON.parse(body) + resp = api_client.cache(1.minute).get(url) + json = resp.parse - if resp.success? + if resp.status == 200 NovelResponse.new(json["response"][0]) elsif json["status"] == "failure" && json.dig("errors", "system", "message") =~ /対象のイラストは見つかりませんでした。/ raise Error.new("Pixiv API call failed (status=#{resp.code} body=#{body})") @@ -147,37 +129,42 @@ class PixivApiClient end def access_token - Cache.get("pixiv-papi-access-token", 3000) do - access_token = nil + # truncate timestamp to 1-hour resolution so that it doesn't break caching. + client_time = Time.zone.now.utc.change(min: 0).rfc3339 + client_hash = Digest::MD5.hexdigest(client_time + CLIENT_HASH_SALT) - client_time = Time.now.rfc3339 - client_hash = Digest::MD5.hexdigest(client_time + CLIENT_HASH_SALT) + headers = { + "Referer": "http://www.pixiv.net", + "X-Client-Time": client_time, + "X-Client-Hash": client_hash + } - headers = { - "Referer": "http://www.pixiv.net", - "X-Client-Time": client_time, - "X-Client-Hash": client_hash - } - params = { - username: Danbooru.config.pixiv_login, - password: Danbooru.config.pixiv_password, - grant_type: "password", - client_id: CLIENT_ID, - client_secret: CLIENT_SECRET - } - url = "https://oauth.secure.pixiv.net/auth/token" + params = { + username: Danbooru.config.pixiv_login, + password: Danbooru.config.pixiv_password, + grant_type: "password", + client_id: CLIENT_ID, + client_secret: CLIENT_SECRET + } - resp = HTTParty.post(url, Danbooru.config.httparty_options.deep_merge(body: params, headers: headers)) - body = resp.body.force_encoding("utf-8") + resp = http.headers(headers).cache(1.hour).post("https://oauth.secure.pixiv.net/auth/token", form: params) + return nil unless resp.status == 200 - if resp.success? - json = JSON.parse(body) - access_token = json["response"]["access_token"] - else - raise Error.new("Pixiv API access token call failed (status=#{resp.code} body=#{body})") - end - - access_token - end + resp.parse.dig("response", "access_token") end + + def api_client + http.headers( + **Danbooru.config.http_headers, + "Referer": "http://www.pixiv.net", + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": "Bearer #{access_token}" + ) + end + + def http + Danbooru::Http.new + end + + memoize :access_token, :api_client, :http end From 7e471fe223a26f4df727a3bcc1bbcc0fd5a1456c Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 13:10:16 -0500 Subject: [PATCH 062/173] sources: replace HTTParty with Danbooru::Http in `http_exists?`. --- app/logical/sources/strategies/art_station.rb | 2 +- app/logical/sources/strategies/base.rb | 5 ++--- app/logical/sources/strategies/tumblr.rb | 2 +- test/test_helpers/download_test_helper.rb | 15 --------------- test/unit/sources/nijie_test.rb | 8 ++++---- 5 files changed, 8 insertions(+), 24 deletions(-) diff --git a/app/logical/sources/strategies/art_station.rb b/app/logical/sources/strategies/art_station.rb index 6ab7445ca..1c4d32b56 100644 --- a/app/logical/sources/strategies/art_station.rb +++ b/app/logical/sources/strategies/art_station.rb @@ -147,7 +147,7 @@ module Sources::Strategies urls = urls.reverse end - chosen_url = urls.find { |url| http_exists?(url, headers) } + chosen_url = urls.find { |url| http_exists?(url) } chosen_url || url end end diff --git a/app/logical/sources/strategies/base.rb b/app/logical/sources/strategies/base.rb index fcc0ba6e6..e4f3d1a6a 100644 --- a/app/logical/sources/strategies/base.rb +++ b/app/logical/sources/strategies/base.rb @@ -288,9 +288,8 @@ module Sources to_h.to_json end - def http_exists?(url, headers) - res = HTTParty.head(url, Danbooru.config.httparty_options.deep_merge(headers: headers)) - res.success? + def http_exists?(url, headers = {}) + http.headers(headers).head(url).status.success? end # Convert commentary to dtext by stripping html tags. Sites can override diff --git a/app/logical/sources/strategies/tumblr.rb b/app/logical/sources/strategies/tumblr.rb index b82a1f77d..e3440c910 100644 --- a/app/logical/sources/strategies/tumblr.rb +++ b/app/logical/sources/strategies/tumblr.rb @@ -168,7 +168,7 @@ module Sources::Strategies end candidates.find do |candidate| - http_exists?(candidate, headers) + http_exists?(candidate) end end diff --git a/test/test_helpers/download_test_helper.rb b/test/test_helpers/download_test_helper.rb index eea711588..c3f70c75d 100644 --- a/test/test_helpers/download_test_helper.rb +++ b/test/test_helpers/download_test_helper.rb @@ -16,19 +16,4 @@ module DownloadTestHelper def assert_not_rewritten(source, referer = nil) assert_rewritten(source, source, referer) end - - def assert_http_exists(url, headers: {}) - res = HTTParty.head(url, Danbooru.config.httparty_options.deep_merge(headers: headers)) - assert_equal(true, res.success?) - end - - def assert_http_status(code, url, headers: {}) - res = HTTParty.head(url, Danbooru.config.httparty_options.deep_merge(headers: headers)) - assert_equal(code, res.code) - end - - def assert_http_size(size, url, headers: {}) - res = HTTParty.head(url, Danbooru.config.httparty_options.deep_merge(headers: headers)) - assert_equal(size, res.content_length) - end end diff --git a/test/unit/sources/nijie_test.rb b/test/unit/sources/nijie_test.rb index 7ca695c90..c4f7b88c8 100644 --- a/test/unit/sources/nijie_test.rb +++ b/test/unit/sources/nijie_test.rb @@ -43,7 +43,7 @@ module Sources should "get the image url" do assert_equal("https://pic.nijie.net/03/nijie_picture/728995_20170505014820_0.jpg", @site.image_url) - assert_http_size(132_555, @site.image_url) + assert_downloaded(132_555, @site.image_url) end should "get the canonical url" do @@ -53,7 +53,7 @@ module Sources should "get the preview url" do assert_equal("https://pic.nijie.net/03/__rs_l170x170/nijie_picture/728995_20170505014820_0.jpg", @site.preview_url) assert_equal([@site.preview_url], @site.preview_urls) - assert_http_exists(@site.preview_url) + assert_downloaded(132_555, @site.preview_url) end should "get the profile" do @@ -205,8 +205,8 @@ module Sources assert_equal("https://nijie.info/members.php?id=236014", site.profile_url) assert_nothing_raised { site.to_h } - assert_http_size(3619, site.image_url) - assert_http_exists(site.preview_url) + assert_downloaded(3619, site.image_url) + assert_downloaded(3619, site.preview_url) end end From 29a5f7dfc83aa5bced43acb6570f4445b35dd23f Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 14:01:38 -0500 Subject: [PATCH 063/173] image proxy: replace HTTParty with Danbooru::Http. --- app/controllers/uploads_controller.rb | 2 +- app/logical/image_proxy.rb | 15 +++++++-------- test/functional/uploads_controller_test.rb | 11 +++++++++++ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index d99d58125..97f5090dc 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -21,7 +21,7 @@ class UploadsController < ApplicationController def image_proxy authorize Upload resp = ImageProxy.get_image(params[:url]) - send_data resp.body, :type => resp.content_type, :disposition => "inline" + send_data resp.body, type: resp.mime_type, disposition: "inline" end def index diff --git a/app/logical/image_proxy.rb b/app/logical/image_proxy.rb index 80982c7cc..9c2acfe42 100644 --- a/app/logical/image_proxy.rb +++ b/app/logical/image_proxy.rb @@ -1,4 +1,6 @@ class ImageProxy + class Error < StandardError; end + def self.needs_proxy?(url) fake_referer_for(url).present? end @@ -8,16 +10,13 @@ class ImageProxy end def self.get_image(url) - if url.blank? - raise "Must specify url" - end + raise Error, "URL not present" unless url.present? + raise Error, "Proxy not allowed for this url (url=#{url})" unless needs_proxy?(url) - if !needs_proxy?(url) - raise "Proxy not allowed for this site" - end + referer = fake_referer_for(url) + response = Danbooru::Http.headers(Referer: referer).get(url) + raise Error, "Couldn't proxy image (code=#{response.status}, url=#{url})" unless response.status.success? - response = HTTParty.get(url, Danbooru.config.httparty_options.deep_merge(headers: {"Referer" => fake_referer_for(url)})) - raise "HTTP error code: #{response.code} #{response.message}" unless response.success? response end end diff --git a/test/functional/uploads_controller_test.rb b/test/functional/uploads_controller_test.rb index 9df37a1b4..e19949fca 100644 --- a/test/functional/uploads_controller_test.rb +++ b/test/functional/uploads_controller_test.rb @@ -18,6 +18,17 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest mock_iqdb_service! end + context "image proxy action" do + should "work" do + url = "https://i.pximg.net/img-original/img/2017/11/21/17/06/44/65985331_p0.png" + get_auth image_proxy_uploads_path, @user, params: { url: url } + + assert_response :success + assert_equal("image/png", response.media_type) + assert_equal(15_573, response.body.size) + end + end + context "batch action" do context "for twitter galleries" do should "render" do From 5c7843bd3da0add6afd12c0baf98ad160721afac Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 15:02:14 -0500 Subject: [PATCH 064/173] scripts: refactor mocked services. Replace the mocked services in scripts/mocked_services with Rails-level mocked services. The scripts in scripts/mocked_services were a set of stub Sinatra servers used to mock the Reportbooru, Recommender, and IQDBs services during development. They return fake data so you can test pages that use these services. Implementing these services in Rails makes it easier to run them. It also lets us drop a dependency on Sinatra and drop a use of HTTParty. To use these services, set the following configuration in danbooru_local_config.rb or .env.local: * reportbooru_server: http://localhost:3000/mock/reportbooru * recommender_server: http://localhost:3000/mock/recommender * iqdbs_server: http://localhost:3000/mock/iqdb where `http://localhost:300` is the url for your local Danbooru server (may need to be changed depending on your configuration). --- app/controllers/mock_services_controller.rb | 48 +++++++++++++++++++ config/danbooru_default_config.rb | 14 +++++- config/routes.rb | 8 ++++ script/mock_services/README.md | 7 --- script/mock_services/iqdbs.rb | 14 ------ script/mock_services/mock_service_helper.rb | 22 --------- script/mock_services/recommender.rb | 19 -------- script/mock_services/reportbooru.rb | 22 --------- .../mock_services_controller_test.rb | 28 +++++++++++ 9 files changed, 96 insertions(+), 86 deletions(-) create mode 100644 app/controllers/mock_services_controller.rb delete mode 100644 script/mock_services/README.md delete mode 100644 script/mock_services/iqdbs.rb delete mode 100644 script/mock_services/mock_service_helper.rb delete mode 100644 script/mock_services/recommender.rb delete mode 100644 script/mock_services/reportbooru.rb create mode 100644 test/functional/mock_services_controller_test.rb diff --git a/app/controllers/mock_services_controller.rb b/app/controllers/mock_services_controller.rb new file mode 100644 index 000000000..67d387fb6 --- /dev/null +++ b/app/controllers/mock_services_controller.rb @@ -0,0 +1,48 @@ +class MockServicesController < ApplicationController + skip_forgery_protection + respond_to :json + + before_action do + raise User::PrivilegeError if Rails.env.production? + end + + def recommender_recommend + @data = posts.map { |post| [post.id, rand(0.0..1.0)] } + render json: @data + end + + def recommender_similar + @data = posts.map { |post| [post.id, rand(0.0..1.0)] } + render json: @data + end + + def reportbooru_missed_searches + @data = tags.map { |tag| "#{tag.name} #{rand(1.0..1000.0)}" }.join("\n") + render json: @data + end + + def reportbooru_post_searches + @data = tags.map { |tag| [tag.name, rand(1..1000)] } + render json: @data + end + + def reportbooru_post_views + @data = posts.map { |post| [post.id, rand(1..1000)] } + render json: @data + end + + def iqdbs_similar + @data = posts.map { |post| { post_id: post.id, score: rand(0..100)} } + render json: @data + end + + private + + def posts(limit = 10) + Post.last(limit) + end + + def tags(limit = 10) + Tag.order(post_count: :desc).limit(limit) + end +end diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb index b82014e0a..e52e6a998 100644 --- a/config/danbooru_default_config.rb +++ b/config/danbooru_default_config.rb @@ -376,14 +376,20 @@ module Danbooru false end - # reportbooru options - see https://github.com/r888888888/reportbooru + # The URL for the Reportbooru server (https://github.com/evazion/reportbooru). + # Optional. Used for tracking post views, popular searches, and missed searches. + # Set to http://localhost/mock/reportbooru to enable a fake reportbooru + # server for development purposes. def reportbooru_server end def reportbooru_key end - # iqdbs options - see https://github.com/r888888888/iqdbs + # The URL for the IQDBs server (https://github.com/evazion/iqdbs). + # Optional. Used for dupe detection and reverse image searches. + # Set to http://localhost/mock/iqdbs to enable a fake iqdb server for + # development purposes. def iqdbs_server end @@ -461,6 +467,10 @@ module Danbooru def cloudflare_zone end + # The URL for the recommender server (https://github.com/evazion/recommender). + # Optional. Used to generate post recommendations. + # Set to http://localhost/mock/recommender to enable a fake recommender + # server for development purposes. def recommender_server end diff --git a/config/routes.rb b/config/routes.rb index 95392b1e1..9b8ff2e6e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -372,6 +372,14 @@ Rails.application.routes.draw do get "/static/contact" => "static#contact", :as => "contact" get "/static/dtext_help" => "static#dtext_help", :as => "dtext_help" + get "/mock/recommender/recommend/:user_id" => "mock_services#recommender_recommend", as: "mock_recommender_recommend" + get "/mock/recommender/similiar/:post_id" => "mock_services#recommender_similar", as: "mock_recommender_similar" + get "/mock/reportbooru/missed_searches" => "mock_services#reportbooru_missed_searches", as: "mock_reportbooru_missed_searches" + get "/mock/reportbooru/post_searches/rank" => "mock_services#reportbooru_post_searches", as: "mock_reportbooru_post_searches" + get "/mock/reportbooru/post_views/rank" => "mock_services#reportbooru_post_views", as: "mock_reportbooru_post_views" + get "/mock/iqdbs/similar" => "mock_services#iqdbs_similar", as: "mock_iqdbs_similar" + post "/mock/iqdbs/similar" => "mock_services#iqdbs_similar" + root :to => "posts#index" get "*other", :to => "static#not_found" diff --git a/script/mock_services/README.md b/script/mock_services/README.md deleted file mode 100644 index 6f8f84e8a..000000000 --- a/script/mock_services/README.md +++ /dev/null @@ -1,7 +0,0 @@ -These are mocked services to be used for development purposes. - -- danbooru: port 3000 -- recommender: port 3001 -- iqdbs: port 3002 -- reportbooru: port 3003 - diff --git a/script/mock_services/iqdbs.rb b/script/mock_services/iqdbs.rb deleted file mode 100644 index a3cca08c3..000000000 --- a/script/mock_services/iqdbs.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'sinatra' -require 'json' -require_relative './mock_service_helper' - -set :port, 3002 - -configure do - POST_IDS = MockServiceHelper.fetch_post_ids -end - -get '/similar' do - content_type :json - POST_IDS[0..10].map {|x| {post_id: x}}.to_json -end diff --git a/script/mock_services/mock_service_helper.rb b/script/mock_services/mock_service_helper.rb deleted file mode 100644 index 2b259bc4c..000000000 --- a/script/mock_services/mock_service_helper.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'socket' -require 'timeout' -require 'httparty' - -module MockServiceHelper - module_function - - DANBOORU_PORT = 3000 - - def fetch_post_ids - begin - s = TCPSocket.new("localhost", DANBOORU_PORT) - s.close - rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH - sleep 1 - retry - end - - json = HTTParty.get("http://localhost:#{DANBOORU_PORT}/posts.json?random=true&limit=10").body - return JSON.parse(json).map {|x| x["id"]} - end -end diff --git a/script/mock_services/recommender.rb b/script/mock_services/recommender.rb deleted file mode 100644 index cb5f2b317..000000000 --- a/script/mock_services/recommender.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'sinatra' -require 'json' -require_relative './mock_service_helper' - -set :port, 3001 - -configure do - POST_IDS = MockServiceHelper.fetch_post_ids -end - -get '/recommend/:user_id' do - content_type :json - POST_IDS[0..10].map {|x| [x, "1.000"]}.to_json -end - -get '/similar/:post_id' do - content_type :json - POST_IDS[0..6].map {|x| [x, "1.000"]}.to_json -end diff --git a/script/mock_services/reportbooru.rb b/script/mock_services/reportbooru.rb deleted file mode 100644 index 3aa40edb3..000000000 --- a/script/mock_services/reportbooru.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'sinatra' -require 'json' - -set :port, 3003 - -get '/missed_searches' do - content_type :text - return "abcdefg 10.0\nblahblahblah 20.0\n" -end - -get '/post_searches/rank' do - content_type :json - return [["abc", 100], ["def", 200]].to_json -end - -get '/reports/user_similarity' do - # todo -end - -post '/post_views' do - # todo -end diff --git a/test/functional/mock_services_controller_test.rb b/test/functional/mock_services_controller_test.rb new file mode 100644 index 000000000..5f33cc691 --- /dev/null +++ b/test/functional/mock_services_controller_test.rb @@ -0,0 +1,28 @@ +require 'test_helper' + +class MockServicesControllerTest < ActionDispatch::IntegrationTest + context "The mock services controller" do + setup do + create(:post) + create(:tag) + end + + context "for all actions" do + should "work" do + paths = [ + mock_recommender_recommend_path(42), + mock_recommender_similar_path(42), + mock_reportbooru_missed_searches_path, + mock_reportbooru_post_searches_path, + mock_reportbooru_post_views_path, + mock_iqdbs_similar_path, + ] + + paths.each do |path| + get path + assert_response :success + end + end + end + end +end From a4efeb22607d2a78a43030ce01e702fdeeb0dc00 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 12:00:57 -0500 Subject: [PATCH 065/173] gems: drop Mechanize, HTTParty, and Sinatra gems. --- Gemfile | 3 -- Gemfile.lock | 36 --------------- app/logical/danbooru/http.rb | 2 +- app/logical/pixiv_api_client.rb | 1 - app/logical/sources/strategies/base.rb | 2 +- config/danbooru_default_config.rb | 16 ------- config/initializers/mechanize_patch.rb | 64 -------------------------- 7 files changed, 2 insertions(+), 122 deletions(-) delete mode 100644 config/initializers/mechanize_patch.rb diff --git a/Gemfile b/Gemfile index 70656ec6b..86bb62c2d 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,6 @@ gem "pg" gem "delayed_job" gem "delayed_job_active_record" gem "simple_form" -gem "mechanize" gem "whenever", :require => false gem "sanitize" gem 'ruby-vips' @@ -28,7 +27,6 @@ gem 'daemons' gem 'oauth2' gem 'bootsnap' gem 'addressable' -gem 'httparty' gem 'rakismet' gem 'recaptcha', require: "recaptcha/rails" gem 'activemodel-serializers-xml' @@ -63,7 +61,6 @@ end group :development do gem 'rubocop' gem 'rubocop-rails' - gem 'sinatra' gem 'meta_request' gem 'rack-mini-profiler' gem 'stackprof' diff --git a/Gemfile.lock b/Gemfile.lock index 222928350..a8fa40f17 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -122,7 +122,6 @@ GEM chronic (0.10.2) coderay (1.1.3) concurrent-ruby (1.1.6) - connection_pool (2.2.3) crass (1.0.6) daemons (1.3.1) delayed_job (4.1.8) @@ -164,9 +163,6 @@ GEM http-form_data (2.3.0) http-parser (1.2.1) ffi-compiler (>= 1.0, < 2.0) - httparty (0.18.1) - mime-types (~> 3.0) - multi_xml (>= 0.5.2) i18n (1.8.3) concurrent-ruby (~> 1.0) ipaddress_2 (0.13.0) @@ -188,24 +184,12 @@ GEM mini_mime (>= 0.1.1) marcel (0.3.3) mimemagic (~> 0.3.2) - mechanize (2.7.6) - domain_name (~> 0.5, >= 0.5.1) - http-cookie (~> 1.0) - mime-types (>= 1.17.2) - net-http-digest_auth (~> 1.1, >= 1.1.1) - net-http-persistent (>= 2.5.2) - nokogiri (~> 1.6) - ntlm-http (~> 0.1, >= 0.1.1) - webrobots (>= 0.0.9, < 0.2) memoist (0.16.2) memory_profiler (0.9.14) meta_request (0.7.2) rack-contrib (>= 1.1, < 3) railties (>= 3.0.0, < 7) method_source (1.0.0) - mime-types (3.3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2020.0512) mimemagic (0.3.5) mini_mime (1.0.2) mini_portile2 (2.5.0) @@ -224,11 +208,6 @@ GEM multi_json (1.14.1) multi_xml (0.6.0) multipart-post (2.1.1) - mustermann (1.1.1) - ruby2_keywords (~> 0.0.1) - net-http-digest_auth (1.4.1) - net-http-persistent (4.0.0) - connection_pool (~> 2.2) net-scp (3.0.0) net-ssh (>= 2.6.5, < 7.0.0) net-sftp (3.0.0) @@ -241,7 +220,6 @@ GEM nokogiri (1.11.0.rc2-x64-mingw32) nokogumbo (2.0.2) nokogiri (~> 1.8, >= 1.8.4) - ntlm-http (0.1.1) oauth2 (1.4.4) faraday (>= 0.8, < 2.0) jwt (>= 1.0, < 3.0) @@ -271,8 +249,6 @@ GEM rack (~> 2.0) rack-mini-profiler (2.0.2) rack (>= 1.2.0) - rack-protection (2.0.8.1) - rack rack-proxy (0.6.5) rack rack-test (1.1.0) @@ -339,9 +315,7 @@ GEM ruby-progressbar (1.10.1) ruby-vips (2.0.17) ffi (~> 1.9) - ruby2_keywords (0.0.2) rubyzip (2.3.0) - safe_yaml (1.0.5) sanitize (5.2.1) crass (~> 1.0.2) nokogiri (>= 1.8.0) @@ -364,11 +338,6 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) - sinatra (2.0.8.1) - mustermann (~> 1.0) - rack (~> 2.0) - rack-protection (= 2.0.8.1) - tilt (~> 2.0) sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -385,7 +354,6 @@ GEM stripe (5.22.0) thor (1.0.1) thread_safe (0.3.6) - tilt (2.0.10) tzinfo (1.2.7) thread_safe (~> 0.1) unf (0.1.4) @@ -404,7 +372,6 @@ GEM rack-proxy (>= 0.6.1) railties (>= 5.2) semantic_range (>= 2.3.0) - webrobots (0.1.2) websocket-driver (0.7.2) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -442,12 +409,10 @@ DEPENDENCIES ffaker flamegraph http - httparty ipaddress_2 jquery-rails listen mail - mechanize memoist memory_profiler meta_request @@ -484,7 +449,6 @@ DEPENDENCIES shoulda-matchers simple_form simplecov (~> 0.17.0) - sinatra stackprof streamio-ffmpeg stripe diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index dfe6558f8..590271fe0 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -22,8 +22,8 @@ module Danbooru @http ||= ::Danbooru::Http::ApplicationClient.new .timeout(DEFAULT_TIMEOUT) - .headers(Danbooru.config.http_headers) .headers("Accept-Encoding" => "gzip") + .headers("User-Agent": "#{Danbooru.config.canonical_app_name}/#{Rails.application.config.x.git_hash}") .use(:auto_inflate) .use(:retriable) .use(redirector: { max_redirects: MAX_REDIRECTS }) diff --git a/app/logical/pixiv_api_client.rb b/app/logical/pixiv_api_client.rb index 7d3d00156..ca4f06ae6 100644 --- a/app/logical/pixiv_api_client.rb +++ b/app/logical/pixiv_api_client.rb @@ -155,7 +155,6 @@ class PixivApiClient def api_client http.headers( - **Danbooru.config.http_headers, "Referer": "http://www.pixiv.net", "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Bearer #{access_token}" diff --git a/app/logical/sources/strategies/base.rb b/app/logical/sources/strategies/base.rb index e4f3d1a6a..f7e4f4b76 100644 --- a/app/logical/sources/strategies/base.rb +++ b/app/logical/sources/strategies/base.rb @@ -141,7 +141,7 @@ module Sources # Subclasses should merge in any required headers needed to access resources # on the site. def headers - Danbooru.config.http_headers + {} end # Returns the size of the image resource without actually downloading the file. diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb index e52e6a998..f9570949c 100644 --- a/config/danbooru_default_config.rb +++ b/config/danbooru_default_config.rb @@ -340,22 +340,6 @@ module Danbooru def twitter_api_secret end - # The default headers to be sent with outgoing http requests. Some external - # services will fail if you don't set a valid User-Agent. - def http_headers - { - "User-Agent" => "#{Danbooru.config.canonical_app_name}/#{Rails.application.config.x.git_hash}" - } - end - - def httparty_options - # proxy example: - # {http_proxyaddr: "", http_proxyport: "", http_proxyuser: nil, http_proxypass: nil} - { - headers: Danbooru.config.http_headers - } - end - # you should override this def email_key "zDMSATq0W3hmA5p3rKTgD" diff --git a/config/initializers/mechanize_patch.rb b/config/initializers/mechanize_patch.rb deleted file mode 100644 index 875c28b9d..000000000 --- a/config/initializers/mechanize_patch.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'mechanize' - -if Rails.env.test? - # something about the root certs on the travis ci image causes Mechanize - # to intermittently fail. this is a monkey patch to reset the connection - # after every request to avoid dealing wtiht he issue. - # - # from http://scottwb.com/blog/2013/11/09/defeating-the-infamous-mechanize-too-many-connection-resets-bug/ - class Mechanize::HTTP::Agent - MAX_RESET_RETRIES = 10 - - # We need to replace the core Mechanize HTTP method: - # - # Mechanize::HTTP::Agent#fetch - # - # with a wrapper that handles the infamous "too many connection resets" - # Mechanize bug that is described here: - # - # https://github.com/sparklemotion/mechanize/issues/123 - # - # The wrapper shuts down the persistent HTTP connection when it fails with - # this error, and simply tries again. In practice, this only ever needs to - # be retried once, but I am going to let it retry a few times - # (MAX_RESET_RETRIES), just in case. - # - def fetch_with_retry( - uri, - method = :get, - headers = {}, - params = [], - referer = current_page, - redirects = 0 - ) - action = "#{method.to_s.upcase} #{uri}" - retry_count = 0 - - begin - fetch_without_retry(uri, method, headers, params, referer, redirects) - rescue Net::HTTP::Persistent::Error => e - # Pass on any other type of error. - raise unless e.message =~ /too many connection resets/ - - # Pass on the error if we've tried too many times. - if retry_count >= MAX_RESET_RETRIES - print "R" - # puts "**** WARN: Mechanize retried connection reset #{MAX_RESET_RETRIES} times and never succeeded: #{action}" - raise - end - - # Otherwise, shutdown the persistent HTTP connection and try again. - print "R" - # puts "**** WARN: Mechanize retrying connection reset error: #{action}" - retry_count += 1 - self.http.shutdown - retry - end - end - - # Alias so #fetch actually uses our new #fetch_with_retry to wrap the - # old one aliased as #fetch_without_retry. - alias fetch_without_retry fetch - alias fetch fetch_with_retry - end -end From f020070b7d7871558f37999bcaf87e4145f3ccfb Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 15:15:47 -0500 Subject: [PATCH 066/173] Add .editorconfig. --- .editorconfig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..2f5475e44 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[**.{js,rb,css,erb,md,json,yml}] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[**.html.erb] +indent_size = unset + +[app/javascript/vendor/**] +indent_size = unset From 4acd89838b034fa23d9ca9181668b75e630c0ac9 Mon Sep 17 00:00:00 2001 From: BrokenEagle <brokeneagle98@yahoo.com> Date: Sun, 21 Jun 2020 22:37:59 +0000 Subject: [PATCH 067/173] Add ability to search on status of parent/child --- app/javascript/src/javascripts/autocomplete.js.erb | 13 ++++--------- app/logical/post_query_builder.rb | 4 ++++ test/unit/post_query_builder_test.rb | 13 +++++++++++++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/app/javascript/src/javascripts/autocomplete.js.erb b/app/javascript/src/javascripts/autocomplete.js.erb index 28f44f989..49e9abee4 100644 --- a/app/javascript/src/javascripts/autocomplete.js.erb +++ b/app/javascript/src/javascripts/autocomplete.js.erb @@ -9,6 +9,7 @@ Autocomplete.ORDER_METATAGS = <%= PostQueryBuilder::ORDER_METATAGS.to_json.html_ Autocomplete.DISAPPROVAL_REASONS = <%= PostDisapproval::REASONS.to_json.html_safe %>; /* eslint-enable */ +Autocomplete.MISC_STATUSES = ["deleted", "active", "pending", "flagged", "banned", "modqueue", "unmoderated"]; Autocomplete.TAG_PREFIXES = "-|~|" + Object.keys(Autocomplete.TAG_CATEGORIES).map(category => category + ":").join("|"); Autocomplete.METATAGS_REGEX = Autocomplete.METATAGS.concat(Object.keys(Autocomplete.TAG_CATEGORIES)).join("|"); Autocomplete.TERM_REGEX = new RegExp(`([-~]*)(?:(${Autocomplete.METATAGS_REGEX}):)?(\\S*)$`, "i"); @@ -268,9 +269,7 @@ Autocomplete.render_item = function(list, item) { Autocomplete.static_metatags = { order: Autocomplete.ORDER_METATAGS, - status: [ - "any", "deleted", "active", "pending", "flagged", "banned", "modqueue", "unmoderated" - ], + status: ["any"].concat(Autocomplete.MISC_STATUSES), rating: [ "safe", "questionable", "explicit" ], @@ -280,12 +279,8 @@ Autocomplete.static_metatags = { embedded: [ "true", "false" ], - child: [ - "any", "none" - ], - parent: [ - "any", "none" - ], + child: ["any", "none"].concat(Autocomplete.MISC_STATUSES), + parent: ["any", "none"].concat(Autocomplete.MISC_STATUSES), filetype: [ "jpg", "png", "gif", "swf", "zip", "webm", "mp4" ], diff --git a/app/logical/post_query_builder.rb b/app/logical/post_query_builder.rb index 8d3b61d66..0829ce3ba 100644 --- a/app/logical/post_query_builder.rb +++ b/app/logical/post_query_builder.rb @@ -307,6 +307,8 @@ class PostQueryBuilder Post.where(parent: nil) when "any" Post.where.not(parent: nil) + when /pending|flagged|modqueue|deleted|banned|active|unmoderated/ + Post.where.not(parent: nil).where(parent: status_matches(parent)) when /\A\d+\z/ Post.where(id: parent).or(Post.where(parent: parent)) else @@ -320,6 +322,8 @@ class PostQueryBuilder Post.where(has_children: false) when "any" Post.where(has_children: true) + when /pending|flagged|modqueue|deleted|banned|active|unmoderated/ + Post.where(has_children: true).where(children: status_matches(child)) else Post.none end diff --git a/test/unit/post_query_builder_test.rb b/test/unit/post_query_builder_test.rb index d74b08e8e..8323f72e0 100644 --- a/test/unit/post_query_builder_test.rb +++ b/test/unit/post_query_builder_test.rb @@ -277,6 +277,19 @@ class PostQueryBuilderTest < ActiveSupport::TestCase assert_tag_match([child, parent], "-child:garbage") end + should "return posts when using the status of the parent/child" do + parent_of_deleted = create(:post) + deleted = create(:post, is_deleted: true, tag_string: "parent:#{parent_of_deleted.id}") + child_of_deleted = create(:post, tag_string: "parent:#{deleted.id}") + all = [child_of_deleted, deleted, parent_of_deleted] + + assert_tag_match([child_of_deleted], "parent:deleted") + assert_tag_match(all - [child_of_deleted], "-parent:deleted") + + assert_tag_match([parent_of_deleted], "child:deleted") + assert_tag_match(all - [parent_of_deleted], "-child:deleted") + end + should "return posts for the favgroup:<name> metatag" do post1 = create(:post) post2 = create(:post) From f85eef9bcdc35c69395ce457393e5c11868ae024 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 18:03:00 -0500 Subject: [PATCH 068/173] nijie: fix bug with retries returning cached responses. Bug: if a Nijie login failed with a 429 Too Many Requests error, the error would get cached, so when we retried the request, we would just get our own cached response back every time. The 429 error would eventually be passed up to the Nijie strategy, which caused random methods to fail because they couldn't get the html page. Fix: add the `retriable` feature *after* the `cache` feature so that retries don't go through the cache. This is a hack. We want retries to go at the bottom of the stack, below caching, but we can't enforce this ordering. --- app/logical/danbooru/http.rb | 1 - app/logical/danbooru/http/retriable.rb | 2 ++ app/logical/sources/strategies/nijie.rb | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index 590271fe0..8e8655965 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -25,7 +25,6 @@ module Danbooru .headers("Accept-Encoding" => "gzip") .headers("User-Agent": "#{Danbooru.config.canonical_app_name}/#{Rails.application.config.x.git_hash}") .use(:auto_inflate) - .use(:retriable) .use(redirector: { max_redirects: MAX_REDIRECTS }) .use(:session) end diff --git a/app/logical/danbooru/http/retriable.rb b/app/logical/danbooru/http/retriable.rb index 681dd8bbe..23d5e865a 100644 --- a/app/logical/danbooru/http/retriable.rb +++ b/app/logical/danbooru/http/retriable.rb @@ -21,6 +21,8 @@ module Danbooru retries = max_retries while retriable?(response) && retries > 0 && retry_delay(response) <= max_delay + DanbooruLogger.info "Retrying url=#{request.uri} status=#{response.status} retries=#{retries} delay=#{retry_delay(response)}" + retries -= 1 sleep(retry_delay(response)) response = yield request diff --git a/app/logical/sources/strategies/nijie.rb b/app/logical/sources/strategies/nijie.rb index f3a15b576..b2a6fcc7a 100644 --- a/app/logical/sources/strategies/nijie.rb +++ b/app/logical/sources/strategies/nijie.rb @@ -180,7 +180,10 @@ module Sources http = Danbooru::Http.new form = { email: Danbooru.config.nijie_login, password: Danbooru.config.nijie_password } - response = http.cache(1.hour).post("https://nijie.info/login_int.php", form: form) + + # XXX `retriable` must come after `cache` so that retries don't return cached error responses. + response = http.cache(1.hour).use(:retriable).post("https://nijie.info/login_int.php", form: form) + DanbooruLogger.info "Nijie login failed (#{url}, #{response.status})" if response.status != 200 return nil unless response.status == 200 response = http.cookies(R18: 1).cache(1.minute).get(page_url) From bd25be95f52f99e69e573e44d295751478bfed63 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 18:27:32 -0500 Subject: [PATCH 069/173] danbooru::http: factor out cache feature. Fixes a bug with cookies stored by the `session` feature not being sent with cached requests. --- app/logical/danbooru/http.rb | 32 +++++++----------------------- app/logical/danbooru/http/cache.rb | 30 ++++++++++++++++++++++++++++ test/unit/danbooru_http_test.rb | 31 +++++++++++++++++++++++++---- 3 files changed, 64 insertions(+), 29 deletions(-) create mode 100644 app/logical/danbooru/http/cache.rb diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index 8e8655965..6bbc7db87 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -1,5 +1,6 @@ require "danbooru/http/html_adapter" require "danbooru/http/xml_adapter" +require "danbooru/http/cache" require "danbooru/http/redirector" require "danbooru/http/retriable" require "danbooru/http/session" @@ -12,7 +13,7 @@ module Danbooru DEFAULT_TIMEOUT = 10 MAX_REDIRECTS = 5 - attr_accessor :cache, :max_size, :http + attr_accessor :max_size, :http class << self delegate :get, :head, :put, :post, :delete, :cache, :follow, :max_size, :timeout, :auth, :basic_auth, :headers, :cookies, :use, :public_only, :download_media, to: :new @@ -49,10 +50,6 @@ module Danbooru request(:delete, url, **options) end - def cache(expiry) - dup.tap { |o| o.cache = expiry.to_i } - end - def follow(*args) dup.tap { |o| o.http = o.http.follow(*args) } end @@ -85,6 +82,10 @@ module Danbooru dup.tap { |o| o.http = o.http.use(*args) } end + def cache(expires_in) + use(cache: { expires_in: expires_in }) + end + # allow requests only to public IPs, not to local or private networks. def public_only dup.tap do |o| @@ -128,11 +129,7 @@ module Danbooru protected def request(method, url, **options) - if @cache.present? - cached_request(method, url, **options) - else - raw_request(method, url, **options) - end + http.send(method, url, **options) rescue ValidatingSocket::ProhibitedIpError fake_response(597, "") rescue HTTP::Redirector::TooManyRedirectsError @@ -141,21 +138,6 @@ module Danbooru fake_response(599, "") end - def cached_request(method, url, **options) - key = Cache.hash({ method: method, url: url, headers: http.default_options.headers.to_h, **options }.to_json) - - cached_response = Cache.get(key, @cache) do - response = raw_request(method, url, **options) - { status: response.status, body: response.to_s, headers: response.headers.to_h, version: "1.1" } - end - - ::HTTP::Response.new(**cached_response) - end - - def raw_request(method, url, **options) - http.send(method, url, **options) - end - def fake_response(status, body) ::HTTP::Response.new(status: status, version: "1.1", body: ::HTTP::Response::Body.new(body)) end diff --git a/app/logical/danbooru/http/cache.rb b/app/logical/danbooru/http/cache.rb new file mode 100644 index 000000000..43932bd28 --- /dev/null +++ b/app/logical/danbooru/http/cache.rb @@ -0,0 +1,30 @@ +module Danbooru + class Http + class Cache < HTTP::Feature + HTTP::Options.register_feature :cache, self + + attr_reader :expires_in + + def initialize(expires_in:) + @expires_in = expires_in + end + + def perform(request, &block) + ::Cache.get(cache_key(request), expires_in) do + response = yield request + + # XXX hack to remove connection state from response body so we can serialize it for caching. + response.flush + response.body.instance_variable_set(:@connection, nil) + response.body.instance_variable_set(:@stream, nil) + + response + end + end + + def cache_key(request) + "http:" + ::Cache.hash({ method: request.verb, url: request.uri.to_s, headers: request.headers.sort }.to_json) + end + end + end +end diff --git a/test/unit/danbooru_http_test.rb b/test/unit/danbooru_http_test.rb index b6ef81e7c..aa73976ea 100644 --- a/test/unit/danbooru_http_test.rb +++ b/test/unit/danbooru_http_test.rb @@ -62,14 +62,37 @@ class DanbooruHttpTest < ActiveSupport::TestCase resp4 = http.cookies(def: 3, ghi: 4).get("https://httpbin.org/cookies") assert_equal({ abc: "1", def: "3", ghi: "4" }, resp4.parse["cookies"].symbolize_keys) end + end - should "cache requests" do - response1 = Danbooru::Http.cache(1.minute).get("https://httpbin.org/uuid") + context "cache feature" do + should "cache multiple requests to the same url" do + http = Danbooru::Http.cache(1.hour) + + response1 = http.get("https://httpbin.org/uuid") assert_equal(200, response1.status) - response2 = Danbooru::Http.cache(1.minute).get("https://httpbin.org/uuid") + response2 = http.get("https://httpbin.org/uuid") assert_equal(200, response2.status) - assert_equal(response2.body, response1.body) + assert_equal(response2.to_s, response1.to_s) + end + + should "cache cookies correctly" do + http = Danbooru::Http.cache(1.hour) + + resp1 = http.get("https://httpbin.org/cookies") + resp2 = http.get("https://httpbin.org/cookies/set/abc/1") + resp3 = http.get("https://httpbin.org/cookies/set/def/2") + resp4 = http.get("https://httpbin.org/cookies") + + assert_equal(200, resp1.status) + assert_equal(200, resp2.status) + assert_equal(200, resp3.status) + assert_equal(200, resp4.status) + + assert_equal({}, resp1.parse["cookies"].symbolize_keys) + assert_equal({ abc: "1" }, resp2.parse["cookies"].symbolize_keys) + assert_equal({ abc: "1", def: "2" }, resp3.parse["cookies"].symbolize_keys) + assert_equal({ abc: "1", def: "2" }, resp4.parse["cookies"].symbolize_keys) end end From 504edff14be0801a335f12fc480d889285180b08 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 18:49:08 -0500 Subject: [PATCH 070/173] Update ruby gems and yarn packages. --- Gemfile.lock | 18 +- yarn.lock | 587 +++++++++++++++++++++------------------------------ 2 files changed, 253 insertions(+), 352 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a8fa40f17..9ae511510 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,7 +77,7 @@ GEM ansi (1.5.0) ast (2.4.1) aws-eventstream (1.1.0) - aws-partitions (1.329.0) + aws-partitions (1.332.0) aws-sdk-core (3.100.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) @@ -86,8 +86,8 @@ GEM aws-sdk-sqs (1.27.1) aws-sdk-core (~> 3, >= 3.99.0) aws-sigv4 (~> 1.1) - aws-sigv4 (1.1.4) - aws-eventstream (~> 1.0, >= 1.0.2) + aws-sigv4 (1.2.0) + aws-eventstream (~> 1, >= 1.0.2) bcrypt (3.1.13) bootsnap (1.4.6) msgpack (~> 1.0) @@ -110,7 +110,7 @@ GEM sshkit (~> 1.3) capistrano3-unicorn (0.2.1) capistrano (~> 3.1, >= 3.1.0) - capybara (3.32.2) + capybara (3.33.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -138,8 +138,8 @@ GEM dotenv (= 2.7.5) railties (>= 3.2, < 6.1) erubi (1.9.0) - factory_bot (5.2.0) - activesupport (>= 4.2.0) + factory_bot (6.0.2) + activesupport (>= 5.0.0) faraday (1.0.1) multipart-post (>= 1.2, < 3) ffaker (2.15.0) @@ -226,9 +226,9 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) - parallel (1.19.1) - parser (2.7.1.3) - ast (~> 2.4.0) + parallel (1.19.2) + parser (2.7.1.4) + ast (~> 2.4.1) pg (1.2.3) pg (1.2.3-x64-mingw32) pry (0.13.1) diff --git a/yarn.lock b/yarn.lock index f062816ea..c8abadcae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,35 +2,35 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff" - integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.3.tgz#324bcfd8d35cd3d47dae18cde63d752086435e9a" + integrity sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg== dependencies: - "@babel/highlight" "^7.10.1" + "@babel/highlight" "^7.10.3" -"@babel/compat-data@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.1.tgz#b1085ffe72cd17bf2c0ee790fc09f9626011b2db" - integrity sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw== +"@babel/compat-data@^7.10.1", "@babel/compat-data@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.3.tgz#9af3e033f36e8e2d6e47570db91e64a846f5d382" + integrity sha512-BDIfJ9uNZuI0LajPfoYV28lX8kyCPMHY6uY4WH1lJdcicmAfxCK5ASzaeV0D/wsUaRH/cLk+amuxtC37sZ8TUg== dependencies: browserslist "^4.12.0" invariant "^2.2.4" semver "^5.5.0" "@babel/core@>=7.9.0", "@babel/core@^7.9.0": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.2.tgz#bd6786046668a925ac2bd2fd95b579b92a23b36a" - integrity sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ== + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.3.tgz#73b0e8ddeec1e3fdd7a2de587a60e17c440ec77e" + integrity sha512-5YqWxYE3pyhIi84L84YcwjeEgS+fa7ZjK6IBVGTjDVfm64njkR2lfDhVR5OudLk8x2GK59YoSyVv+L/03k1q9w== dependencies: - "@babel/code-frame" "^7.10.1" - "@babel/generator" "^7.10.2" + "@babel/code-frame" "^7.10.3" + "@babel/generator" "^7.10.3" "@babel/helper-module-transforms" "^7.10.1" "@babel/helpers" "^7.10.1" - "@babel/parser" "^7.10.2" - "@babel/template" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.2" + "@babel/parser" "^7.10.3" + "@babel/template" "^7.10.3" + "@babel/traverse" "^7.10.3" + "@babel/types" "^7.10.3" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" @@ -40,12 +40,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.10.1", "@babel/generator@^7.10.2": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.2.tgz#0fa5b5b2389db8bfdfcc3492b551ee20f5dd69a9" - integrity sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA== +"@babel/generator@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.3.tgz#32b9a0d963a71d7a54f5f6c15659c3dbc2a523a5" + integrity sha512-drt8MUHbEqRzNR0xnF8nMehbY11b1SDkRw03PSNH/3Rb2Z35oxkddVSi3rcaak0YJQ86PCuE7Qx1jSFhbLNBMA== dependencies: - "@babel/types" "^7.10.2" + "@babel/types" "^7.10.3" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" @@ -58,12 +58,12 @@ "@babel/types" "^7.10.1" "@babel/helper-builder-binary-assignment-operator-visitor@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz#0ec7d9be8174934532661f87783eb18d72290059" - integrity sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw== + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.3.tgz#4e9012d6701bef0030348d7f9c808209bd3e8687" + integrity sha512-lo4XXRnBlU6eRM92FkiZxpo1xFLmv3VsPFk61zJKMm7XYJfwqXHsYJTY6agoc4a3L8QPw1HqWehO18coZgbT6A== dependencies: - "@babel/helper-explode-assignable-expression" "^7.10.1" - "@babel/types" "^7.10.1" + "@babel/helper-explode-assignable-expression" "^7.10.3" + "@babel/types" "^7.10.3" "@babel/helper-compilation-targets@^7.10.2": version "7.10.2" @@ -77,14 +77,14 @@ semver "^5.5.0" "@babel/helper-create-class-features-plugin@^7.10.1": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz#7474295770f217dbcf288bf7572eb213db46ee67" - integrity sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ== + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.3.tgz#2783daa6866822e3d5ed119163b50f0fc3ae4b35" + integrity sha512-iRT9VwqtdFmv7UheJWthGc/h2s7MqoweBF9RUj77NFZsg9VfISvBTum3k6coAhJ8RWv2tj3yUjA03HxPd0vfpQ== dependencies: - "@babel/helper-function-name" "^7.10.1" - "@babel/helper-member-expression-to-functions" "^7.10.1" - "@babel/helper-optimise-call-expression" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-function-name" "^7.10.3" + "@babel/helper-member-expression-to-functions" "^7.10.3" + "@babel/helper-optimise-call-expression" "^7.10.3" + "@babel/helper-plugin-utils" "^7.10.3" "@babel/helper-replace-supers" "^7.10.1" "@babel/helper-split-export-declaration" "^7.10.1" @@ -97,59 +97,59 @@ "@babel/helper-regex" "^7.10.1" regexpu-core "^4.7.0" -"@babel/helper-define-map@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz#5e69ee8308648470dd7900d159c044c10285221d" - integrity sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg== +"@babel/helper-define-map@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.3.tgz#d27120a5e57c84727b30944549b2dfeca62401a8" + integrity sha512-bxRzDi4Sin/k0drWCczppOhov1sBSdBvXJObM1NLHQzjhXhwRtn7aRWGvLJWCYbuu2qUk3EKs6Ci9C9ps8XokQ== dependencies: - "@babel/helper-function-name" "^7.10.1" - "@babel/types" "^7.10.1" + "@babel/helper-function-name" "^7.10.3" + "@babel/types" "^7.10.3" lodash "^4.17.13" -"@babel/helper-explode-assignable-expression@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz#e9d76305ee1162ca467357ae25df94f179af2b7e" - integrity sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg== +"@babel/helper-explode-assignable-expression@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.3.tgz#9dc14f0cfa2833ea830a9c8a1c742b6e7461b05e" + integrity sha512-0nKcR64XrOC3lsl+uhD15cwxPvaB6QKUDlD84OT9C3myRbhJqTMYir69/RWItUvHpharv0eJ/wk7fl34ONSwZw== dependencies: - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" + "@babel/traverse" "^7.10.3" + "@babel/types" "^7.10.3" -"@babel/helper-function-name@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz#92bd63829bfc9215aca9d9defa85f56b539454f4" - integrity sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ== +"@babel/helper-function-name@^7.10.1", "@babel/helper-function-name@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.3.tgz#79316cd75a9fa25ba9787ff54544307ed444f197" + integrity sha512-FvSj2aiOd8zbeqijjgqdMDSyxsGHaMt5Tr0XjQsGKHD3/1FP3wksjnLAWzxw7lvXiej8W1Jt47SKTZ6upQNiRw== dependencies: - "@babel/helper-get-function-arity" "^7.10.1" - "@babel/template" "^7.10.1" - "@babel/types" "^7.10.1" + "@babel/helper-get-function-arity" "^7.10.3" + "@babel/template" "^7.10.3" + "@babel/types" "^7.10.3" -"@babel/helper-get-function-arity@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz#7303390a81ba7cb59613895a192b93850e373f7d" - integrity sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw== +"@babel/helper-get-function-arity@^7.10.1", "@babel/helper-get-function-arity@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.3.tgz#3a28f7b28ccc7719eacd9223b659fdf162e4c45e" + integrity sha512-iUD/gFsR+M6uiy69JA6fzM5seno8oE85IYZdbVVEuQaZlEzMO2MXblh+KSPJgsZAUx0EEbWXU0yJaW7C9CdAVg== dependencies: - "@babel/types" "^7.10.1" + "@babel/types" "^7.10.3" -"@babel/helper-hoist-variables@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz#7e77c82e5dcae1ebf123174c385aaadbf787d077" - integrity sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg== +"@babel/helper-hoist-variables@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.3.tgz#d554f52baf1657ffbd7e5137311abc993bb3f068" + integrity sha512-9JyafKoBt5h20Yv1+BXQMdcXXavozI1vt401KBiRc2qzUepbVnd7ogVNymY1xkQN9fekGwfxtotH2Yf5xsGzgg== dependencies: - "@babel/types" "^7.10.1" + "@babel/types" "^7.10.3" -"@babel/helper-member-expression-to-functions@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz#432967fd7e12a4afef66c4687d4ca22bc0456f15" - integrity sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g== +"@babel/helper-member-expression-to-functions@^7.10.1", "@babel/helper-member-expression-to-functions@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.3.tgz#bc3663ac81ac57c39148fef4c69bf48a77ba8dd6" + integrity sha512-q7+37c4EPLSjNb2NmWOjNwj0+BOyYlssuQ58kHEWk1Z78K5i8vTUsteq78HMieRPQSl/NtpQyJfdjt3qZ5V2vw== dependencies: - "@babel/types" "^7.10.1" + "@babel/types" "^7.10.3" -"@babel/helper-module-imports@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz#dd331bd45bccc566ce77004e9d05fe17add13876" - integrity sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg== +"@babel/helper-module-imports@^7.10.1", "@babel/helper-module-imports@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.3.tgz#766fa1d57608e53e5676f23ae498ec7a95e1b11a" + integrity sha512-Jtqw5M9pahLSUWA+76nhK9OG8nwYXzhQzVIGFoNaHnXF/r4l7kz4Fl0UAW7B6mqC5myoJiBP5/YQlXQTMfHI9w== dependencies: - "@babel/types" "^7.10.1" + "@babel/types" "^7.10.3" "@babel/helper-module-transforms@^7.10.1": version "7.10.1" @@ -164,17 +164,17 @@ "@babel/types" "^7.10.1" lodash "^4.17.13" -"@babel/helper-optimise-call-expression@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz#b4a1f2561870ce1247ceddb02a3860fa96d72543" - integrity sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg== +"@babel/helper-optimise-call-expression@^7.10.1", "@babel/helper-optimise-call-expression@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.3.tgz#f53c4b6783093195b0f69330439908841660c530" + integrity sha512-kT2R3VBH/cnSz+yChKpaKRJQJWxdGoc6SjioRId2wkeV3bK0wLLioFpJROrX0U4xr/NmxSSAWT/9Ih5snwIIzg== dependencies: - "@babel/types" "^7.10.1" + "@babel/types" "^7.10.3" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.1", "@babel/helper-plugin-utils@^7.8.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz#ec5a5cf0eec925b66c60580328b122c01230a127" - integrity sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.1", "@babel/helper-plugin-utils@^7.10.3", "@babel/helper-plugin-utils@^7.8.0": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.3.tgz#aac45cccf8bc1873b99a85f34bceef3beb5d3244" + integrity sha512-j/+j8NAWUTxOtx4LKHybpSClxHoq6I91DQ/mKgAXn5oNUPIUiGppjPIX3TDtJWPrdfP9Kfl7e4fgVMiQR9VE/g== "@babel/helper-regex@^7.10.1": version "7.10.1" @@ -183,16 +183,16 @@ dependencies: lodash "^4.17.13" -"@babel/helper-remap-async-to-generator@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz#bad6aaa4ff39ce8d4b82ccaae0bfe0f7dbb5f432" - integrity sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A== +"@babel/helper-remap-async-to-generator@^7.10.1", "@babel/helper-remap-async-to-generator@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.3.tgz#18564f8a6748be466970195b876e8bba3bccf442" + integrity sha512-sLB7666ARbJUGDO60ZormmhQOyqMX/shKBXZ7fy937s+3ID8gSrneMvKSSb+8xIM5V7Vn6uNVtOY1vIm26XLtA== dependencies: "@babel/helper-annotate-as-pure" "^7.10.1" "@babel/helper-wrap-function" "^7.10.1" - "@babel/template" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" + "@babel/template" "^7.10.3" + "@babel/traverse" "^7.10.3" + "@babel/types" "^7.10.3" "@babel/helper-replace-supers@^7.10.1": version "7.10.1" @@ -219,10 +219,10 @@ dependencies: "@babel/types" "^7.10.1" -"@babel/helper-validator-identifier@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5" - integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw== +"@babel/helper-validator-identifier@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.3.tgz#60d9847f98c4cea1b279e005fdb7c28be5412d15" + integrity sha512-bU8JvtlYpJSBPuj1VUmKpFGaDZuLxASky3LhaKj3bmpSTY6VWooSM8msk+Z0CZoErFye2tlABF6yDkT3FOPAXw== "@babel/helper-wrap-function@^7.10.1": version "7.10.1" @@ -243,27 +243,27 @@ "@babel/traverse" "^7.10.1" "@babel/types" "^7.10.1" -"@babel/highlight@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0" - integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg== +"@babel/highlight@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.3.tgz#c633bb34adf07c5c13156692f5922c81ec53f28d" + integrity sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw== dependencies: - "@babel/helper-validator-identifier" "^7.10.1" + "@babel/helper-validator-identifier" "^7.10.3" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.10.1", "@babel/parser@^7.10.2", "@babel/parser@^7.7.0": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.2.tgz#871807f10442b92ff97e4783b9b54f6a0ca812d0" - integrity sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ== +"@babel/parser@^7.10.3", "@babel/parser@^7.7.0": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.3.tgz#7e71d892b0d6e7d04a1af4c3c79d72c1f10f5315" + integrity sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA== -"@babel/plugin-proposal-async-generator-functions@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz#6911af5ba2e615c4ff3c497fe2f47b35bf6d7e55" - integrity sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw== +"@babel/plugin-proposal-async-generator-functions@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.3.tgz#5a02453d46e5362e2073c7278beab2e53ad7d939" + integrity sha512-WUUWM7YTOudF4jZBAJIW9D7aViYC/Fn0Pln4RIHlQALyno3sXSjqmTA4Zy1TKC2D49RCR8Y/Pn4OIUtEypK3CA== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-remap-async-to-generator" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.3" + "@babel/helper-remap-async-to-generator" "^7.10.3" "@babel/plugin-syntax-async-generators" "^7.8.0" "@babel/plugin-proposal-class-properties@^7.10.1", "@babel/plugin-proposal-class-properties@^7.8.3": @@ -306,12 +306,12 @@ "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-syntax-numeric-separator" "^7.10.1" -"@babel/plugin-proposal-object-rest-spread@^7.10.1", "@babel/plugin-proposal-object-rest-spread@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz#cba44908ac9f142650b4a65b8aa06bf3478d5fb6" - integrity sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ== +"@babel/plugin-proposal-object-rest-spread@^7.10.3", "@babel/plugin-proposal-object-rest-spread@^7.9.0": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.3.tgz#b8d0d22f70afa34ad84b7a200ff772f9b9fce474" + integrity sha512-ZZh5leCIlH9lni5bU/wB/UcjtcVLgR8gc+FAgW2OOY+m9h1II3ItTO1/cewNUcsIDZSYcSaz/rYVls+Fb0ExVQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.3" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-transform-parameters" "^7.10.1" @@ -323,12 +323,12 @@ "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/plugin-proposal-optional-chaining@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz#15f5d6d22708629451a91be28f8facc55b0e818c" - integrity sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA== +"@babel/plugin-proposal-optional-chaining@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.3.tgz#9a726f94622b653c0a3a7a59cdce94730f526f7c" + integrity sha512-yyG3n9dJ1vZ6v5sfmIlMMZ8azQoqx/5/nZTSWX1td6L1H1bsjzA8TInDChpafCZiJkeOFzp/PtrfigAQXxI1Ng== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.3" "@babel/plugin-syntax-optional-chaining" "^7.8.0" "@babel/plugin-proposal-private-methods@^7.10.1": @@ -448,26 +448,26 @@ "@babel/helper-plugin-utils" "^7.10.1" lodash "^4.17.13" -"@babel/plugin-transform-classes@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz#6e11dd6c4dfae70f540480a4702477ed766d733f" - integrity sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ== +"@babel/plugin-transform-classes@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.3.tgz#8d9a656bc3d01f3ff69e1fccb354b0f9d72ac544" + integrity sha512-irEX0ChJLaZVC7FvvRoSIxJlmk0IczFLcwaRXUArBKYHCHbOhe57aG8q3uw/fJsoSXvZhjRX960hyeAGlVBXZw== dependencies: "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-define-map" "^7.10.1" - "@babel/helper-function-name" "^7.10.1" - "@babel/helper-optimise-call-expression" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-define-map" "^7.10.3" + "@babel/helper-function-name" "^7.10.3" + "@babel/helper-optimise-call-expression" "^7.10.3" + "@babel/helper-plugin-utils" "^7.10.3" "@babel/helper-replace-supers" "^7.10.1" "@babel/helper-split-export-declaration" "^7.10.1" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz#59aa399064429d64dce5cf76ef9b90b7245ebd07" - integrity sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ== +"@babel/plugin-transform-computed-properties@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.3.tgz#d3aa6eef67cb967150f76faff20f0abbf553757b" + integrity sha512-GWzhaBOsdbjVFav96drOz7FzrcEW6AP5nax0gLIpstiFaI3LOb2tAg06TimaWU6YKOfUACK3FVrxPJ4GSc5TgA== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.3" "@babel/plugin-transform-destructuring@^7.10.1", "@babel/plugin-transform-destructuring@^7.8.8": version "7.10.1" @@ -547,14 +547,14 @@ "@babel/helper-simple-access" "^7.10.1" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz#9962e4b0ac6aaf2e20431ada3d8ec72082cbffb6" - integrity sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA== +"@babel/plugin-transform-modules-systemjs@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.3.tgz#004ae727b122b7b146b150d50cba5ffbff4ac56b" + integrity sha512-GWXWQMmE1GH4ALc7YXW56BTh/AlzvDWhUNn9ArFF0+Cz5G8esYlVbXfdyHa1xaD1j+GnBoCeoQNlwtZTVdiG/A== dependencies: - "@babel/helper-hoist-variables" "^7.10.1" + "@babel/helper-hoist-variables" "^7.10.3" "@babel/helper-module-transforms" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.3" babel-plugin-dynamic-import-node "^2.3.3" "@babel/plugin-transform-modules-umd@^7.10.1": @@ -565,10 +565,10 @@ "@babel/helper-module-transforms" "^7.10.1" "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" - integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== +"@babel/plugin-transform-named-capturing-groups-regex@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.3.tgz#a4f8444d1c5a46f35834a410285f2c901c007ca6" + integrity sha512-I3EH+RMFyVi8Iy/LekQm948Z4Lz4yKT7rK+vuCAeRm0kTa6Z5W7xuhRxDNJv0FPya/her6AUgrDITb70YHtTvA== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.8.3" @@ -602,10 +602,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.1" -"@babel/plugin-transform-regenerator@^7.10.1", "@babel/plugin-transform-regenerator@^7.8.7": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz#10e175cbe7bdb63cc9b39f9b3f823c5c7c5c5490" - integrity sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw== +"@babel/plugin-transform-regenerator@^7.10.3", "@babel/plugin-transform-regenerator@^7.8.7": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.3.tgz#6ec680f140a5ceefd291c221cb7131f6d7e8cb6d" + integrity sha512-H5kNeW0u8mbk0qa1jVIVTeJJL6/TJ81ltD4oyPx0P499DhMJrTmmIFCmJ3QloGpQG8K9symccB7S7SJpCKLwtw== dependencies: regenerator-transform "^0.14.2" @@ -617,12 +617,12 @@ "@babel/helper-plugin-utils" "^7.10.1" "@babel/plugin-transform-runtime@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.1.tgz#fd1887f749637fb2ed86dc278e79eb41df37f4b1" - integrity sha512-4w2tcglDVEwXJ5qxsY++DgWQdNJcCCsPxfT34wCUwIf2E7dI7pMpH8JczkMBbgBTNzBX62SZlNJ9H+De6Zebaw== + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.3.tgz#3b287b06acc534a7cb6e6c71d6b1d88b1922dd6c" + integrity sha512-b5OzMD1Hi8BBzgQdRHyVVaYrk9zG0wset1it2o3BgonkPadXfOv0aXRqd7864DeOIu3FGKP/h6lr15FE5mahVw== dependencies: - "@babel/helper-module-imports" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-module-imports" "^7.10.3" + "@babel/helper-plugin-utils" "^7.10.3" resolve "^1.8.1" semver "^5.5.1" @@ -648,13 +648,13 @@ "@babel/helper-plugin-utils" "^7.10.1" "@babel/helper-regex" "^7.10.1" -"@babel/plugin-transform-template-literals@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz#914c7b7f4752c570ea00553b4284dad8070e8628" - integrity sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg== +"@babel/plugin-transform-template-literals@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.3.tgz#69d39b3d44b31e7b4864173322565894ce939b25" + integrity sha512-yaBn9OpxQra/bk0/CaA4wr41O0/Whkg6nqjqApcinxM7pro51ojhX6fv1pimAnVjVfDy14K0ULoRL70CA9jWWA== dependencies: "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.3" "@babel/plugin-transform-typeof-symbol@^7.10.1": version "7.10.1" @@ -679,23 +679,23 @@ "@babel/helper-plugin-utils" "^7.10.1" "@babel/preset-env@^7.9.0": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.2.tgz#715930f2cf8573b0928005ee562bed52fb65fdfb" - integrity sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA== + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.3.tgz#3e58c9861bbd93b6a679987c7e4bd365c56c80c9" + integrity sha512-jHaSUgiewTmly88bJtMHbOd1bJf2ocYxb5BWKSDQIP5tmgFuS/n0gl+nhSrYDhT33m0vPxp+rP8oYYgPgMNQlg== dependencies: - "@babel/compat-data" "^7.10.1" + "@babel/compat-data" "^7.10.3" "@babel/helper-compilation-targets" "^7.10.2" - "@babel/helper-module-imports" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-proposal-async-generator-functions" "^7.10.1" + "@babel/helper-module-imports" "^7.10.3" + "@babel/helper-plugin-utils" "^7.10.3" + "@babel/plugin-proposal-async-generator-functions" "^7.10.3" "@babel/plugin-proposal-class-properties" "^7.10.1" "@babel/plugin-proposal-dynamic-import" "^7.10.1" "@babel/plugin-proposal-json-strings" "^7.10.1" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.1" "@babel/plugin-proposal-numeric-separator" "^7.10.1" - "@babel/plugin-proposal-object-rest-spread" "^7.10.1" + "@babel/plugin-proposal-object-rest-spread" "^7.10.3" "@babel/plugin-proposal-optional-catch-binding" "^7.10.1" - "@babel/plugin-proposal-optional-chaining" "^7.10.1" + "@babel/plugin-proposal-optional-chaining" "^7.10.3" "@babel/plugin-proposal-private-methods" "^7.10.1" "@babel/plugin-proposal-unicode-property-regex" "^7.10.1" "@babel/plugin-syntax-async-generators" "^7.8.0" @@ -712,8 +712,8 @@ "@babel/plugin-transform-async-to-generator" "^7.10.1" "@babel/plugin-transform-block-scoped-functions" "^7.10.1" "@babel/plugin-transform-block-scoping" "^7.10.1" - "@babel/plugin-transform-classes" "^7.10.1" - "@babel/plugin-transform-computed-properties" "^7.10.1" + "@babel/plugin-transform-classes" "^7.10.3" + "@babel/plugin-transform-computed-properties" "^7.10.3" "@babel/plugin-transform-destructuring" "^7.10.1" "@babel/plugin-transform-dotall-regex" "^7.10.1" "@babel/plugin-transform-duplicate-keys" "^7.10.1" @@ -724,24 +724,24 @@ "@babel/plugin-transform-member-expression-literals" "^7.10.1" "@babel/plugin-transform-modules-amd" "^7.10.1" "@babel/plugin-transform-modules-commonjs" "^7.10.1" - "@babel/plugin-transform-modules-systemjs" "^7.10.1" + "@babel/plugin-transform-modules-systemjs" "^7.10.3" "@babel/plugin-transform-modules-umd" "^7.10.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.3" "@babel/plugin-transform-new-target" "^7.10.1" "@babel/plugin-transform-object-super" "^7.10.1" "@babel/plugin-transform-parameters" "^7.10.1" "@babel/plugin-transform-property-literals" "^7.10.1" - "@babel/plugin-transform-regenerator" "^7.10.1" + "@babel/plugin-transform-regenerator" "^7.10.3" "@babel/plugin-transform-reserved-words" "^7.10.1" "@babel/plugin-transform-shorthand-properties" "^7.10.1" "@babel/plugin-transform-spread" "^7.10.1" "@babel/plugin-transform-sticky-regex" "^7.10.1" - "@babel/plugin-transform-template-literals" "^7.10.1" + "@babel/plugin-transform-template-literals" "^7.10.3" "@babel/plugin-transform-typeof-symbol" "^7.10.1" "@babel/plugin-transform-unicode-escapes" "^7.10.1" "@babel/plugin-transform-unicode-regex" "^7.10.1" "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.10.2" + "@babel/types" "^7.10.3" browserslist "^4.12.0" core-js-compat "^3.6.2" invariant "^2.2.2" @@ -760,42 +760,42 @@ esutils "^2.0.2" "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839" - integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg== + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364" + integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811" - integrity sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig== +"@babel/template@^7.10.1", "@babel/template@^7.10.3": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.3.tgz#4d13bc8e30bf95b0ce9d175d30306f42a2c9a7b8" + integrity sha512-5BjI4gdtD+9fHZUsaxPHPNpwa+xRkDO7c7JbhYn2afvrkDu5SfAAbi9AIMXw2xEhO/BR35TqiW97IqNvCo/GqA== dependencies: - "@babel/code-frame" "^7.10.1" - "@babel/parser" "^7.10.1" - "@babel/types" "^7.10.1" + "@babel/code-frame" "^7.10.3" + "@babel/parser" "^7.10.3" + "@babel/types" "^7.10.3" -"@babel/traverse@^7.10.1", "@babel/traverse@^7.7.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.1.tgz#bbcef3031e4152a6c0b50147f4958df54ca0dd27" - integrity sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ== +"@babel/traverse@^7.10.1", "@babel/traverse@^7.10.3", "@babel/traverse@^7.7.0": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.3.tgz#0b01731794aa7b77b214bcd96661f18281155d7e" + integrity sha512-qO6623eBFhuPm0TmmrUFMT1FulCmsSeJuVGhiLodk2raUDFhhTECLd9E9jC4LBIWziqt4wgF6KuXE4d+Jz9yug== dependencies: - "@babel/code-frame" "^7.10.1" - "@babel/generator" "^7.10.1" - "@babel/helper-function-name" "^7.10.1" + "@babel/code-frame" "^7.10.3" + "@babel/generator" "^7.10.3" + "@babel/helper-function-name" "^7.10.3" "@babel/helper-split-export-declaration" "^7.10.1" - "@babel/parser" "^7.10.1" - "@babel/types" "^7.10.1" + "@babel/parser" "^7.10.3" + "@babel/types" "^7.10.3" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.10.1", "@babel/types@^7.10.2", "@babel/types@^7.4.4", "@babel/types@^7.7.0": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.2.tgz#30283be31cad0dbf6fb00bd40641ca0ea675172d" - integrity sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng== +"@babel/types@^7.10.1", "@babel/types@^7.10.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": + version "7.10.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.3.tgz#6535e3b79fea86a6b09e012ea8528f935099de8e" + integrity sha512-nZxaJhBXBQ8HVoIcGsf9qWep3Oh3jCENK54V4mRF7qaJabVsAYdbTtmSD8WmAp1R6ytPiu5apMwSXyxB1WlaBA== dependencies: - "@babel/helper-validator-identifier" "^7.10.1" + "@babel/helper-validator-identifier" "^7.10.3" lodash "^4.17.13" to-fast-properties "^2.0.0" @@ -1144,9 +1144,9 @@ ajv-errors@^1.0.0: integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" - integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== + version "3.5.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.0.tgz#5c894537098785926d71e696114a53ce768ed773" + integrity sha512-eyoaac3btgU8eJlvh01En8OCKzRqlLe2G5jDsCr3RiE2uLGMEEB1aaGwVVpwR8M95956tGH6R+9edC++OvzaVw== ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.5.5: version "6.12.2" @@ -1168,18 +1168,11 @@ amdefine@>=0.0.4: resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= -ansi-colors@^3.0.0: +ansi-colors@^3.0.0, ansi-colors@^3.2.1: version "3.2.4" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== -ansi-escapes@^4.2.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" - integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== - dependencies: - type-fest "^0.11.0" - ansi-html@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" @@ -1400,16 +1393,16 @@ atob@^2.1.2: integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== autoprefixer@^9.6.1, autoprefixer@^9.8.0: - version "9.8.0" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.0.tgz#68e2d2bef7ba4c3a65436f662d0a56a741e56511" - integrity sha512-D96ZiIHXbDmU02dBaemyAg53ez+6F5yZmapmgKcjm35yEe1uVDYI8hGW3VYoGRaG290ZFf91YxHrR518vC0u/A== + version "9.8.2" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.2.tgz#7347396ee576b18687041bfbacd76d78e27baa56" + integrity sha512-9UwMMU8Rg7Fj0c55mbOpXrr/2WrRqoOwOlLNTyyYt+nhiyQdIBWipp5XWzt+Lge8r3DK5y+EHMc1OBf8VpZA6Q== dependencies: browserslist "^4.12.0" - caniuse-lite "^1.0.30001061" - chalk "^2.4.2" + caniuse-lite "^1.0.30001084" + kleur "^4.0.1" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^7.0.30" + postcss "^7.0.32" postcss-value-parser "^4.1.0" aws-sign2@~0.7.0: @@ -1894,10 +1887,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061: - version "1.0.30001084" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001084.tgz#00e471931eaefbeef54f46aa2203914d3c165669" - integrity sha512-ftdc5oGmhEbLUuMZ/Qp3mOpzfZLCxPYKcvGv6v2dJJ+8EdqcvZRbAGOiLmkM/PV1QGta/uwBs8/nCl6sokDW6w== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001084: + version "1.0.30001085" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001085.tgz#bed28bd51ff7425d33ee23e730c7f3b703711db6" + integrity sha512-x0YRFRE0pmOD90z+9Xk7jwO58p4feVNXP+U8kWV+Uo/HADyrgESlepzIkUqPgaXkpyceZU6siM1gsK7sHgplqA== case-sensitive-paths-webpack-plugin@^2.3.0: version "2.3.0" @@ -1943,14 +1936,6 @@ chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" @@ -1979,11 +1964,6 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -2053,18 +2033,6 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-width@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" - integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== - cliui@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" @@ -2956,9 +2924,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.413: - version "1.3.477" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.477.tgz#7e6b931d0c1a2572101a6e9a835128c50fd49323" - integrity sha512-81p6DZ/XmHDD7O0ITJMa7ESo9bSCfE+v3Fny3MIYR0y77xmhoriu2ShNOLXcPS4eowF6dkxw6d2QqxTkS3DjBg== + version "1.3.480" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.480.tgz#190ae45074578349a4c4f336fba29e76b20e9ef5" + integrity sha512-wnuUfQCBMAdzu5Xe+F4FjaRK+6ToG6WvwG72s8k/3E6b+hoGVYGiQE7JD1NhiCMcqF3+wV+c2vAnaLGRSSWVqA== elliptic@^6.0.0, elliptic@^6.5.2: version "6.5.3" @@ -3014,6 +2982,13 @@ enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1: memory-fs "^0.5.0" tapable "^1.0.0" +enquirer@^2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.5.tgz#3ab2b838df0a9d8ab9e7dff235b0e8712ef92381" + integrity sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA== + dependencies: + ansi-colors "^3.2.1" + entities@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -3133,14 +3108,14 @@ eslint-utils@^2.0.0: eslint-visitor-keys "^1.1.0" eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa" - integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ== + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== eslint@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.2.0.tgz#d41b2e47804b30dbabb093a967fb283d560082e6" - integrity sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ== + version "7.3.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.3.0.tgz#f9f1fc3dc1227985d0db88769f2bbac7b4b875d7" + integrity sha512-dJMVXwfU5PT1cj2Nv2VPPrKahKTGdX+5Dh0Q3YuKt+Y2UhdL2YbzsVaBMyG9HC0tBismlv/r1+eZqs6SMIV38Q== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -3148,6 +3123,7 @@ eslint@^7.0.0: cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" + enquirer "^2.3.5" eslint-scope "^5.1.0" eslint-utils "^2.0.0" eslint-visitor-keys "^1.2.0" @@ -3161,7 +3137,6 @@ eslint@^7.0.0: ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^7.0.0" is-glob "^4.0.0" js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" @@ -3358,15 +3333,6 @@ extend@^3.0.0, extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - extglob@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" @@ -3451,13 +3417,6 @@ figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - file-entry-cache@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" @@ -4200,7 +4159,7 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -4344,25 +4303,6 @@ ini@^1.3.4, ini@^1.3.5: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -inquirer@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.2.0.tgz#63ce99d823090de7eb420e4bb05e6f3449aa389a" - integrity sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ== - dependencies: - ansi-escapes "^4.2.1" - chalk "^3.0.0" - cli-cursor "^3.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.15" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.5.3" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - internal-ip@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" @@ -4781,9 +4721,9 @@ jquery@>=1.6.0: integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== js-base64@^2.1.8: - version "2.5.2" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.2.tgz#313b6274dda718f714d00b3330bbae6e38e90209" - integrity sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ== + version "2.6.1" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.1.tgz#c328374225d2e65569791ded73c258e2c59334c7" + integrity sha512-G5x2saUTupU9D/xBY9snJs3TxvwX8EkpLFiYlPpDt/VmMHOXprnSU1nxiTmFbijCX4BLF/cMRIfAcC5BiMYgFQ== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -4916,6 +4856,11 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +kleur@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.0.1.tgz#3d4948534b666e2578f93b6fafb62108e64f05ef" + integrity sha512-Qs6SqCLm63rd0kNVh+wO4XsWLU6kgfwwaPYsLiClWf0Tewkzsa6MvB21bespb8cz+ANS+2t3So1ge3gintzhlw== + known-css-properties@^0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.19.0.tgz#5d92b7fa16c72d971bda9b7fe295bdf61836ee5b" @@ -5368,7 +5313,7 @@ mime@^2.4.4: resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== -mimic-fn@^2.0.0, mimic-fn@^2.1.0: +mimic-fn@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== @@ -5518,11 +5463,6 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - mz@2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -5804,9 +5744,9 @@ object-hash@^2.0.3: integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg== object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== object-is@^1.0.1: version "1.1.2" @@ -5887,13 +5827,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" - integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== - dependencies: - mimic-fn "^2.1.0" - opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" @@ -5947,7 +5880,7 @@ os-locale@^3.0.0: lcid "^2.0.0" mem "^4.0.0" -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= @@ -6984,7 +6917,7 @@ postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: indexes-of "^1.0.1" uniq "^1.0.1" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.30, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: version "7.0.32" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== @@ -7573,14 +7506,6 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.3.2 dependencies: path-parse "^1.0.6" -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -7635,11 +7560,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - run-parallel@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" @@ -7652,13 +7572,6 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@^6.5.3: - version "6.5.5" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" - integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== - dependencies: - tslib "^1.9.0" - safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -8254,7 +8167,7 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== @@ -8630,7 +8543,7 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" -through@^2.3.6, through@^2.3.8: +through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -8652,13 +8565,6 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" @@ -8798,11 +8704,6 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-fest@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" - integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== - type-fest@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" From 209350de5bbe18212759db8d174a7c1ad8c7fc6b Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 18:56:42 -0500 Subject: [PATCH 071/173] scripts: remove unused danbooru-specific config. Danbooru-specific configuration now lives in https://github.com/danbooru/danbooru-ansible. --- INSTALL.debian | 14 --- script/install/app_server.sh | 101 ------------------ script/install/distribute_new_pubkey.sh | 10 -- script/install/nginx.service.d-local.conf | 2 - script/install/nginx/conf.d/common.conf | 92 ---------------- script/install/nginx/nginx.conf | 82 -------------- .../install/nginx/sites-enabled/danbooru.conf | 68 ------------ script/install/vrack-cfg.yaml | 10 -- 8 files changed, 379 deletions(-) delete mode 100644 script/install/app_server.sh delete mode 100644 script/install/distribute_new_pubkey.sh delete mode 100644 script/install/nginx.service.d-local.conf delete mode 100644 script/install/nginx/conf.d/common.conf delete mode 100644 script/install/nginx/nginx.conf delete mode 100644 script/install/nginx/sites-enabled/danbooru.conf delete mode 100644 script/install/vrack-cfg.yaml diff --git a/INSTALL.debian b/INSTALL.debian index a6f3d6736..c435d7748 100644 --- a/INSTALL.debian +++ b/INSTALL.debian @@ -32,9 +32,6 @@ if [[ -z "$HOSTNAME" ]] ; then exit 1 fi -echo -n "* Enter the VLAN IP address for this server (ex: 172.16.0.1, enter nothing to skip): " -read VLAN_IP_ADDR - # Install packages echo "* Installing packages..." @@ -52,17 +49,6 @@ apt-get -y install $LIBSSL_DEV_PKG build-essential automake libxml2-dev libxslt- apt-get -y install libpq-dev postgresql-client apt-get -y install liblcms2-dev $LIBJPEG_TURBO_DEV_PKG libexpat1-dev libgif-dev libpng-dev libexif-dev -# vrack specific stuff -if [ -n "$VLAN_IP_ADDR" ] ; then - apt-get -y install vlan - modprobe 8021q - echo "8021q" >> /etc/modules - vconfig add eno2 99 - ip addr add $VLAN_IP_ADDR/24 dev eno2.99 - ip link set up eno2.99 - curl -L -s $GITHUB_INSTALL_SCRIPTS/vrack-cfg.yaml -o /etc/netplan/01-netcfg.yaml -fi - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list curl -sSL https://deb.nodesource.com/setup_10.x | sudo -E bash - diff --git a/script/install/app_server.sh b/script/install/app_server.sh deleted file mode 100644 index a5a14d8ac..000000000 --- a/script/install/app_server.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash - -# this is a version of the install script designed to be run on -# app servers (that is, they won't install PostgreSQL server). -# -# Run: curl -L -s https://raw.githubusercontent.com/r888888888/danbooru/master/script/install/app_server.sh | sh - -export RUBY_VERSION=2.6.3 -export GITHUB_INSTALL_SCRIPTS=https://raw.githubusercontent.com/r888888888/danbooru/master/script/install -export VIPS_VERSION=8.7.0 - -if [[ "$(whoami)" != "root" ]] ; then - echo "You must run this script as root" - exit 1 -fi - -echo "* DANBOORU INSTALLATION SCRIPT" -echo "*" -echo "* This script will install all the necessary packages to run Danbooru on an" -echo "* Ubuntu server." -echo - -echo -n "* Enter the VLAN IP address for this server: " -read VLAN_IP_ADDR - -# Install packages -echo "* Installing packages..." - -apt-get update -apt-get -y install libssl-dev build-essential automake libxml2-dev libxslt-dev ncurses-dev sudo libreadline-dev flex bison ragel redis git curl libcurl4-openssl-dev sendmail-bin sendmail nginx ssh coreutils ffmpeg mkvtoolnix -apt-get -y install libpq-dev postgresql-client -apt-get -y install liblcms2-dev libjpeg-turbo8-dev libexpat1-dev libgif-dev libpng-dev libexif-dev - -# vrack specific stuff -apt-get -y install vlan -modprobe 8021q -echo "8021q" >> /etc/modules -vconfig add eno2 99 -ip addr add $VLAN_IP_ADDR/24 dev eno2.99 -ip link set up eno2.99 -curl -L -s $GITHUB_INSTALL_SCRIPTS/vrack-cfg.yaml -o /etc/netplan/01-netcfg.yaml - -curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - -echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list -curl -sSL https://deb.nodesource.com/setup_10.x | sudo -E bash - -apt-get update -apt-get -y install nodejs yarn -apt-get remove cmdtest - -# compile and install libvips (the version in apt is too old) -cd /tmp -wget -q https://github.com/libvips/libvips/releases/download/v$VIPS_VERSION/vips-$VIPS_VERSION.tar.gz -tar xzf vips-$VIPS_VERSION.tar.gz -cd vips-$VIPS_VERSION -./configure --prefix=/usr -make install -ldconfig - -# Create user account -useradd -m danbooru -chsh -s /bin/bash danbooru -usermod -G danbooru,sudo danbooru - -# Set up Postgres -git clone https://github.com/r888888888/test_parser.git /tmp/test_parser -cd /tmp/test_parser -make install - -# Install rbenv -echo "* Installing rbenv..." -cd /tmp -sudo -u danbooru git clone git://github.com/sstephenson/rbenv.git ~danbooru/.rbenv -sudo -u danbooru touch ~danbooru/.bash_profile -echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~danbooru/.bash_profile -echo 'eval "$(rbenv init -)"' >> ~danbooru/.bash_profile -sudo -u danbooru mkdir -p ~danbooru/.rbenv/plugins -sudo -u danbooru git clone git://github.com/sstephenson/ruby-build.git ~danbooru/.rbenv/plugins/ruby-build -sudo -u danbooru bash -l -c "rbenv install $RUBY_VERSION" -sudo -u danbooru bash -l -c "rbenv global $RUBY_VERSION" - -# Install gems -echo "* Installing gems..." -sudo -u danbooru bash -l -c 'gem install --no-ri --no-rdoc bundler' - -# Setup danbooru account -echo "* Enter a new password for the danbooru account" -passwd danbooru - -echo "* Setting up SSH keys for the danbooru account" -sudo -u danbooru ssh-keygen -sudo -u danbooru cat ~danbooru/.ssh/id_rsa.pub >> ~danbooru/.ssh/authorized_keys - -echo "* TODO:" -echo "on kagamihara:" -echo "script/install/distribute_new_pubkey.sh" -echo -echo "on this server:" -echo "rsync -av kagamihara:/etc/nginx/nginx.conf /etc/nginx" -echo "rsync -av kagamihara:/etc/nginx/conf.d /etc/nginx" -echo "rsync -av kagamihara:/etc/nginx/sites-enabled /etc/nginx" -echo "rsync -av kagamihara:/etc/logrotate.d /etc/logrotate.d" diff --git a/script/install/distribute_new_pubkey.sh b/script/install/distribute_new_pubkey.sh deleted file mode 100644 index 52feaa245..000000000 --- a/script/install/distribute_new_pubkey.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -HOSTS="kagamihara shima saitou" - -echo "Enter new SSH pubkey: " -read $key - -for host in $HOSTS ; do - ssh danbooru@$host echo $key >> .ssh/authorized_keys -done diff --git a/script/install/nginx.service.d-local.conf b/script/install/nginx.service.d-local.conf deleted file mode 100644 index 01c1b16bc..000000000 --- a/script/install/nginx.service.d-local.conf +++ /dev/null @@ -1,2 +0,0 @@ -[Service] -LimitNOFILE=10000 diff --git a/script/install/nginx/conf.d/common.conf b/script/install/nginx/conf.d/common.conf deleted file mode 100644 index 677a14da7..000000000 --- a/script/install/nginx/conf.d/common.conf +++ /dev/null @@ -1,92 +0,0 @@ -ssl_protocols TLSv1 TLSv1.1 TLSv1.2; -ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; -ssl_prefer_server_ciphers on; -ssl_session_timeout 4h; -ssl_session_cache shared:SSL:20m; -ssl_session_tickets off; -ssl_stapling on; -ssl_stapling_verify on; -ssl_dhparam /etc/nginx/ssl/dhparam.pem; -resolver 8.8.8.8 8.8.4.4; - -root /var/www/danbooru/current/public; -index index.html; -access_log off; -error_log /var/www/danbooru/shared/log/server.error.log; -try_files $uri/index.html $uri.html $uri @app; -client_max_body_size 35m; -error_page 503 @maintenance; -error_page 404 /404.html; -error_page 500 502 503 504 /500.html; - -location /assets { - expires max; - break; -} - -location /data/preview { - expires max; - break; -} - -location /posts/mobile { - return 404; -} - -location /users { - limit_req zone=users burst=5; - limit_req_status 429; - try_files $uri @app_server; -} - -location /posts { - limit_req zone=posts burst=20; - limit_req_status 429; - try_files $uri @app_server; -} - -location /data { - valid_referers none *.donmai.us donmai.us ~\.google\. ~\.bing\. ~\.yahoo\.; - - if ($invalid_referer) { - return 403; - } - - rewrite ^/data/sample/__.+?__(.+) /data/sample/$1 last; - rewrite ^/data/__.+?__(.+) /data/$1 last; - - expires max; - break; -} - -location /maintenance.html { - expires 10; -} - -if (-f $document_root/maintenance.html) { - return 503; -} - -if ($http_user_agent ~ (WinHttp\.WinHttpRequest\.5) ) { - return 403; -} - -location @maintenance { - rewrite ^(.*)$ /maintenance.html last; -} - -location @app_server { - proxy_pass http://app_server; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; - proxy_redirect off; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_buffer_size 128k; - proxy_buffers 4 256k; - proxy_busy_buffers_size 256k; -} - -location / { - try_files $uri @app_server; -} diff --git a/script/install/nginx/nginx.conf b/script/install/nginx/nginx.conf deleted file mode 100644 index f8d9b7e99..000000000 --- a/script/install/nginx/nginx.conf +++ /dev/null @@ -1,82 +0,0 @@ -user www-data; -worker_processes auto; -pid /var/run/nginx.pid; - -events { - use epoll; - worker_connections 10000; - multi_accept on; - accept_mutex on; -} - -http { - limit_req_zone $binary_remote_addr zone=users:10m rate=5r/s; - limit_req_zone $binary_remote_addr zone=posts:100m rate=10r/s; - - ## - # Basic Settings - ## - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 5; - types_hash_max_size 2048; - # server_tokens off; - - server_names_hash_bucket_size 128; - # server_name_in_redirect off; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ## - # Logging Settings - ## - - access_log off; - error_log /var/log/nginx/error.log; - - ## - # Gzip Settings - ## - - gzip on; - gzip_disable "msie6"; - - gzip_http_version 1.1; - gzip_vary on; - gzip_comp_level 5; - gzip_proxied any; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/rss+xml text/javascript application/atom+xml; - - # curl https://www.cloudflare.com/ips-v4 | sort - set_real_ip_from 103.21.244.0/22; - set_real_ip_from 103.22.200.0/22; - set_real_ip_from 103.31.4.0/22; - set_real_ip_from 104.16.0.0/12; - set_real_ip_from 108.162.192.0/18; - set_real_ip_from 131.0.72.0/22; - set_real_ip_from 141.101.64.0/18; - set_real_ip_from 162.158.0.0/15; - set_real_ip_from 172.64.0.0/13; - set_real_ip_from 173.245.48.0/20; - set_real_ip_from 188.114.96.0/20; - set_real_ip_from 190.93.240.0/20; - set_real_ip_from 197.234.240.0/22; - set_real_ip_from 198.41.128.0/17; - set_real_ip_from 199.27.128.0/21; - - # curl https://www.cloudflare.com/ips-v4 | sort - set_real_ip_from 2400:cb00::/32; - set_real_ip_from 2606:4700::/32; - set_real_ip_from 2803:f800::/32; - set_real_ip_from 2405:b500::/32; - set_real_ip_from 2405:8100::/32; - set_real_ip_from 2a06:98c0::/29; - set_real_ip_from 2c0f:f248::/32; - - real_ip_header CF-Connecting-IP; - - include /etc/nginx/sites-enabled/*.conf; -} diff --git a/script/install/nginx/sites-enabled/danbooru.conf b/script/install/nginx/sites-enabled/danbooru.conf deleted file mode 100644 index 30d19cda3..000000000 --- a/script/install/nginx/sites-enabled/danbooru.conf +++ /dev/null @@ -1,68 +0,0 @@ -server { - listen 443 ssl http2 default_server; - server_name danbooru.donmai.us; - - ssl_certificate /etc/nginx/ssl/danbooru.chain.pem; - ssl_certificate_key /etc/nginx/ssl/danbooru.key; - - include /etc/nginx/conf.d/common.conf; -} - -server { - listen 443 ssl http2; - server_name safebooru.donmai.us; - - ssl_certificate /etc/nginx/ssl/safebooru.chain.pem; - ssl_certificate_key /etc/nginx/ssl/safebooru.key; - - include /etc/nginx/conf.d/common.conf; -} - -server { - listen 443 ssl http2; - server_name kagamihara.donmai.us; - - ssl_certificate /etc/letsencrypt/live/kagamihara.donmai.us/fullchain.pem; # managed by Certbot - ssl_certificate_key /etc/letsencrypt/live/kagamihara.donmai.us/privkey.pem; # managed by Certbot - - include /etc/nginx/conf.d/common.conf; -} - -server { - listen 443 ssl http2; - server_name saitou.donmai.us; - - ssl_certificate /etc/letsencrypt/live/saitou.donmai.us/fullchain.pem; # managed by Certbot - ssl_certificate_key /etc/letsencrypt/live/saitou.donmai.us/privkey.pem; # managed by Certbot - - include /etc/nginx/conf.d/common.conf; -} - -server { - listen 443 ssl http2; - server_name shima.donmai.us; - - ssl_certificate /etc/letsencrypt/live/shima.donmai.us/fullchain.pem; # managed by Certbot - ssl_certificate_key /etc/letsencrypt/live/shima.donmai.us/privkey.pem; # managed by Certbot - - include /etc/nginx/conf.d/common.conf; -} - -# redirect HTTP to HTTPS. -server { - listen 80; - server_name safebooru.donmai.us danbooru.donmai.us kagamihara.donmai.us saitou.donmai.us shima.donmai.us; - return 301 https://$host$request_uri; -} - -# redirect donmai.us and www.donmai.us to danbooru.donmai.us. -server { - listen 80; - listen 443 ssl; - server_name donmai.us www.donmai.us; - return 301 https://danbooru.donmai.us$request_uri; -} - -upstream app_server { - server unix:/tmp/.unicorn.sock fail_timeout=0; -} diff --git a/script/install/vrack-cfg.yaml b/script/install/vrack-cfg.yaml deleted file mode 100644 index cbd7434a3..000000000 --- a/script/install/vrack-cfg.yaml +++ /dev/null @@ -1,10 +0,0 @@ -network: - version: 2 - renderer: networkd - ethernets: - eno2: {} - vlans: - eno2.99: - id: 99 - link: eno2 - addresses: [172.16.0.1] From b904c01d69ef9685f3dc23497f217b3806e9a13e Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 21 Jun 2020 19:01:12 -0500 Subject: [PATCH 072/173] config: remove unused config files. --- config/cable.yml | 10 ---------- config/secrets.yml | 26 -------------------------- config/storage.yml | 34 ---------------------------------- 3 files changed, 70 deletions(-) delete mode 100644 config/cable.yml delete mode 100644 config/secrets.yml delete mode 100644 config/storage.yml diff --git a/config/cable.yml b/config/cable.yml deleted file mode 100644 index feea78f65..000000000 --- a/config/cable.yml +++ /dev/null @@ -1,10 +0,0 @@ -development: - adapter: async - -test: - adapter: test - -production: - adapter: redis - url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> - channel_prefix: danbooru_production diff --git a/config/secrets.yml b/config/secrets.yml deleted file mode 100644 index a3d89cfec..000000000 --- a/config/secrets.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Your secret key is used for verifying the integrity of signed cookies. -# If you change this key, all old signed cookies will become invalid! - -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -# You can use `rake secret` to generate a secure secret key. - -# Make sure the secrets in this file are kept private -# if you're sharing your code publicly. - -development: - secret_key_base: bcc62a512b9c055c292c17742f1e65bd6d88fa37f4d01c8475103809f3ac4c03e3e98605c47d55cd8801333010ea98920a61b722770629926759624bce732539 - -test: - secret_key_base: 60e32a818af77bdfc40bca866e3b4d7b88d7ba767057ffc9e4532279358af8c67d42f2b99c084b700727303ce25b812a592b52723ebc1e3b812fd09a1f969435 - -# Do not keep production secrets in the repository, -# instead read values from the environment. -production: - secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> - -staging: - secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> - \ No newline at end of file diff --git a/config/storage.yml b/config/storage.yml deleted file mode 100644 index d32f76e8f..000000000 --- a/config/storage.yml +++ /dev/null @@ -1,34 +0,0 @@ -test: - service: Disk - root: <%= Rails.root.join("tmp/storage") %> - -local: - service: Disk - root: <%= Rails.root.join("storage") %> - -# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) -# amazon: -# service: S3 -# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> -# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> -# region: us-east-1 -# bucket: your_own_bucket - -# Remember not to checkin your GCS keyfile to a repository -# google: -# service: GCS -# project: your_project -# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> -# bucket: your_own_bucket - -# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) -# microsoft: -# service: AzureStorage -# storage_account_name: your_account_name -# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> -# container: your_container_name - -# mirror: -# service: Mirror -# primary: local -# mirrors: [ amazon, google, microsoft ] From db3407caa31e1da1c3b6cd634aac1a1cfadb4881 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Mon, 22 Jun 2020 15:32:48 -0500 Subject: [PATCH 073/173] uploads: fix uploading from source not working. ref: https://github.com/danbooru/danbooru/commit/26ad844bbe9be28eead133701f42036f7fd8bb62#r40077579. --- app/logical/sources/strategies/base.rb | 3 +- test/functional/uploads_controller_test.rb | 96 ++++++++++++++++------ 2 files changed, 71 insertions(+), 28 deletions(-) diff --git a/app/logical/sources/strategies/base.rb b/app/logical/sources/strategies/base.rb index f7e4f4b76..e2f490657 100644 --- a/app/logical/sources/strategies/base.rb +++ b/app/logical/sources/strategies/base.rb @@ -39,7 +39,7 @@ module Sources def initialize(url, referer_url = nil) @url = url.to_s @referer_url = referer_url&.to_s - @urls = [url, referer_url].select(&:present?) + @urls = [@url, @referer_url].select(&:present?) @parsed_url = Addressable::URI.heuristic_parse(url) rescue nil @parsed_referer = Addressable::URI.heuristic_parse(referer_url) rescue nil @@ -152,6 +152,7 @@ module Sources # Download the file at the given url, or at the main image url by default. def download_file!(download_url = image_url) + raise DownloadError, "Download failed: couldn't find download url for #{url}" if download_url.blank? response, file = http.download_media(download_url) raise DownloadError, "Download failed: #{download_url} returned error #{response.status}" if response.status != 200 file diff --git a/test/functional/uploads_controller_test.rb b/test/functional/uploads_controller_test.rb index e19949fca..c3b71f3fd 100644 --- a/test/functional/uploads_controller_test.rb +++ b/test/functional/uploads_controller_test.rb @@ -1,15 +1,30 @@ require 'test_helper' class UploadsControllerTest < ActionDispatch::IntegrationTest - def assert_uploaded(file_path, user, **upload_params) - file = Rack::Test::UploadedFile.new("#{Rails.root}/#{file_path}") + def self.should_upload_successfully(source) + should "upload successfully from #{source}" do + assert_successful_upload(source, user: create(:user, created_at: 1.month.ago)) + end + end - assert_difference(["Upload.count", "Post.count"]) do - post_auth uploads_path, user, params: { upload: { file: file, **upload_params }} - assert_redirected_to Upload.last + def assert_successful_upload(source_or_file_path, user: @user, **params) + if source_or_file_path =~ %r{\Ahttps?://}i + source = { source: source_or_file_path } + else + file = Rack::Test::UploadedFile.new(Rails.root.join(source_or_file_path)) + source = { file: file } end - Upload.last + assert_difference(["Upload.count"]) do + post_auth uploads_path, user, params: { upload: { tag_string: "abc", rating: "e", **source, **params }} + end + + upload = Upload.last + assert_response :redirect + assert_redirected_to upload + assert_equal("completed", upload.status) + assert_equal(Post.last, upload.post) + assert_equal(upload.post.md5, upload.md5) end context "The uploads controller" do @@ -270,32 +285,59 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest end context "uploading a file from your computer" do - should "work for a jpeg file" do - upload = assert_uploaded("test/files/test.jpg", @user, tag_string: "aaa", rating: "e", source: "aaa") + should_upload_successfully("test/files/test.jpg") + should_upload_successfully("test/files/test.png") + should_upload_successfully("test/files/test-static-32x32.gif") + should_upload_successfully("test/files/test-animated-86x52.gif") + should_upload_successfully("test/files/test-300x300.mp4") + should_upload_successfully("test/files/test-512x512.webm") + should_upload_successfully("test/files/compressed.swf") + end - assert_equal("jpg", upload.post.file_ext) - assert_equal("aaa", upload.post.source) - assert_equal(500, upload.post.image_width) - assert_equal(335, upload.post.image_height) - end + context "uploading a file from a source" do + should_upload_successfully("https://www.artstation.com/artwork/04XA4") + should_upload_successfully("https://dantewontdie.artstation.com/projects/YZK5q") + should_upload_successfully("https://cdna.artstation.com/p/assets/images/images/006/029/978/large/amama-l-z.jpg") - should "work for a webm file" do - upload = assert_uploaded("test/files/test-512x512.webm", @user, tag_string: "aaa", rating: "e", source: "aaa") + should_upload_successfully("https://www.deviantart.com/aeror404/art/Holiday-Elincia-424551484") + should_upload_successfully("https://noizave.deviantart.com/art/test-no-download-697415967") + should_upload_successfully("https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/intermediary/f/8b472d70-a0d6-41b5-9a66-c35687090acc/d23jbr4-8a06af02-70cb-46da-8a96-42a6ba73cdb4.jpg/v1/fill/w_786,h_1017,q_70,strp/silverhawks_quicksilver_by_edsfox_d23jbr4-pre.jpg") - assert_equal("webm", upload.post.file_ext) - assert_equal("aaa", upload.post.source) - assert_equal(512, upload.post.image_width) - assert_equal(512, upload.post.image_height) - end + should_upload_successfully("https://www.hentai-foundry.com/pictures/user/Afrobull/795025/kuroeda") + should_upload_successfully("https://pictures.hentai-foundry.com/a/Afrobull/795025/Afrobull-795025-kuroeda.png") - should "work for a flash file" do - upload = assert_uploaded("test/files/compressed.swf", @user, tag_string: "aaa", rating: "e", source: "aaa") + should_upload_successfully("https://yande.re/post/show/482880") + should_upload_successfully("https://files.yande.re/image/7ecfdead705d7b956b26b1d37b98d089/yande.re%20482880.jpg") - assert_equal("swf", upload.post.file_ext) - assert_equal("aaa", upload.post.source) - assert_equal(607, upload.post.image_width) - assert_equal(756, upload.post.image_height) - end + should_upload_successfully("https://konachan.com/post/show/270916") + should_upload_successfully("https://konachan.com/image/ca12cdb79a66d242e95a6f958341bf05/Konachan.com%20-%20270916.png") + + should_upload_successfully("http://lohas.nicoseiga.jp/o/910aecf08e542285862954017f8a33a8c32a8aec/1433298801/4937663") + should_upload_successfully("http://seiga.nicovideo.jp/seiga/im4937663") + should_upload_successfully("https://seiga.nicovideo.jp/image/source/9146749") + should_upload_successfully("https://seiga.nicovideo.jp/watch/mg389884") + should_upload_successfully("https://www.nicovideo.jp/watch/sm36465441") + should_upload_successfully("https://dic.nicovideo.jp/oekaki/52833.png") + + should_upload_successfully("http://nijie.info/view.php?id=213043") + should_upload_successfully("https://nijie.info/view_popup.php?id=213043") + should_upload_successfully("https://pic.nijie.net/03/nijie_picture/728995_20170505014820_0.jpg") + + should_upload_successfully("https://pawoo.net/web/statuses/1202176") + should_upload_successfully("https://img.pawoo.net/media_attachments/files/000/128/953/original/4c0a06087b03343f.png") + + should_upload_successfully("https://www.pixiv.net/en/artworks/64476642") + should_upload_successfully("https://i.pximg.net/img-original/img/2017/08/18/00/09/21/64476642_p0.jpg") + + should_upload_successfully("https://noizave.tumblr.com/post/162206271767") + should_upload_successfully("https://media.tumblr.com/3bbfcbf075ddf969c996641b264086fd/tumblr_os2buiIOt51wsfqepo1_1280.png") + + should_upload_successfully("https://twitter.com/noizave/status/875768175136317440") + should_upload_successfully("https://pbs.twimg.com/media/DCdZ_FhUIAAYKFN?format=jpg&name=medium") + should_upload_successfully("https://video.twimg.com/tweet_video/EWHWVrmVcAAp4Vw.mp4") + + should_upload_successfully("https://www.weibo.com/5501756072/J2UNKfbqV") + should_upload_successfully("https://wx1.sinaimg.cn/mw690/0060kO5aly1gezsyt5xvhj30ok0sgtc9.jpg") end end end From 95fee75d9ac5ecdf57db9e5bfb13ac642692a37f Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Mon, 22 Jun 2020 16:53:50 -0500 Subject: [PATCH 074/173] nicoseiga: fix uploads not working for certain direct image urls. Fix Nicoseiga strategy to work with certain direct image urls that we can't otherwise extract any information from. Examples: * https://dic.nicovideo.jp/oekaki/52833.png --- app/logical/sources/strategies/nico_seiga.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/logical/sources/strategies/nico_seiga.rb b/app/logical/sources/strategies/nico_seiga.rb index ee4efd3d5..e3ca777ba 100644 --- a/app/logical/sources/strategies/nico_seiga.rb +++ b/app/logical/sources/strategies/nico_seiga.rb @@ -73,8 +73,7 @@ module Sources end def image_url - return if image_urls.blank? - return url if api_client.blank? + return url if image_urls.blank? || api_client.blank? img = case url when DIRECT || CDN_DIRECT then "https://seiga.nicovideo.jp/image/source/#{image_id_from_url(url)}" From 8c6759bbd714c65f20c1e17b850c5ca6d3526e0a Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Mon, 22 Jun 2020 18:35:19 -0500 Subject: [PATCH 075/173] nicoseiga: fix login endpoint. * Update the login endpoint. The old endpoint returns 404 now. POST https://account.nicovideo.jp/api/v1/login -> POST https://account.nicovideo.jp/login/redirector?site=seiga * Let Danbooru::Http cache the login request instead of caching it manually. * Let Danbooru::Http automatically follow redirects instead of dealing with the Location header manually. --- app/logical/nico_seiga_api_client.rb | 31 +++++++------------- app/logical/sources/strategies/nico_seiga.rb | 8 ++--- test/functional/uploads_controller_test.rb | 9 ++++-- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/app/logical/nico_seiga_api_client.rb b/app/logical/nico_seiga_api_client.rb index 2b67bbde0..b9f4f00d7 100644 --- a/app/logical/nico_seiga_api_client.rb +++ b/app/logical/nico_seiga_api_client.rb @@ -4,12 +4,10 @@ class NicoSeigaApiClient attr_reader :http - # XXX temp disable following redirects. - def initialize(work_id:, type:) + def initialize(work_id:, type:, http: Danbooru::Http.new) @work_id = work_id @work_type = type - @http = Danbooru::Http.new - @http.http.default_options.features.reject! { |name, _| name == :redirector } + @http = http end def image_ids @@ -81,28 +79,19 @@ class NicoSeigaApiClient end def get(url) - cookie_header = Cache.get("nicoseiga-cookie-header") || regenerate_cookie_header - - resp = http.headers({Cookie: cookie_header}).cache(1.minute).get(url) - - if resp.headers["Location"] =~ %r{seiga\.nicovideo\.jp/login/}i - cookie_header = regenerate_cookie_header - resp = http.headers({Cookie: cookie_header}).cache(1.minute).get(url) - end - - resp - end - - def regenerate_cookie_header form = { mail_tel: Danbooru.config.nico_seiga_login, password: Danbooru.config.nico_seiga_password } - resp = http.post("https://account.nicovideo.jp/api/v1/login", form: form) - cookies = resp.cookies.map { |c| c.name + "=" + c.value } - cookies << "accept_fetish_warning=2" - Cache.put("nicoseiga-cookie-header", cookies.join(";"), 1.week) + # XXX should fail gracefully instead of raising exception + resp = http.cache(1.hour).post("https://account.nicovideo.jp/login/redirector?site=seiga", form: form) + raise RuntimeError, "NicoSeiga login failed (status=#{resp.status} url=#{url})" if resp.status != 200 + + resp = http.headers(accept_fetish_warning: 2).cache(1.minute).get(url) + #raise RuntimeError, "NicoSeiga get failed (status=#{resp.status} url=#{url})" if resp.status != 200 + + resp end memoize :api_response, :manga_api_response, :user_api_response diff --git a/app/logical/sources/strategies/nico_seiga.rb b/app/logical/sources/strategies/nico_seiga.rb index e3ca777ba..d4af63084 100644 --- a/app/logical/sources/strategies/nico_seiga.rb +++ b/app/logical/sources/strategies/nico_seiga.rb @@ -82,7 +82,7 @@ module Sources end resp = api_client.get(img) - if resp.headers["Location"] =~ %r{https?://.+/(\w+/\d+/\d+)\z}i + if resp.uri.to_s =~ %r{https?://.+/(\w+/\d+/\d+)\z}i "https://lohas.nicoseiga.jp/priv/#{$1}" else img @@ -180,12 +180,12 @@ module Sources def api_client if illust_id.present? - NicoSeigaApiClient.new(work_id: illust_id, type: "illust") + NicoSeigaApiClient.new(work_id: illust_id, type: "illust", http: http) elsif manga_id.present? - NicoSeigaApiClient.new(work_id: manga_id, type: "manga") + NicoSeigaApiClient.new(work_id: manga_id, type: "manga", http: http) elsif image_id.present? # We default to illust to attempt getting the api anyway - NicoSeigaApiClient.new(work_id: image_id, type: "illust") + NicoSeigaApiClient.new(work_id: image_id, type: "illust", http: http) end end memoize :api_client diff --git a/test/functional/uploads_controller_test.rb b/test/functional/uploads_controller_test.rb index c3b71f3fd..1ff08e658 100644 --- a/test/functional/uploads_controller_test.rb +++ b/test/functional/uploads_controller_test.rb @@ -316,8 +316,13 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest should_upload_successfully("http://seiga.nicovideo.jp/seiga/im4937663") should_upload_successfully("https://seiga.nicovideo.jp/image/source/9146749") should_upload_successfully("https://seiga.nicovideo.jp/watch/mg389884") - should_upload_successfully("https://www.nicovideo.jp/watch/sm36465441") should_upload_successfully("https://dic.nicovideo.jp/oekaki/52833.png") + should_upload_successfully("https://lohas.nicoseiga.jp/o/971eb8af9bbcde5c2e51d5ef3a2f62d6d9ff5552/1589933964/3583893") + should_upload_successfully("http://lohas.nicoseiga.jp/priv/3521156?e=1382558156&h=f2e089256abd1d453a455ec8f317a6c703e2cedf") + should_upload_successfully("http://lohas.nicoseiga.jp/priv/b80f86c0d8591b217e7513a9e175e94e00f3c7a1/1384936074/3583893") + should_upload_successfully("http://lohas.nicoseiga.jp/material/5746c5/4459092") + # XXX should_upload_successfully("https://dcdn.cdn.nimg.jp/priv/62a56a7f67d3d3746ae5712db9cac7d465f4a339/1592186183/10466669") + # XXX should_upload_successfully("https://dcdn.cdn.nimg.jp/nicoseiga/lohas/o/8ba0a9b2ea34e1ef3b5cc50785bd10cd63ec7e4a/1592187477/10466669") should_upload_successfully("http://nijie.info/view.php?id=213043") should_upload_successfully("https://nijie.info/view_popup.php?id=213043") @@ -334,7 +339,7 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest should_upload_successfully("https://twitter.com/noizave/status/875768175136317440") should_upload_successfully("https://pbs.twimg.com/media/DCdZ_FhUIAAYKFN?format=jpg&name=medium") - should_upload_successfully("https://video.twimg.com/tweet_video/EWHWVrmVcAAp4Vw.mp4") + # XXX should_upload_successfully("https://video.twimg.com/tweet_video/EWHWVrmVcAAp4Vw.mp4") should_upload_successfully("https://www.weibo.com/5501756072/J2UNKfbqV") should_upload_successfully("https://wx1.sinaimg.cn/mw690/0060kO5aly1gezsyt5xvhj30ok0sgtc9.jpg") From a6994cd4d78cc752b26403bcb402ab88d55cf498 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Mon, 22 Jun 2020 16:59:28 -0500 Subject: [PATCH 076/173] media file: fix exception on empty files. This may happen if a user uploads from a source that returns an error HTTP response with no data. --- app/logical/danbooru/http.rb | 2 -- app/logical/media_file.rb | 2 ++ test/files/test-empty.bin | 0 test/unit/media_file_test.rb | 4 ++++ 4 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 test/files/test-empty.bin diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index 6bbc7db87..17f45ec21 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -7,7 +7,6 @@ require "danbooru/http/session" module Danbooru class Http - class DownloadError < StandardError; end class FileTooLargeError < StandardError; end DEFAULT_TIMEOUT = 10 @@ -111,7 +110,6 @@ module Danbooru end def download_response(response, file: Tempfile.new("danbooru-download-", binmode: true)) - raise DownloadError, response if response.status != 200 raise FileTooLargeError, response if @max_size && response.content_length.to_i > @max_size size = 0 diff --git a/app/logical/media_file.rb b/app/logical/media_file.rb index c28bf25a9..334de8f05 100644 --- a/app/logical/media_file.rb +++ b/app/logical/media_file.rb @@ -43,6 +43,8 @@ class MediaFile else :bin end + rescue EOFError + :bin end def self.videos_enabled? diff --git a/test/files/test-empty.bin b/test/files/test-empty.bin new file mode 100644 index 000000000..e69de29bb diff --git a/test/unit/media_file_test.rb b/test/unit/media_file_test.rb index a7516198d..4c77cf5b3 100644 --- a/test/unit/media_file_test.rb +++ b/test/unit/media_file_test.rb @@ -98,6 +98,10 @@ class MediaFileTest < ActiveSupport::TestCase should "determine the correct extension for a flash file" do assert_equal(:swf, MediaFile.open("test/files/compressed.swf").file_ext) end + + should "not fail for empty files" do + assert_equal(:bin, MediaFile.open("test/files/test-empty.bin").file_ext) + end end should "determine the correct md5 for a jpeg file" do From 31802fb666c1a86987b4f57f37a7a13149f14b07 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Mon, 22 Jun 2020 22:21:17 -0500 Subject: [PATCH 077/173] nijie: fix parallel test failures. Nijie tests fail often under parallel testing. This is because every test needs to login to Nijie first, but Nijie rate-limits the login endpoint, so eventually we hit the limit and tests start failing. This is made worse by a thundering herd problem. Eight test processes try to login to Nijie at the same time, but only one succeeds, so the rest sleep and try again, but they all wakeup and try again at the same time, hitting the rate limits again. The workaround is to set the retry limit ridiculously high, higher than we would ideally like in production. Another workaround would be to serialize the Nijie tests in the test suite. This can be done with lockfiles and flock(2). This helps, but we can still hit the rate limit even under serialized execution. --- app/logical/sources/strategies/nijie.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logical/sources/strategies/nijie.rb b/app/logical/sources/strategies/nijie.rb index b2a6fcc7a..4556f274a 100644 --- a/app/logical/sources/strategies/nijie.rb +++ b/app/logical/sources/strategies/nijie.rb @@ -182,7 +182,7 @@ module Sources form = { email: Danbooru.config.nijie_login, password: Danbooru.config.nijie_password } # XXX `retriable` must come after `cache` so that retries don't return cached error responses. - response = http.cache(1.hour).use(:retriable).post("https://nijie.info/login_int.php", form: form) + response = http.cache(1.hour).use(retriable: { max_retries: 20 }).post("https://nijie.info/login_int.php", form: form) DanbooruLogger.info "Nijie login failed (#{url}, #{response.status})" if response.status != 200 return nil unless response.status == 200 From 7f5e87568a6d322d44b2d82d101913c3f1ab4898 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Mon, 22 Jun 2020 22:51:36 -0500 Subject: [PATCH 078/173] danbooru::http: raise exception on failed downloads. Restore behavior from a6994cd4d, it breaks tests when they try to the response body from a fake 599 response. --- app/logical/danbooru/http.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index 17f45ec21..ed25aa804 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -7,6 +7,7 @@ require "danbooru/http/session" module Danbooru class Http + class DownloadError < StandardError; end class FileTooLargeError < StandardError; end DEFAULT_TIMEOUT = 10 @@ -110,6 +111,7 @@ module Danbooru end def download_response(response, file: Tempfile.new("danbooru-download-", binmode: true)) + raise DownloadError, "Downloading #{response.uri} failed with code #{response.status}" if response.status != 200 raise FileTooLargeError, response if @max_size && response.content_length.to_i > @max_size size = 0 From 6ea2c934fd0197f4e13c4d3a316125afb5e7c732 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Mon, 22 Jun 2020 22:54:41 -0500 Subject: [PATCH 079/173] tests: temp skip failing danbooru::http redirect tests. Skip for now until https://github.com/postmanlabs/httpbin/issues/617 is fixed. --- test/unit/danbooru_http_test.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit/danbooru_http_test.rb b/test/unit/danbooru_http_test.rb index aa73976ea..79ba9e0d5 100644 --- a/test/unit/danbooru_http_test.rb +++ b/test/unit/danbooru_http_test.rb @@ -11,11 +11,13 @@ class DanbooruHttpTest < ActiveSupport::TestCase end should "follow redirects" do + skip "Skipping test (https://github.com/postmanlabs/httpbin/issues/617)" response = Danbooru::Http.get("https://httpbin.org/absolute-redirect/3") assert_equal(200, response.status) end should "fail if redirected too many times" do + skip "Skipping test (https://github.com/postmanlabs/httpbin/issues/617)" response = Danbooru::Http.get("https://httpbin.org/absolute-redirect/10") assert_equal(598, response.status) end @@ -134,6 +136,7 @@ class DanbooruHttpTest < ActiveSupport::TestCase end should "follow redirects when downloading files" do + skip "Skipping test (https://github.com/postmanlabs/httpbin/issues/617)" response, file = Danbooru::Http.download_media("https://httpbin.org/redirect-to?url=https://httpbin.org/bytes/1000") assert_equal(200, response.status) From 50740e302fcc21ab2e2d67253b528abf9a542346 Mon Sep 17 00:00:00 2001 From: BrokenEagle <brokeneagle98@yahoo.com> Date: Mon, 22 Jun 2020 16:07:11 +0000 Subject: [PATCH 080/173] Add option to search for wikis that don't link to a specific wiki Also add inputs on the search page for both the linked_to and the not_linked_to search parameters. Additionally, normalize the title first since autocomplete adds trailing spaces. The search query was also simplified a bit by taking advantage of Rails associations. --- app/models/wiki_page.rb | 11 ++++++++++- app/views/wiki_pages/search.html.erb | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index efef59f3a..73f7f8ced 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -53,7 +53,11 @@ class WikiPage < ApplicationRecord end def linked_to(title) - where(id: DtextLink.wiki_page.wiki_link.where(link_target: title).select(:model_id)) + where(dtext_links: DtextLink.wiki_page.wiki_link.where(link_target: normalize_title(title))) + end + + def not_linked_to(title) + where.not(dtext_links: DtextLink.wiki_page.wiki_link.where(link_target: normalize_title(title))) end def default_order @@ -82,6 +86,10 @@ class WikiPage < ApplicationRecord q = q.linked_to(params[:linked_to]) end + if params[:not_linked_to].present? + q = q.not_linked_to(params[:not_linked_to]) + end + if params[:hide_deleted].to_s.truthy? q = q.where("is_deleted = false") end @@ -146,6 +154,7 @@ class WikiPage < ApplicationRecord end def self.normalize_title(title) + return if title.blank? title.downcase.delete_prefix("~").gsub(/[[:space:]]+/, "_").gsub(/__/, "_").gsub(/\A_|_\z/, "") end diff --git a/app/views/wiki_pages/search.html.erb b/app/views/wiki_pages/search.html.erb index 4fb24c4fb..3297d9695 100644 --- a/app/views/wiki_pages/search.html.erb +++ b/app/views/wiki_pages/search.html.erb @@ -4,6 +4,8 @@ <%= f.input :title_normalize, label: "Title", hint: "Use * for wildcard searches", input_html: { "data-autocomplete": "wiki-page" } %> <%= f.input :other_names_match, label: "Other names", hint: "Use * for wildcard searches" %> <%= f.input :body_matches, label: "Body" %> + <%= f.input :linked_to, hint: "Which wikis link to the specified wiki.", input_html: { "data-autocomplete": "wiki-page" } %> + <%= f.input :not_linked_to, hint: "Which wikis do not link to the specified wiki.", input_html: { "data-autocomplete": "wiki-page" } %> <%= f.input :other_names_present, as: :select %> <%= f.input :hide_deleted, as: :select, include_blank: false %> <%= f.input :order, collection: [%w[Name title], %w[Date time], %w[Posts post_count]], include_blank: false %> From 5dbe08372bc1733ab83842561ba589da993da0ee Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 23 Jun 2020 00:49:38 -0500 Subject: [PATCH 081/173] nicoseiga: remove accept_fetish_warning. This should have been a cookie, but it doesn't matter because the strategy still works without it. --- app/logical/nico_seiga_api_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logical/nico_seiga_api_client.rb b/app/logical/nico_seiga_api_client.rb index b9f4f00d7..d471836d6 100644 --- a/app/logical/nico_seiga_api_client.rb +++ b/app/logical/nico_seiga_api_client.rb @@ -88,7 +88,7 @@ class NicoSeigaApiClient resp = http.cache(1.hour).post("https://account.nicovideo.jp/login/redirector?site=seiga", form: form) raise RuntimeError, "NicoSeiga login failed (status=#{resp.status} url=#{url})" if resp.status != 200 - resp = http.headers(accept_fetish_warning: 2).cache(1.minute).get(url) + resp = http.cache(1.minute).get(url) #raise RuntimeError, "NicoSeiga get failed (status=#{resp.status} url=#{url})" if resp.status != 200 resp From 1a879357643717acad82125d8db6d73af05a6b2b Mon Sep 17 00:00:00 2001 From: BrokenEagle <brokeneagle98@yahoo.com> Date: Tue, 23 Jun 2020 05:46:25 +0000 Subject: [PATCH 082/173] Fix the tag matches option The split function was mistakenly used instead of the match function. --- app/models/post_version.rb | 2 +- test/functional/post_versions_controller_test.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/post_version.rb b/app/models/post_version.rb index a6be23df2..a38ee2779 100644 --- a/app/models/post_version.rb +++ b/app/models/post_version.rb @@ -33,7 +33,7 @@ class PostVersion < ApplicationRecord end def tag_matches(string) - tag = string.split(/\S+/)[0] + tag = string.match(/\S+/)[0] return all if tag.nil? tag = "*#{tag}*" unless tag =~ /\*/ where_ilike(:tags, tag) diff --git a/test/functional/post_versions_controller_test.rb b/test/functional/post_versions_controller_test.rb index a57914cff..521d99adb 100644 --- a/test/functional/post_versions_controller_test.rb +++ b/test/functional/post_versions_controller_test.rb @@ -41,6 +41,13 @@ class PostVersionsControllerTest < ActionDispatch::IntegrationTest assert_response :success assert_equal @post.versions[1].id, response.parsed_body[0]["id"].to_i end + + should "list all versions for search[tag_matches]" do + get post_versions_path, as: :json, params: { search: { tag_matches: "tagme" }} + assert_response :success + assert_equal @post.versions[0].id, response.parsed_body[0]["id"].to_i + assert_equal 1, response.parsed_body.length + end end context "undo action" do From 73506bac3314d60827eb4c3d00f27f7c44eb0c0a Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 23 Jun 2020 02:37:21 -0500 Subject: [PATCH 083/173] twitter: add tests for uploading profile banners (#4520). --- test/functional/uploads_controller_test.rb | 1 + test/unit/sources/twitter_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/test/functional/uploads_controller_test.rb b/test/functional/uploads_controller_test.rb index 1ff08e658..10fab5471 100644 --- a/test/functional/uploads_controller_test.rb +++ b/test/functional/uploads_controller_test.rb @@ -339,6 +339,7 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest should_upload_successfully("https://twitter.com/noizave/status/875768175136317440") should_upload_successfully("https://pbs.twimg.com/media/DCdZ_FhUIAAYKFN?format=jpg&name=medium") + should_upload_successfully("https://pbs.twimg.com/profile_banners/1225702850002468864/1588597370/1500x500") # XXX should_upload_successfully("https://video.twimg.com/tweet_video/EWHWVrmVcAAp4Vw.mp4") should_upload_successfully("https://www.weibo.com/5501756072/J2UNKfbqV") diff --git a/test/unit/sources/twitter_test.rb b/test/unit/sources/twitter_test.rb index a3b9728f8..96e187ae2 100644 --- a/test/unit/sources/twitter_test.rb +++ b/test/unit/sources/twitter_test.rb @@ -244,6 +244,14 @@ module Sources end end + context "A profile banner image" do + should "work" do + @site = Sources::Strategies.find("https://pbs.twimg.com/profile_banners/1225702850002468864/1588597370/1500x500") + assert_equal(@site.image_url, @site.url) + assert_nothing_raised { @site.to_h } + end + end + context "A tweet containing non-normalized Unicode text" do should "be normalized to nfkc" do site = Sources::Strategies.find("https://twitter.com/aprilarcus/status/367557195186970624") From 0276792b35700bc9575dccbe8b1bd9fec0d54951 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 23 Jun 2020 02:43:12 -0500 Subject: [PATCH 084/173] BURs: add test for users voting on their own BURs (#4527) --- test/functional/forum_post_votes_controller_test.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/functional/forum_post_votes_controller_test.rb b/test/functional/forum_post_votes_controller_test.rb index 7143c038d..5d693d26b 100644 --- a/test/functional/forum_post_votes_controller_test.rb +++ b/test/functional/forum_post_votes_controller_test.rb @@ -36,6 +36,13 @@ class ForumPostVotesControllerTest < ActionDispatch::IntegrationTest assert_response 403 end end + + should "not allow creators to vote on their own BURs" do + assert_difference("ForumPostVote.count", 0) do + post_auth forum_post_votes_path(format: :js), @bulk_update_request.user, params: { forum_post_id: @forum_post.id, forum_post_vote: { score: 1 }} + assert_response 403 + end + end end context "destroy action" do From be4bdfc1368023ae7b68bdd0b08955168e332c88 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 23 Jun 2020 02:57:30 -0500 Subject: [PATCH 085/173] artists: add test for hiding deleted wikis on artist pages (#4526). --- app/views/artists/_show.html.erb | 12 +++++++----- app/views/wiki_pages/show.html.erb | 2 +- test/functional/artists_controller_test.rb | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/app/views/artists/_show.html.erb b/app/views/artists/_show.html.erb index 5352fc333..8a00e1bf6 100644 --- a/app/views/artists/_show.html.erb +++ b/app/views/artists/_show.html.erb @@ -8,12 +8,14 @@ <% if @artist.is_banned? && !policy(@artist).can_view_banned? %> <p>The artist requested removal of this page.</p> <% else %> - <% if @artist.wiki_page.present? && !@artist.wiki_page.is_deleted %> - <div class="prose"> - <%= format_text(@artist.wiki_page.body, :disable_mentions => true) %> - </div> + <% if @artist.wiki_page.present? && !@artist.wiki_page.is_deleted? %> + <div class="artist-wiki"> + <div class="prose"> + <%= format_text(@artist.wiki_page.body, :disable_mentions => true) %> + </div> - <p><%= link_to "View wiki page", @artist.wiki_page %></p> + <p><%= link_to "View wiki page", @artist.wiki_page %></p> + </div> <% end %> <%= yield %> diff --git a/app/views/wiki_pages/show.html.erb b/app/views/wiki_pages/show.html.erb index 9596e045b..bd3b0bd22 100644 --- a/app/views/wiki_pages/show.html.erb +++ b/app/views/wiki_pages/show.html.erb @@ -28,7 +28,7 @@ <%= format_text(@wiki_page.body) %> <% end %> - <% if @wiki_page.artist.present? && !@wiki_page.artist.is_deleted %> + <% if @wiki_page.artist.present? && !@wiki_page.artist.is_deleted? %> <p><%= link_to "View artist", @wiki_page.artist %></p> <% end %> diff --git a/test/functional/artists_controller_test.rb b/test/functional/artists_controller_test.rb index 2b24f3cc7..2907aa2a9 100644 --- a/test/functional/artists_controller_test.rb +++ b/test/functional/artists_controller_test.rb @@ -54,6 +54,22 @@ class ArtistsControllerTest < ActionDispatch::IntegrationTest get artist_path(@artist.id) assert_response :success end + + should "show active wikis" do + as(@user) { create(:wiki_page, title: @artist.name) } + get artist_path(@artist.id) + + assert_response :success + assert_select ".artist-wiki", count: 1 + end + + should "not show deleted wikis" do + as(@user) { create(:wiki_page, title: @artist.name, is_deleted: true) } + get artist_path(@artist.id) + + assert_response :success + assert_select ".artist-wiki", count: 0 + end end context "new action" do From 83a8468ee968f6de4a86a8434fd90ca831b62785 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 23 Jun 2020 03:09:22 -0500 Subject: [PATCH 086/173] tests: remove unnecessary rescueing of Net::OpenTimeout errors. These exceptions are no longer thrown now that we've switched from HTTParty to http.rb. Swallowing unexpected exceptions during testing was a bad practice anyway. --- test/functional/artists_controller_test.rb | 9 +- test/test_helpers/download_test_helper.rb | 2 - test/unit/artist_test.rb | 18 +-- test/unit/sources/pixiv_test.rb | 3 - test/unit/upload_service_test.rb | 171 +++++++++------------ 5 files changed, 77 insertions(+), 126 deletions(-) diff --git a/test/functional/artists_controller_test.rb b/test/functional/artists_controller_test.rb index 2907aa2a9..2ae68d78e 100644 --- a/test/functional/artists_controller_test.rb +++ b/test/functional/artists_controller_test.rb @@ -4,11 +4,8 @@ class ArtistsControllerTest < ActionDispatch::IntegrationTest def assert_artist_found(expected_artist, source_url = nil) if source_url get_auth artists_path(format: "json", search: { url_matches: source_url }), @user - if response.body =~ /Net::OpenTimeout/ - skip "Remote connection to #{source_url} failed" - return - end end + assert_response :success json = JSON.parse(response.body) assert_equal(1, json.size, "Testing URL: #{source_url}") @@ -17,10 +14,6 @@ class ArtistsControllerTest < ActionDispatch::IntegrationTest def assert_artist_not_found(source_url) get_auth artists_path(format: "json", search: { url_matches: source_url }), @user - if response.body =~ /Net::OpenTimeout/ - skip "Remote connection to #{source_url} failed" - return - end assert_response :success json = JSON.parse(response.body) diff --git a/test/test_helpers/download_test_helper.rb b/test/test_helpers/download_test_helper.rb index c3f70c75d..68e14abc6 100644 --- a/test/test_helpers/download_test_helper.rb +++ b/test/test_helpers/download_test_helper.rb @@ -3,8 +3,6 @@ module DownloadTestHelper strategy = Sources::Strategies.find(source, referer) file = strategy.download_file! assert_equal(expected_filesize, file.size, "Tested source URL: #{source}") - rescue Net::OpenTimeout - skip "Remote connection to #{source} failed" end def assert_rewritten(expected_source, test_source, test_referer = nil) diff --git a/test/unit/artist_test.rb b/test/unit/artist_test.rb index b9dc3a90c..44beba428 100644 --- a/test/unit/artist_test.rb +++ b/test/unit/artist_test.rb @@ -6,15 +6,11 @@ class ArtistTest < ActiveSupport::TestCase assert_equal(1, artists.size) assert_equal(expected_name, artists.first.name, "Testing URL: #{source_url}") - rescue Net::OpenTimeout, PixivApiClient::Error - skip "Remote connection failed for #{source_url}" end def assert_artist_not_found(source_url) artists = ArtistFinder.find_artists(source_url).to_a assert_equal(0, artists.size, "Testing URL: #{source_url}") - rescue Net::OpenTimeout - skip "Remote connection failed for #{source_url}" end context "An artist" do @@ -172,15 +168,11 @@ class ArtistTest < ActiveSupport::TestCase a2 = FactoryBot.create(:artist, :name => "subway", :url_string => "http://subway.com/x/test.jpg") a3 = FactoryBot.create(:artist, :name => "minko", :url_string => "https://minko.com/x/test.jpg") - begin - assert_artist_found("rembrandt", "http://rembrandt.com/x/test.jpg") - assert_artist_found("rembrandt", "http://rembrandt.com/x/another.jpg") - assert_artist_not_found("http://nonexistent.com/test.jpg") - assert_artist_found("minko", "https://minko.com/x/test.jpg") - assert_artist_found("minko", "http://minko.com/x/test.jpg") - rescue Net::OpenTimeout - skip "network failure" - end + assert_artist_found("rembrandt", "http://rembrandt.com/x/test.jpg") + assert_artist_found("rembrandt", "http://rembrandt.com/x/another.jpg") + assert_artist_not_found("http://nonexistent.com/test.jpg") + assert_artist_found("minko", "https://minko.com/x/test.jpg") + assert_artist_found("minko", "http://minko.com/x/test.jpg") end should "be case-insensitive to domains when finding matches by url" do diff --git a/test/unit/sources/pixiv_test.rb b/test/unit/sources/pixiv_test.rb index 323e40a00..82f03f5ba 100644 --- a/test/unit/sources/pixiv_test.rb +++ b/test/unit/sources/pixiv_test.rb @@ -15,10 +15,7 @@ module Sources def get_source(source) @site = Sources::Strategies.find(source) - @site - rescue Net::OpenTimeout - skip "Remote connection to #{source} failed" end context "in all cases" do diff --git a/test/unit/upload_service_test.rb b/test/unit/upload_service_test.rb index b15a399a5..d44ecdf44 100644 --- a/test/unit/upload_service_test.rb +++ b/test/unit/upload_service_test.rb @@ -131,12 +131,9 @@ class UploadServiceTest < ActiveSupport::TestCase end should "download the file" do - begin - @service = UploadService::Preprocessor.new(source: @source, referer_url: @ref) - @upload = @service.start! - rescue Net::OpenTimeout - skip "network failure" - end + @service = UploadService::Preprocessor.new(source: @source, referer_url: @ref) + @upload = @service.start! + assert_equal("preprocessed", @upload.status) assert_equal(294591, @upload.file_size) assert_equal("jpg", @upload.file_ext) @@ -155,11 +152,8 @@ class UploadServiceTest < ActiveSupport::TestCase skip unless MediaFile::Ugoira.videos_enabled? @service = UploadService::Preprocessor.new(source: @source) - begin - @upload = @service.start! - rescue Net::OpenTimeout - skip "network problems" - end + @upload = @service.start! + assert_equal("preprocessed", @upload.status) assert_equal(2804, @upload.file_size) assert_equal("zip", @upload.file_ext) @@ -176,11 +170,8 @@ class UploadServiceTest < ActiveSupport::TestCase should "download the file" do @service = UploadService::Preprocessor.new(source: @source) - begin - @upload = @service.start! - rescue Net::OpenTimeout - skip "network problems" - end + @upload = @service.start! + assert_equal("preprocessed", @upload.status) assert_equal(181309, @upload.file_size) assert_equal("jpg", @upload.file_ext) @@ -512,36 +503,28 @@ class UploadServiceTest < ActiveSupport::TestCase context "a post with a pixiv html source" do should "replace with the full size image" do - begin - as(@user) do - @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") - end - - assert_equal(80, @post.image_width) - assert_equal(82, @post.image_height) - assert_equal(16275, @post.file_size) - assert_equal("png", @post.file_ext) - assert_equal("4ceadc314938bc27f3574053a3e1459a", @post.md5) - assert_equal("4ceadc314938bc27f3574053a3e1459a", Digest::MD5.file(@post.file).hexdigest) - assert_equal("https://i.pximg.net/img-original/img/2017/04/04/08/54/15/62247350_p0.png", @post.replacements.last.replacement_url) - assert_equal("https://i.pximg.net/img-original/img/2017/04/04/08/54/15/62247350_p0.png", @post.source) - rescue Net::OpenTimeout - skip "Remote connection to Pixiv failed" + as(@user) do + @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") end + + assert_equal(80, @post.image_width) + assert_equal(82, @post.image_height) + assert_equal(16275, @post.file_size) + assert_equal("png", @post.file_ext) + assert_equal("4ceadc314938bc27f3574053a3e1459a", @post.md5) + assert_equal("4ceadc314938bc27f3574053a3e1459a", Digest::MD5.file(@post.file).hexdigest) + assert_equal("https://i.pximg.net/img-original/img/2017/04/04/08/54/15/62247350_p0.png", @post.replacements.last.replacement_url) + assert_equal("https://i.pximg.net/img-original/img/2017/04/04/08/54/15/62247350_p0.png", @post.source) end should "delete the old files after thirty days" do - begin - @post.unstub(:queue_delete_files) - FileUtils.expects(:rm_f).times(3) + @post.unstub(:queue_delete_files) + FileUtils.expects(:rm_f).times(3) - as(@user) { @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") } + as(@user) { @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") } - travel_to((PostReplacement::DELETION_GRACE_PERIOD + 1).days.from_now) do - perform_enqueued_jobs - end - rescue Net::OpenTimeout - skip "Remote connection to Pixiv failed" + travel_to((PostReplacement::DELETION_GRACE_PERIOD + 1).days.from_now) do + perform_enqueued_jobs end end end @@ -568,34 +551,30 @@ class UploadServiceTest < ActiveSupport::TestCase context "a post that is replaced to another file then replaced back to the original file" do should "not delete the original files" do - begin - skip unless MediaFile::Ugoira.videos_enabled? - @post.unstub(:queue_delete_files) + skip unless MediaFile::Ugoira.videos_enabled? + @post.unstub(:queue_delete_files) - # this is called thrice to delete the file for 62247364 - FileUtils.expects(:rm_f).times(3) + # this is called thrice to delete the file for 62247364 + FileUtils.expects(:rm_f).times(3) - as(@user) do - @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") - @post.reload - @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364") - @post.reload - Upload.destroy_all - @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") - end - - assert_nothing_raised { @post.file(:original) } - assert_nothing_raised { @post.file(:preview) } - - assert_enqueued_jobs 3, only: DeletePostFilesJob - travel PostReplacement::DELETION_GRACE_PERIOD + 1.day - assert_raise(Post::DeletionError) { perform_enqueued_jobs } - - assert_nothing_raised { @post.file(:original) } - assert_nothing_raised { @post.file(:preview) } - rescue Net::OpenTimeout - skip "Remote connection to Pixiv failed" + as(@user) do + @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") + @post.reload + @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364") + @post.reload + Upload.destroy_all + @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") end + + assert_nothing_raised { @post.file(:original) } + assert_nothing_raised { @post.file(:preview) } + + assert_enqueued_jobs 3, only: DeletePostFilesJob + travel PostReplacement::DELETION_GRACE_PERIOD + 1.day + assert_raise(Post::DeletionError) { perform_enqueued_jobs } + + assert_nothing_raised { @post.file(:original) } + assert_nothing_raised { @post.file(:preview) } end end @@ -609,39 +588,35 @@ class UploadServiceTest < ActiveSupport::TestCase should "not delete the still active files" do # swap the images between @post1 and @post2. - begin - as(@user) do - skip unless MediaFile::Ugoira.videos_enabled? + as(@user) do + skip unless MediaFile::Ugoira.videos_enabled? - @post1.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") - @post2.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364") - assert_equal("4ceadc314938bc27f3574053a3e1459a", @post1.md5) - assert_equal("cad1da177ef309bf40a117c17b8eecf5", @post2.md5) + @post1.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") + @post2.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364") + assert_equal("4ceadc314938bc27f3574053a3e1459a", @post1.md5) + assert_equal("cad1da177ef309bf40a117c17b8eecf5", @post2.md5) - @post2.reload - @post2.replace!(replacement_url: "https://cdn.donmai.us/original/d3/4e/d34e4cf0a437a5d65f8e82b7bcd02606.jpg") - assert_equal("d34e4cf0a437a5d65f8e82b7bcd02606", @post2.md5) - Upload.destroy_all - @post1.reload - @post2.reload + @post2.reload + @post2.replace!(replacement_url: "https://cdn.donmai.us/original/d3/4e/d34e4cf0a437a5d65f8e82b7bcd02606.jpg") + assert_equal("d34e4cf0a437a5d65f8e82b7bcd02606", @post2.md5) + Upload.destroy_all + @post1.reload + @post2.reload - @post1.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364") - @post2.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") - assert_equal("cad1da177ef309bf40a117c17b8eecf5", @post1.md5) - assert_equal("4ceadc314938bc27f3574053a3e1459a", @post2.md5) - end - - travel_to (PostReplacement::DELETION_GRACE_PERIOD + 1).days.from_now do - assert_raise(Post::DeletionError) do - perform_enqueued_jobs - end - end - - assert_nothing_raised { @post1.file(:original) } - assert_nothing_raised { @post2.file(:original) } - rescue Net::OpenTimeout - skip "Remote connection to Pixiv failed" + @post1.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364") + @post2.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") + assert_equal("cad1da177ef309bf40a117c17b8eecf5", @post1.md5) + assert_equal("4ceadc314938bc27f3574053a3e1459a", @post2.md5) end + + travel_to (PostReplacement::DELETION_GRACE_PERIOD + 1).days.from_now do + assert_raise(Post::DeletionError) do + perform_enqueued_jobs + end + end + + assert_nothing_raised { @post1.file(:original) } + assert_nothing_raised { @post2.file(:original) } end end @@ -885,12 +860,8 @@ class UploadServiceTest < ActiveSupport::TestCase end should "record the canonical source" do - begin - post = subject.new({}).create_post_from_upload(@upload) - assert_equal(@source, post.source) - rescue Net::OpenTimeout - skip "network failure" - end + post = subject.new({}).create_post_from_upload(@upload) + assert_equal(@source, post.source) end end From 2b969646bc2586fdcc71ecaa3264c91e7332454d Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 23 Jun 2020 23:32:14 -0500 Subject: [PATCH 087/173] tests: add `respond_to_search` test helper. Add a `respond_to_search` test helper for concisely testing that a controller's index action correctly responds to a search. Usage: # Tests that `/tags.json?search[name]=touhou` returns the `touhou` tag. setup { @touhou = create(:tag, name: "touhou") } should respond_to_search(name: "touhou").with { @touhou } --- test/test_helper.rb | 2 ++ test/test_helpers/controller_helper.rb | 47 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 test/test_helpers/controller_helper.rb diff --git a/test/test_helper.rb b/test/test_helper.rb index 1d81bdb12..892046f7e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -61,6 +61,8 @@ class ActiveSupport::TestCase end class ActionDispatch::IntegrationTest + extend ControllerHelper + register_encoder :xml, response_parser: ->(body) { Nokogiri.XML(body) } def method_authenticated(method_name, url, user, **options) diff --git a/test/test_helpers/controller_helper.rb b/test/test_helpers/controller_helper.rb new file mode 100644 index 000000000..716bfaa97 --- /dev/null +++ b/test/test_helpers/controller_helper.rb @@ -0,0 +1,47 @@ +module ControllerHelper + # A custom Shoulda matcher that tests that a controller's index endpoint + # responds to a search correctly. See https://thoughtbot.com/blog/shoulda-matchers. + # + # Usage: + # + # # Tests that `/tags.json?search[name]=touhou` returns the `touhou` tag. + # subject { TagsController } + # setup { @touhou = create(:tag, name: "touhou") } + # should respond_to_search(name: "touhou").with { @touhou } + # + def respond_to_search(search_params) + RespondToSearchMatcher.new(search_params) + end + + class RespondToSearchMatcher < Struct.new(:params) + def description + "should respond to a search for #{params}" + end + + def matches?(subject, &block) + search_params = { search: params } + expected_items = @test_case.instance_eval(&@expected) + + @test_case.instance_eval do + # calls e.g. "wiki_pages_path" if we're in WikiPagesControllerTest. + index_url = send("#{subject.controller_path}_path") + get index_url, as: :json, params: search_params + + expected_ids = Array(expected_items).map(&:id) + responded_ids = response.parsed_body.map { |item| item["id"] } + + assert_response :success + assert_equal(expected_ids, responded_ids) + end + end + + def with(&block) + @expected = block + self + end + + def in_context(test_case) + @test_case = test_case + end + end +end From 5f3c41416ea947a47b13c8029368aaf2b5cda2bc Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 23 Jun 2020 23:36:16 -0500 Subject: [PATCH 088/173] tests: add more wiki page controller search tests. --- test/functional/wiki_pages_controller_test.rb | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/test/functional/wiki_pages_controller_test.rb b/test/functional/wiki_pages_controller_test.rb index 119483201..2ea1619c6 100644 --- a/test/functional/wiki_pages_controller_test.rb +++ b/test/functional/wiki_pages_controller_test.rb @@ -10,8 +10,11 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest context "index action" do setup do as(@user) do - @wiki_page_abc = create(:wiki_page, :title => "abc") - @wiki_page_def = create(:wiki_page, :title => "def") + @tagme = create(:wiki_page, title: "tagme") + @deleted = create(:wiki_page, title: "deleted", is_deleted: true) + @vocaloid = create(:wiki_page, title: "vocaloid") + @miku = create(:wiki_page, title: "hatsune_miku", other_names: ["初音ミク"], body: "miku is a [[vocaloid]]") + create(:tag, name: "hatsune_miku", category: Tag.categories.character) end end @@ -20,22 +23,24 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest assert_response :success end - should "list all wiki_pages (with search)" do - get wiki_pages_path, params: {:search => {:title => "abc"}} - assert_response :success - assert_select "tr td:first-child", text: "abc" - end - - should "list wiki_pages without tags with order=post_count" do - get wiki_pages_path, params: {:search => {:title => "abc", :order => "post_count"}} - assert_response :success - assert_select "tr td:first-child", text: "abc" - end - should "redirect the legacy title param to the show page" do - get wiki_pages_path(title: "abc") - assert_redirected_to wiki_pages_path(search: { title_normalize: "abc" }, redirect: true) + get wiki_pages_path(title: "tagme") + assert_redirected_to wiki_pages_path(search: { title_normalize: "tagme" }, redirect: true) end + + should respond_to_search(title: "tagme").with { @tagme } + should respond_to_search(title: "tagme", order: "post_count").with { @tagme } + should respond_to_search(title_normalize: "TAGME ").with { @tagme } + + should respond_to_search(tag: { category: Tag.categories.character }).with { @miku } + should respond_to_search(hide_deleted: "true").with { [@miku, @vocaloid, @tagme] } + should respond_to_search(linked_to: "vocaloid").with { @miku } + should respond_to_search(not_linked_to: "vocaloid").with { [@vocaloid, @deleted, @tagme] } + + should respond_to_search(other_names_match: "初音ミク").with { @miku } + should respond_to_search(other_names_match: "初*").with { @miku } + should respond_to_search(other_names_present: "true").with { @miku } + should respond_to_search(other_names_present: "false").with { [@vocaloid, @deleted, @tagme] } end context "search action" do From bb765f55d5ad89e1f87821147e7fac7b2fdc21ea Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 23 Jun 2020 23:37:56 -0500 Subject: [PATCH 089/173] Eliminate misc dead code. --- app/logical/current_user.rb | 9 --------- app/logical/tag_relationship_retirement_service.rb | 8 -------- app/models/moderation_report.rb | 2 +- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/app/logical/current_user.rb b/app/logical/current_user.rb index 292bf4a72..4c8d80349 100644 --- a/app/logical/current_user.rb +++ b/app/logical/current_user.rb @@ -24,15 +24,6 @@ class CurrentUser scoped(user, &block) end - def self.as_system(&block) - if block_given? - scoped(::User.system, "127.0.0.1", &block) - else - self.user = User.system - self.ip_addr = "127.0.0.1" - end - end - def self.user RequestStore[:current_user] end diff --git a/app/logical/tag_relationship_retirement_service.rb b/app/logical/tag_relationship_retirement_service.rb index 0f11ee931..0aa79cc6b 100644 --- a/app/logical/tag_relationship_retirement_service.rb +++ b/app/logical/tag_relationship_retirement_service.rb @@ -11,14 +11,6 @@ module TagRelationshipRetirementService "This topic deals with tag relationships created two or more years ago that have not been used since. They will be retired. This topic will be updated as an automated system retires expired relationships." end - def dry_run - [TagAlias, TagImplication].each do |model| - each_candidate(model) do |rel| - puts "#{rel.relationship} #{rel.antecedent_name} -> #{rel.consequent_name} retired" - end - end - end - def forum_topic topic = ForumTopic.where(title: forum_topic_title).first if topic.nil? diff --git a/app/models/moderation_report.rb b/app/models/moderation_report.rb index 17083458b..b45a6d86c 100644 --- a/app/models/moderation_report.rb +++ b/app/models/moderation_report.rb @@ -34,7 +34,7 @@ class ModerationReport < ApplicationRecord def forum_topic topic = ForumTopic.find_by_title(forum_topic_title) if topic.nil? - CurrentUser.as_system do + CurrentUser.scoped(User.system) do topic = ForumTopic.create!(creator: User.system, title: forum_topic_title, category_id: 0, min_level: User::Levels::MODERATOR) forum_post = ForumPost.create!(creator: User.system, body: forum_topic_body, topic: topic) end From 85f58bf2f6d46b0f07934eae228223b270079edc Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 24 Jun 2020 00:25:45 -0500 Subject: [PATCH 090/173] newgrounds: fix style nitpicks. --- app/logical/sources/strategies/newgrounds.rb | 15 +++++++-------- test/functional/uploads_controller_test.rb | 4 ++++ test/unit/sources/newgrounds_test.rb | 12 ++++++------ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/logical/sources/strategies/newgrounds.rb b/app/logical/sources/strategies/newgrounds.rb index 69d5d737a..0fefba41d 100644 --- a/app/logical/sources/strategies/newgrounds.rb +++ b/app/logical/sources/strategies/newgrounds.rb @@ -7,19 +7,18 @@ # * https://www.newgrounds.com/art/view/puddbytes/costanza-at-bat # * https://www.newgrounds.com/art/view/natthelich/fire-emblem-marth-plus-progress-pic (multiple) # -# # Profile URLs # * https://natthelich.newgrounds.com/ module Sources module Strategies class Newgrounds < Base - IMAGE_URL = %r{https?://art\.ngfiles\.com/images/\d+/\d+_(?<user_name>[0-9a-z-]+)_(?<illust_title>[0-9a-z-]+)\.\w+}i - COMMENT_URL = %r{https?://art\.ngfiles\.com/comments/\d+/\w+\.\w+}i + IMAGE_URL = %r{\Ahttps?://art\.ngfiles\.com/images/\d+/\d+_(?<user_name>[0-9a-z-]+)_(?<illust_title>[0-9a-z-]+)\.\w+}i + COMMENT_URL = %r{\Ahttps?://art\.ngfiles\.com/comments/\d+/\w+\.\w+}i - PAGE_URL = %r{https?://(?:www\.)?newgrounds\.com/art/view/(?<user_name>[0-9a-z-]+)/(?<illust_title>[0-9a-z-]+)(?:\?.*)?}i + PAGE_URL = %r{\Ahttps?://(?:www\.)?newgrounds\.com/art/view/(?<user_name>[0-9a-z-]+)/(?<illust_title>[0-9a-z-]+)(?:\?.*)?}i - PROFILE_URL = %r{https?://(?<artist_name>(?!www)[0-9a-z-]+)\.newgrounds\.com(?:/.*)?}i + PROFILE_URL = %r{\Ahttps?://(?<artist_name>(?!www)[0-9a-z-]+)\.newgrounds\.com(?:/.*)?}i def domains ["newgrounds.com", "ngfiles.com"] @@ -50,11 +49,11 @@ module Sources def page return nil if page_url.blank? - doc = Danbooru::Http.cache(1.minute).get(page_url) - return if doc.code == 404 + response = Danbooru::Http.cache(1.minute).get(page_url) + return nil if response.status == 404 - Nokogiri::HTML(doc.body) + response.parse end memoize :page diff --git a/test/functional/uploads_controller_test.rb b/test/functional/uploads_controller_test.rb index 10fab5471..1464db180 100644 --- a/test/functional/uploads_controller_test.rb +++ b/test/functional/uploads_controller_test.rb @@ -344,6 +344,10 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest should_upload_successfully("https://www.weibo.com/5501756072/J2UNKfbqV") should_upload_successfully("https://wx1.sinaimg.cn/mw690/0060kO5aly1gezsyt5xvhj30ok0sgtc9.jpg") + + should_upload_successfully("https://art.ngfiles.com/images/1254000/1254722_natthelich_pandora.jpg") + should_upload_successfully("https://art.ngfiles.com/comments/57000/iu_57615_7115981.jpg") + should_upload_successfully("https://www.newgrounds.com/art/view/puddbytes/costanza-at-bat") end end end diff --git a/test/unit/sources/newgrounds_test.rb b/test/unit/sources/newgrounds_test.rb index 8c7c57fcb..f4195de37 100644 --- a/test/unit/sources/newgrounds_test.rb +++ b/test/unit/sources/newgrounds_test.rb @@ -60,9 +60,9 @@ module Sources end should "find the right artist" do - artist_1 = FactoryBot.create(:artist, name: "natthelich1", url_string: "https://natthelich.newgrounds.com/art") - artist_2 = FactoryBot.create(:artist, name: "natthelich2", url_string: "https://www.newgrounds.com/art/view/natthelich/fire-emblem-marth-plus-progress-pic") - artist_3 = FactoryBot.create(:artist, name: "bad_artist", url_string: "https://www.newgrounds.com/art") + artist_1 = create(:artist, name: "natthelich1", url_string: "https://natthelich.newgrounds.com/art") + artist_2 = create(:artist, name: "natthelich2", url_string: "https://www.newgrounds.com/art/view/natthelich/fire-emblem-marth-plus-progress-pic") + artist_3 = create(:artist, name: "bad_artist", url_string: "https://www.newgrounds.com/art") assert_equal([artist_1, artist_2], @image_1.artists) assert_equal([artist_1, artist_2], @image_2.artists) @@ -75,13 +75,13 @@ module Sources context "A deleted or not existing picture" do setup do @fake_1 = Sources::Strategies.find("https://www.newgrounds.com/art/view/ThisUser/DoesNotExist") - @artist_1 = FactoryBot.create(:artist, name: "thisuser", url_string: "https://thisuser.newgrounds.com") + @artist_1 = create(:artist, name: "thisuser", url_string: "https://thisuser.newgrounds.com") @fake_2 = Sources::Strategies.find("https://www.newgrounds.com/art/view/natthelich/nopicture") - @artist_2 = FactoryBot.create(:artist, name: "natthelich", url_string: "https://natthelich.newgrounds.com") + @artist_2 = create(:artist, name: "natthelich", url_string: "https://natthelich.newgrounds.com") @fake_3 = Sources::Strategies.find("https://www.newgrounds.com/art/view/theolebrave/sensitive-pochaco") - @artist_3 = FactoryBot.create(:artist, name: "taffytoad", url_string: "https://taffytoad.newgrounds.com") + @artist_3 = create(:artist, name: "taffytoad", url_string: "https://taffytoad.newgrounds.com") end should "still find the artist name" do From 1d369f45745e43933454d6eaec58c90365d38dbd Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 24 Jun 2020 01:55:44 -0500 Subject: [PATCH 091/173] ci: notify when a PR has a merge conflict. Adds a "merge conflict" label and posts a comment when a PR has a merge conflict. See https://github.com/Marr11317/ConflictAdviser#limitations. --- .github/workflows/test.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 592dac48b..03a6c2d3f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,6 +10,16 @@ on: - master jobs: + # https://github.com/Marr11317/ConflictAdviser + notify-merge-conflicts: + runs-on: ubuntu-latest + steps: + - uses: Marr11317/ConflictAdviser@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + conflict_label: "merge conflict" + comment: 'Rebase needed' + test: runs-on: ubuntu-latest container: ubuntu:20.04 From 8eac82a971409a1eb413ff741deeba8702ea2d0e Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 24 Jun 2020 02:32:07 -0500 Subject: [PATCH 092/173] pixiv: fix regression with new user profile urls. * Update tests to use new Pixiv profile urls. * Fix issue with artist finder not working when given direct image or html page urls. --- app/models/artist_url.rb | 7 ++++--- test/unit/artist_test.rb | 4 ++-- test/unit/sources/moebooru_test.rb | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/models/artist_url.rb b/app/models/artist_url.rb index b56c8ead8..34c5e73f1 100644 --- a/app/models/artist_url.rb +++ b/app/models/artist_url.rb @@ -27,13 +27,14 @@ class ArtistUrl < ApplicationRecord # url = url.sub(%r!^(http://seiga.nicovideo.jp/user/illust/\d+)\?.+!, '\1/') url = url.sub(%r!^http://pictures.hentai-foundry.com//!, "http://pictures.hentai-foundry.com/") - # XXX should be handled by pixiv strategy. - url = url.sub(%r!\Ahttps?://www\.pixiv\.net/(?:en/)?users/(\d+)\z!i, 'https://www.pixiv.net/member.php?id=\1') - # 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 + "/" diff --git a/test/unit/artist_test.rb b/test/unit/artist_test.rb index 44beba428..0e55f6e6f 100644 --- a/test/unit/artist_test.rb +++ b/test/unit/artist_test.rb @@ -529,7 +529,7 @@ class ArtistTest < ActiveSupport::TestCase assert_equal("niceandcool", artist.name) assert_equal("nice_and_cool", artist.other_names_string) - assert_includes(artist.urls.map(&:url), "https://www.pixiv.net/member.php?id=906442") + assert_includes(artist.urls.map(&:url), "https://www.pixiv.net/users/906442") assert_includes(artist.urls.map(&:url), "https://www.pixiv.net/stacc/niceandcool") end @@ -540,7 +540,7 @@ class ArtistTest < ActiveSupport::TestCase assert_equal("test_artist", artist.name) assert_equal("nice_and_cool niceandcool", artist.other_names_string) - assert_includes(artist.urls.map(&:url), "https://www.pixiv.net/member.php?id=906442") + assert_includes(artist.urls.map(&:url), "https://www.pixiv.net/users/906442") assert_includes(artist.urls.map(&:url), "https://www.pixiv.net/stacc/niceandcool") end end diff --git a/test/unit/sources/moebooru_test.rb b/test/unit/sources/moebooru_test.rb index 423143ae2..08d3f0558 100644 --- a/test/unit/sources/moebooru_test.rb +++ b/test/unit/sources/moebooru_test.rb @@ -103,7 +103,7 @@ module Sources girls_frontline hara_shoutarou hoodie long_hair pantyhose scar skirt twintails ump-45_(girls_frontline) ump-9_(girls_frontline) ] - @profile_url = "https://www.pixiv.net/member.php?id=22528152" + @profile_url = "https://www.pixiv.net/users/22528152" @data = { site_name: "konachan.com", preview_url: @prev, image_url: @full, page_url: @page, size: @size, tags: @tags, profile_url: @profile_url } assert_source_data_equals(@samp, **@data) From 4074cc99f988c17e42ba4eadb3e589ce0be37081 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 24 Jun 2020 02:54:30 -0500 Subject: [PATCH 093/173] uploads: fix incorrect remote sizes on pixiv uploads. Bug: the uploads page showed a remote size of 146 bytes for Pixiv uploads. Cause: we didn't spoof the Referer header when making the HEAD request for the image, causing Pixiv to return a 403 error. Also fix the case where the Content-Length header is absent. --- app/logical/sources/strategies/base.rb | 11 +++++++---- app/logical/upload_service/controller_helper.rb | 2 +- app/views/uploads/_image.html.erb | 2 +- test/unit/sources/moebooru_test.rb | 2 +- test/unit/sources/pixiv_test.rb | 4 ++++ 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/logical/sources/strategies/base.rb b/app/logical/sources/strategies/base.rb index e2f490657..718e3f73e 100644 --- a/app/logical/sources/strategies/base.rb +++ b/app/logical/sources/strategies/base.rb @@ -145,10 +145,13 @@ module Sources end # Returns the size of the image resource without actually downloading the file. - def size - http.head(image_url).content_length.to_i + def remote_size + response = http.head(image_url) + return nil unless response.status == 200 && response.content_length.present? + + response.content_length.to_i end - memoize :size + memoize :remote_size # Download the file at the given url, or at the main image url by default. def download_file!(download_url = image_url) @@ -159,7 +162,7 @@ module Sources end def http - Danbooru::Http.public_only.timeout(30).max_size(Danbooru.config.max_file_size) + Danbooru::Http.headers(headers).public_only.timeout(30).max_size(Danbooru.config.max_file_size) end memoize :http diff --git a/app/logical/upload_service/controller_helper.rb b/app/logical/upload_service/controller_helper.rb index 9eeb6d442..378ca07f3 100644 --- a/app/logical/upload_service/controller_helper.rb +++ b/app/logical/upload_service/controller_helper.rb @@ -8,7 +8,7 @@ class UploadService UploadPreprocessorDelayedStartJob.perform_later(url, ref, CurrentUser.user) strategy = Sources::Strategies.find(url, ref) - remote_size = strategy.size + remote_size = strategy.remote_size return [upload, remote_size] end diff --git a/app/views/uploads/_image.html.erb b/app/views/uploads/_image.html.erb index 7b0a2208e..4ef2a8d6d 100644 --- a/app/views/uploads/_image.html.erb +++ b/app/views/uploads/_image.html.erb @@ -1,7 +1,7 @@ <% if params[:url] %> <p id="upload-image-metadata"> <strong>Size</strong> - <% if @remote_size %> + <% if @remote_size.present? %> <span id="upload-image-metadata-filesize"><%= number_to_human_size(@remote_size) %></span> <% end %> <span id="upload-image-metadata-resolution"></span> diff --git a/test/unit/sources/moebooru_test.rb b/test/unit/sources/moebooru_test.rb index 08d3f0558..868375295 100644 --- a/test/unit/sources/moebooru_test.rb +++ b/test/unit/sources/moebooru_test.rb @@ -14,7 +14,7 @@ module Sources assert_equal(page_url, site.page_url) if page_url.present? assert_equal(tags.sort, site.tags.map(&:first).sort) assert_equal(profile_url.to_s, site.profile_url.to_s) - assert_equal(size, site.size) + assert_equal(size, site.remote_size) assert_nothing_raised { site.to_h } end diff --git a/test/unit/sources/pixiv_test.rb b/test/unit/sources/pixiv_test.rb index 4660fde8a..e58cd029c 100644 --- a/test/unit/sources/pixiv_test.rb +++ b/test/unit/sources/pixiv_test.rb @@ -97,6 +97,10 @@ module Sources assert_equal("uroobnad", @site.artist_name) end + should "get the remote image size" do + assert_equal(854_653, @site.remote_size) + end + should "get the full size image url" do assert_equal("https://i.pximg.net/img-original/img/2017/11/21/05/12/37/65981735_p0.jpg", @site.image_url) end From b09350c0dc3a0ab1487ffa3298811e07dc6925da Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 24 Jun 2020 18:39:55 -0500 Subject: [PATCH 094/173] tests: add more Tag.search tests. Improve tag model test coverage. --- test/factories/tag.rb | 2 +- test/functional/tags_controller_test.rb | 40 ++++++++++++++++--------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/test/factories/tag.rb b/test/factories/tag.rb index 8a6b31d33..a2eb396e7 100644 --- a/test/factories/tag.rb +++ b/test/factories/tag.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory(:tag) do name {"#{FFaker::Name.first_name.downcase}#{rand(1000)}"} - post_count {0} + post_count { 100 } category {Tag.categories.general} factory(:artist_tag) do diff --git a/test/functional/tags_controller_test.rb b/test/functional/tags_controller_test.rb index 83b01a1a9..5f0ab23a3 100644 --- a/test/functional/tags_controller_test.rb +++ b/test/functional/tags_controller_test.rb @@ -20,26 +20,38 @@ class TagsControllerTest < ActionDispatch::IntegrationTest assert_response :success end - context "with search parameters" do - should "render" do - get tags_path, params: {:search => {:name_matches => "touhou"}} - assert_response :success - end - - should "work for search[fuzzy_name_matches]" do - get tags_path, as: :json, params: { search: { fuzzy_name_matches: "touhuo", order: "similarity" }} - - assert_response :success - assert_equal "touhou", response.parsed_body.first["name"] - end - end - context "with blank search parameters" do should "strip the blank parameters with a redirect" do get tags_path, params: { search: { name: "touhou", category: "" } } assert_redirected_to tags_path(search: { name: "touhou" }) end end + + context "searching" do + setup do + as(@user) do + @miku = create(:tag, name: "hatsune_miku", category: Tag.categories.character) + @wokada = create(:tag, name: "wokada", category: Tag.categories.artist) + @vocaloid = create(:tag, name: "vocaloid", category: Tag.categories.copyright) + @empty = create(:tag, name: "empty", post_count: 0) + + create(:tag_alias, antecedent_name: "miku", consequent_name: "hatsune_miku") + create(:wiki_page, title: "hatsune_miku") + create(:artist, name: "wokada") + end + end + + should respond_to_search(name_matches: "hatsune_miku").with { @miku } + should respond_to_search(name_normalize: "HATSUNE_MIKU ").with { @miku } + should respond_to_search(name_or_alias_matches: "miku").with { @miku } + should respond_to_search(fuzzy_name_matches: "miku_hatsune", order: "similarity").with { @miku } + should respond_to_search(name: "empty", hide_empty: "true").with { [] } + should respond_to_search(name: "empty", hide_empty: "false").with { [@empty] } + should respond_to_search(name: "wokada", has_artist: "true").with { @wokada } + should respond_to_search(name: "hatsune_miku", has_artist: "false").with { @miku } + should respond_to_search(name: "hatsune_miku", has_wiki: "true").with { @miku } + should respond_to_search(name: "vocaloid", has_wiki: "false").with { @vocaloid } + end end context "autocomplete action" do From f84ceb3938d7d8a1a1713463440da04458247012 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 24 Jun 2020 19:31:47 -0500 Subject: [PATCH 095/173] image proxy: raise image download timeout. Possible fix for large Pixiv previews on the upload page sometimes returning HTTP 500 errors. --- app/logical/image_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logical/image_proxy.rb b/app/logical/image_proxy.rb index 9c2acfe42..597d1210f 100644 --- a/app/logical/image_proxy.rb +++ b/app/logical/image_proxy.rb @@ -14,7 +14,7 @@ class ImageProxy raise Error, "Proxy not allowed for this url (url=#{url})" unless needs_proxy?(url) referer = fake_referer_for(url) - response = Danbooru::Http.headers(Referer: referer).get(url) + response = Danbooru::Http.timeout(30).headers(Referer: referer).get(url) raise Error, "Couldn't proxy image (code=#{response.status}, url=#{url})" unless response.status.success? response From d3bb5c67ee42cebc80ecfdef4101de15ccbe9708 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 24 Jun 2020 20:52:29 -0500 Subject: [PATCH 096/173] danbooru::http: factor out referrer spoofing. Factor out referrer spoofing so that it can be used outside of downloading files. We also need to spoof the referrer when determining the remote filesize of images on the uploads page. --- app/logical/danbooru/http.rb | 5 +++-- app/logical/danbooru/http/spoof_referrer.rb | 13 +++++++++++++ test/unit/danbooru_http_test.rb | 9 +++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 app/logical/danbooru/http/spoof_referrer.rb diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index ed25aa804..d5c223b57 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -4,6 +4,7 @@ require "danbooru/http/cache" require "danbooru/http/redirector" require "danbooru/http/retriable" require "danbooru/http/session" +require "danbooru/http/spoof_referrer" module Danbooru class Http @@ -25,6 +26,7 @@ module Danbooru .timeout(DEFAULT_TIMEOUT) .headers("Accept-Encoding" => "gzip") .headers("User-Agent": "#{Danbooru.config.canonical_app_name}/#{Rails.application.config.x.git_hash}") + .use(:spoof_referrer) .use(:auto_inflate) .use(redirector: { max_redirects: MAX_REDIRECTS }) .use(:session) @@ -97,8 +99,7 @@ module Danbooru concerning :DownloadMethods do def download_media(url, no_polish: true, **options) - url = Addressable::URI.heuristic_parse(url) - response = headers(Referer: url.origin).get(url) + response = get(url) # prevent Cloudflare Polish from modifying images. if no_polish && response.headers["CF-Polished"].present? diff --git a/app/logical/danbooru/http/spoof_referrer.rb b/app/logical/danbooru/http/spoof_referrer.rb new file mode 100644 index 000000000..7df6faef9 --- /dev/null +++ b/app/logical/danbooru/http/spoof_referrer.rb @@ -0,0 +1,13 @@ +module Danbooru + class Http + class SpoofReferrer < HTTP::Feature + HTTP::Options.register_feature :spoof_referrer, self + + def perform(request, &block) + request.headers["Referer"] = request.uri.origin unless request.headers["Referer"].present? + response = yield request + response + end + end + end +end diff --git a/test/unit/danbooru_http_test.rb b/test/unit/danbooru_http_test.rb index 79ba9e0d5..7f3e0a2ad 100644 --- a/test/unit/danbooru_http_test.rb +++ b/test/unit/danbooru_http_test.rb @@ -127,6 +127,15 @@ class DanbooruHttpTest < ActiveSupport::TestCase end end + context "spoof referrer feature" do + should "spoof the referer" do + response = Danbooru::Http.get("https://httpbin.org/anything") + + assert_equal(200, response.status) + assert_equal("https://httpbin.org", response.parse.dig("headers", "Referer")) + end + end + context "#download method" do should "download files" do response, file = Danbooru::Http.download_media("https://httpbin.org/bytes/1000") From 5af50b7fcd544aba612f074e2b32bd297fc0986a Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 24 Jun 2020 21:33:10 -0500 Subject: [PATCH 097/173] danbooru::http: factor out Cloudflare Polish bypassing. * Factor out the Cloudflare Polish bypass code to a standalone feature. * Add `http_downloader` method to the base source strategy. This is a HTTP client that should be used for downloading images or making requests to images. This client ensures that referrer spoofing and Cloudflare bypassing are performed. This fixes a bug with the upload page reporting the polished filesize instead of the original filesize when uploading ArtStation images. --- app/logical/danbooru/http.rb | 17 +++------------- .../danbooru/http/unpolish_cloudflare.rb | 20 +++++++++++++++++++ app/logical/sources/strategies/base.rb | 17 +++++++++++----- app/logical/sources/strategies/moebooru.rb | 2 +- test/unit/danbooru_http_test.rb | 12 ++++++++++- 5 files changed, 47 insertions(+), 21 deletions(-) create mode 100644 app/logical/danbooru/http/unpolish_cloudflare.rb diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index d5c223b57..43530522e 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -5,6 +5,7 @@ require "danbooru/http/redirector" require "danbooru/http/retriable" require "danbooru/http/session" require "danbooru/http/spoof_referrer" +require "danbooru/http/unpolish_cloudflare" module Danbooru class Http @@ -26,7 +27,6 @@ module Danbooru .timeout(DEFAULT_TIMEOUT) .headers("Accept-Encoding" => "gzip") .headers("User-Agent": "#{Danbooru.config.canonical_app_name}/#{Rails.application.config.x.git_hash}") - .use(:spoof_referrer) .use(:auto_inflate) .use(redirector: { max_redirects: MAX_REDIRECTS }) .use(:session) @@ -98,20 +98,9 @@ module Danbooru end concerning :DownloadMethods do - def download_media(url, no_polish: true, **options) + def download_media(url, file: Tempfile.new("danbooru-download-", binmode: true)) response = get(url) - # prevent Cloudflare Polish from modifying images. - if no_polish && response.headers["CF-Polished"].present? - url.query_values = url.query_values.to_h.merge(danbooru_no_polish: SecureRandom.uuid) - return download_media(url, no_polish: false) - end - - file = download_response(response, **options) - [response, MediaFile.open(file)] - end - - def download_response(response, file: Tempfile.new("danbooru-download-", binmode: true)) raise DownloadError, "Downloading #{response.uri} failed with code #{response.status}" if response.status != 200 raise FileTooLargeError, response if @max_size && response.content_length.to_i > @max_size @@ -123,7 +112,7 @@ module Danbooru end file.rewind - file + [response, MediaFile.open(file)] end end diff --git a/app/logical/danbooru/http/unpolish_cloudflare.rb b/app/logical/danbooru/http/unpolish_cloudflare.rb new file mode 100644 index 000000000..5ad62dcba --- /dev/null +++ b/app/logical/danbooru/http/unpolish_cloudflare.rb @@ -0,0 +1,20 @@ +# Bypass Cloudflare Polish (https://support.cloudflare.com/hc/en-us/articles/360000607372-Using-Cloudflare-Polish-to-compress-images) + +module Danbooru + class Http + class UnpolishCloudflare < HTTP::Feature + HTTP::Options.register_feature :unpolish_cloudflare, self + + def perform(request, &block) + response = yield request + + if response.headers["CF-Polished"].present? + request.uri.query_values = request.uri.query_values.to_h.merge(danbooru_no_polish: SecureRandom.uuid) + response = yield request + end + + response + end + end + end +end diff --git a/app/logical/sources/strategies/base.rb b/app/logical/sources/strategies/base.rb index 718e3f73e..15f476a15 100644 --- a/app/logical/sources/strategies/base.rb +++ b/app/logical/sources/strategies/base.rb @@ -146,7 +146,7 @@ module Sources # Returns the size of the image resource without actually downloading the file. def remote_size - response = http.head(image_url) + response = http_downloader.head(image_url) return nil unless response.status == 200 && response.content_length.present? response.content_length.to_i @@ -156,16 +156,23 @@ module Sources # Download the file at the given url, or at the main image url by default. def download_file!(download_url = image_url) raise DownloadError, "Download failed: couldn't find download url for #{url}" if download_url.blank? - response, file = http.download_media(download_url) + response, file = http_downloader.download_media(download_url) raise DownloadError, "Download failed: #{download_url} returned error #{response.status}" if response.status != 200 file end + # A http client for API requests. def http - Danbooru::Http.headers(headers).public_only.timeout(30).max_size(Danbooru.config.max_file_size) + Danbooru::Http.new.public_only end memoize :http + # A http client for downloading files. + def http_downloader + http.timeout(30).max_size(Danbooru.config.max_file_size).use(:spoof_referrer).use(:unpolish_cloudflare) + end + memoize :http_downloader + # The url to use for artist finding purposes. This will be stored in the # artist entry. Normally this will be the profile url. def normalize_for_artist_finder @@ -292,8 +299,8 @@ module Sources to_h.to_json end - def http_exists?(url, headers = {}) - http.headers(headers).head(url).status.success? + def http_exists?(url) + http_downloader.head(url).status.success? end # Convert commentary to dtext by stripping html tags. Sites can override diff --git a/app/logical/sources/strategies/moebooru.rb b/app/logical/sources/strategies/moebooru.rb index 9c4cc1f8d..4a47b8d54 100644 --- a/app/logical/sources/strategies/moebooru.rb +++ b/app/logical/sources/strategies/moebooru.rb @@ -155,7 +155,7 @@ module Sources # the api_response wasn't available because it's a deleted post. elsif post_md5.present? - %w[jpg png gif].find { |ext| http_exists?("https://#{site_name}/image/#{post_md5}.#{ext}", headers) } + %w[jpg png gif].find { |ext| http_exists?("https://#{site_name}/image/#{post_md5}.#{ext}") } else nil diff --git a/test/unit/danbooru_http_test.rb b/test/unit/danbooru_http_test.rb index 7f3e0a2ad..08ad71f64 100644 --- a/test/unit/danbooru_http_test.rb +++ b/test/unit/danbooru_http_test.rb @@ -129,13 +129,23 @@ class DanbooruHttpTest < ActiveSupport::TestCase context "spoof referrer feature" do should "spoof the referer" do - response = Danbooru::Http.get("https://httpbin.org/anything") + response = Danbooru::Http.use(:spoof_referrer).get("https://httpbin.org/anything") assert_equal(200, response.status) assert_equal("https://httpbin.org", response.parse.dig("headers", "Referer")) end end + context "unpolish cloudflare feature" do + should "return the original image for polished images" do + url = "https://cdnb.artstation.com/p/assets/images/images/025/273/307/4k/atey-ghailan-a-sage-keyart-s-ch-04-outlined-1.jpg?1585246642" + response = Danbooru::Http.use(:unpolish_cloudflare).get(url) + + assert_equal(200, response.status) + assert_equal(720_743, response.content_length) + end + end + context "#download method" do should "download files" do response, file = Danbooru::Http.download_media("https://httpbin.org/bytes/1000") From 44f826d8fa82ab35e7e574be6b2b1ebb04995759 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 24 Jun 2020 22:16:48 -0500 Subject: [PATCH 098/173] nicoseiga: optimize image_url method. The image_url method makes a request to `https://seiga.nicovideo.jp/images/source/:image_id` to see where this URL redirects to. Before we did a GET request, which caused it to download the full image. This could fail with a timeout error if the download took too long. We also cached the request, which caused the full image to be cached, even though we only need the headers. Change it to a HEAD request so we don't have to download the entire image just to check the URL. --- app/logical/nico_seiga_api_client.rb | 8 ++++++-- app/logical/sources/strategies/nico_seiga.rb | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/logical/nico_seiga_api_client.rb b/app/logical/nico_seiga_api_client.rb index d471836d6..4cdb75c9c 100644 --- a/app/logical/nico_seiga_api_client.rb +++ b/app/logical/nico_seiga_api_client.rb @@ -78,7 +78,7 @@ class NicoSeigaApiClient Hash.from_xml(resp.to_s)["response"]["user"] end - def get(url) + def login form = { mail_tel: Danbooru.config.nico_seiga_login, password: Danbooru.config.nico_seiga_password @@ -88,7 +88,11 @@ class NicoSeigaApiClient resp = http.cache(1.hour).post("https://account.nicovideo.jp/login/redirector?site=seiga", form: form) raise RuntimeError, "NicoSeiga login failed (status=#{resp.status} url=#{url})" if resp.status != 200 - resp = http.cache(1.minute).get(url) + http + end + + def get(url) + resp = login.cache(1.minute).get(url) #raise RuntimeError, "NicoSeiga get failed (status=#{resp.status} url=#{url})" if resp.status != 200 resp diff --git a/app/logical/sources/strategies/nico_seiga.rb b/app/logical/sources/strategies/nico_seiga.rb index d4af63084..93a2d995c 100644 --- a/app/logical/sources/strategies/nico_seiga.rb +++ b/app/logical/sources/strategies/nico_seiga.rb @@ -81,7 +81,7 @@ module Sources else image_urls.first end - resp = api_client.get(img) + resp = api_client.login.head(img) if resp.uri.to_s =~ %r{https?://.+/(\w+/\d+/\d+)\z}i "https://lohas.nicoseiga.jp/priv/#{$1}" else From fccdcbe7cc50b7f5b6e0e4645d4addc458cbe91d Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Wed, 24 Jun 2020 23:36:04 -0500 Subject: [PATCH 099/173] simple form: regenerate config with new defaults. Regenerated with `bin/rails generate simple_form:install`. --- config/initializers/simple_form.rb | 21 ++++++++++++++------- lib/templates/erb/scaffold/_form.html.erb | 3 ++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb index 65c15ba19..80eee625f 100644 --- a/config/initializers/simple_form.rb +++ b/config/initializers/simple_form.rb @@ -1,3 +1,11 @@ +# frozen_string_literal: true +# +# Uncomment this and change the path if necessary to include your own +# components. +# See https://github.com/heartcombo/simple_form#custom-components to know +# more about custom components. +# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f } +# # Use this setup block to configure all options available in SimpleForm. SimpleForm.setup do |config| # Wrappers are used by the form builder to generate a @@ -43,6 +51,7 @@ SimpleForm.setup do |config| b.optional :readonly ## Inputs + # b.use :input, class: 'input', error_class: 'is-invalid', valid_class: 'is-valid' b.use :label_input b.use :hint, wrap_with: { tag: :span, class: :hint } b.use :error, wrap_with: { tag: :span, class: :error } @@ -77,9 +86,6 @@ SimpleForm.setup do |config| # CSS class to add for error notification helper. config.error_notification_class = 'error_notification' - # ID to add for error notification helper. - # config.error_notification_id = nil - # Series of attempts to detect a default label method for collection. # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] @@ -100,7 +106,7 @@ SimpleForm.setup do |config| # config.item_wrapper_class = nil # How the label text should be generated altogether with the required text. - # config.label_text = ->(label, required, explicit_label) { "#{required} #{label}" } + # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" } # You can define the class to use on all labels. Default is nil. # config.label_class = nil @@ -122,9 +128,6 @@ SimpleForm.setup do |config| # change this configuration to true. config.browser_validations = false - # Collection of methods to detect if a file type was given. - # config.file_methods = [ :mounted_as, :file?, :public_filename ] - # Custom mappings for input types. This should be a hash containing a regexp # to match as key, and the input type that will be used when the field name # matches the regexp as value. @@ -165,4 +168,8 @@ SimpleForm.setup do |config| # Defines which i18n scope will be used in Simple Form. # config.i18n_scope = 'simple_form' + + # Defines validation classes to the input_field. By default it's nil. + # config.input_field_valid_class = 'is-valid' + # config.input_field_error_class = 'is-invalid' end diff --git a/lib/templates/erb/scaffold/_form.html.erb b/lib/templates/erb/scaffold/_form.html.erb index 201a069e2..0d60e8e71 100644 --- a/lib/templates/erb/scaffold/_form.html.erb +++ b/lib/templates/erb/scaffold/_form.html.erb @@ -1,5 +1,6 @@ +<%# frozen_string_literal: true %> <%%= simple_form_for(@<%= singular_table_name %>) do |f| %> - <%%= f.error_notification %> + <%%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %> <div class="form-inputs"> <%- attributes.each do |attribute| -%> From 883856d4afdfa9470e12669997c088548d7429a0 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Thu, 25 Jun 2020 15:54:06 -0500 Subject: [PATCH 100/173] simple form: refactor DText form fields to use SimpleForm. * Refactors DText form fields to use a custom SimpleForm input instead of manually generated html. This fixes it so that DText fields use the same markup as normal SimpleForm fields, which lets us apply browser maxlength validations to DText input fields. * Fixes autocomplete for @-mentions only working in comments and forum posts. Now @-mention autocomplete works in all DText fields, including dmails. Known bug: it applies in artist commentary fields when it shouldn't. --- app/helpers/application_helper.rb | 17 ++--------- .../src/javascripts/autocomplete.js.erb | 2 +- app/javascript/src/javascripts/dtext.js | 7 +++-- .../src/styles/common/simple_form.scss | 2 +- app/logical/dtext_input.rb | 30 +++++++++++++++++++ app/views/bulk_update_requests/_form.html.erb | 4 +-- app/views/comments/_form.html.erb | 4 +-- app/views/dmails/_form.html.erb | 4 +-- app/views/dtext/_form.html.erb | 19 ------------ .../forum_posts/partials/edit/_form.html.erb | 4 +-- .../forum_posts/partials/new/_form.html.erb | 4 +-- app/views/forum_topics/_form.html.erb | 4 +-- app/views/moderation_reports/_new.html.erb | 8 ++--- app/views/pools/edit.html.erb | 4 +-- app/views/pools/new.html.erb | 4 +-- app/views/post_appeals/_new.html.erb | 8 ++--- app/views/post_flags/_new.html.erb | 8 ++--- app/views/user_feedbacks/edit.html.erb | 4 +-- app/views/user_feedbacks/new.html.erb | 4 +-- app/views/wiki_pages/_form.html.erb | 4 +-- app/views/wiki_pages/new.html.erb | 4 +-- config/initializers/simple_form.rb | 3 ++ 22 files changed, 75 insertions(+), 77 deletions(-) create mode 100644 app/logical/dtext_input.rb delete mode 100644 app/views/dtext/_form.html.erb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 26a4a0b59..7c010de1d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -217,21 +217,8 @@ module ApplicationHelper tag.div(text, class: "prose", **options) end - def dtext_field(object, name, options = {}) - options[:name] ||= name.capitalize - options[:input_id] ||= "#{object}_#{name}" - options[:input_name] ||= "#{object}[#{name}]" - options[:value] ||= instance_variable_get("@#{object}").try(name) - options[:preview_id] ||= "dtext-preview" - options[:classes] ||= "" - options[:hint] ||= "" - options[:type] ||= "text" - - render "dtext/form", options - end - - def dtext_preview_button(object, name, input_id: "#{object}_#{name}", preview_id: "dtext-preview") - tag.input value: "Preview", type: "button", class: "dtext-preview-button", "data-input-id": input_id, "data-preview-id": preview_id + def dtext_preview_button(preview_field) + tag.input value: "Preview", type: "button", class: "dtext-preview-button", "data-preview-field": preview_field end def quick_search_form_for(attribute, url, name, autocomplete: nil, redirect: false, &block) diff --git a/app/javascript/src/javascripts/autocomplete.js.erb b/app/javascript/src/javascripts/autocomplete.js.erb index 49e9abee4..afe3a854a 100644 --- a/app/javascript/src/javascripts/autocomplete.js.erb +++ b/app/javascript/src/javascripts/autocomplete.js.erb @@ -38,7 +38,7 @@ Autocomplete.initialize_all = function() { }); this.initialize_tag_autocomplete(); - this.initialize_mention_autocomplete($(".autocomplete-mentions textarea")); + this.initialize_mention_autocomplete($("form div.input.dtext textarea")); this.initialize_fields($('[data-autocomplete="tag"]'), Autocomplete.tag_source); this.initialize_fields($('[data-autocomplete="artist"]'), Autocomplete.artist_source); this.initialize_fields($('[data-autocomplete="pool"]'), Autocomplete.pool_source); diff --git a/app/javascript/src/javascripts/dtext.js b/app/javascript/src/javascripts/dtext.js index 94d708f79..8b9edbb60 100644 --- a/app/javascript/src/javascripts/dtext.js +++ b/app/javascript/src/javascripts/dtext.js @@ -39,8 +39,11 @@ Dtext.call_edit = function(e, $button, $input, $preview) { Dtext.click_button = function(e) { var $button = $(e.target); - var $input = $("#" + $button.data("input-id")); - var $preview = $("#" + $button.data("preview-id")); + var $form = $button.parents("form"); + var fieldName = $button.data("preview-field"); + var $inputContainer = $form.find(`div.input.${fieldName} .dtext-previewable`); + var $input = $inputContainer.find("> input, > textarea"); + var $preview = $inputContainer.find("div.dtext-preview"); if ($button.val().match(/preview/i)) { Dtext.call_preview(e, $button, $input, $preview); diff --git a/app/javascript/src/styles/common/simple_form.scss b/app/javascript/src/styles/common/simple_form.scss index 9d39c3a05..2b1706df4 100644 --- a/app/javascript/src/styles/common/simple_form.scss +++ b/app/javascript/src/styles/common/simple_form.scss @@ -39,7 +39,7 @@ form.simple_form { padding-left: 1em; } - &.text { + &.text, &.dtext { .hint { padding-left: 0; display: block; diff --git a/app/logical/dtext_input.rb b/app/logical/dtext_input.rb new file mode 100644 index 000000000..a1910e933 --- /dev/null +++ b/app/logical/dtext_input.rb @@ -0,0 +1,30 @@ +# A custom SimpleForm input for DText fields. +# +# Usage: +# +# <%= f.input :body, as: :dtext %> +# <%= f.input :reason, as: :dtext, inline: true %> +# +# https://github.com/heartcombo/simple_form/wiki/Custom-inputs-examples +# https://github.com/heartcombo/simple_form/blob/master/lib/simple_form/inputs/string_input.rb +# https://github.com/heartcombo/simple_form/blob/master/lib/simple_form/inputs/text_input.rb + +class DtextInput < SimpleForm::Inputs::Base + enable :placeholder, :maxlength, :minlength + + def input(wrapper_options) + t = template + merged_input_options = merge_wrapper_options(input_html_options, wrapper_options) + + t.tag.div(class: "dtext-previewable") do + if options[:inline] + t.concat @builder.text_field(attribute_name, merged_input_options) + else + t.concat @builder.text_area(attribute_name, { rows: 20, cols: 30 }.merge(merged_input_options)) + end + + t.concat t.tag.div(id: "dtext-preview", class: "dtext-preview prose") + t.concat t.tag.span(t.link_to("Formatting help", t.dtext_help_path, remote: true, method: :get), class: "hint dtext-hint") + end + end +end diff --git a/app/views/bulk_update_requests/_form.html.erb b/app/views/bulk_update_requests/_form.html.erb index 8d48d16ea..acf886b59 100644 --- a/app/views/bulk_update_requests/_form.html.erb +++ b/app/views/bulk_update_requests/_form.html.erb @@ -24,7 +24,7 @@ <% if @bulk_update_request.new_record? %> <div class="input"> - <%= dtext_field "bulk_update_request", "reason", :name => "Reason" %> + <%= f.input :reason, as: :dtext %> </div> <% end %> @@ -44,5 +44,5 @@ <% end %> <%= f.submit value: "Submit" %> - <%= dtext_preview_button "bulk_update_request", "reason" %> + <%= dtext_preview_button "bulk_update_request_reason" %> <% end %> diff --git a/app/views/comments/_form.html.erb b/app/views/comments/_form.html.erb index fbf255f5e..72f6185a6 100644 --- a/app/views/comments/_form.html.erb +++ b/app/views/comments/_form.html.erb @@ -4,9 +4,9 @@ <% if comment.new_record? %> <%= f.hidden_field :post_id %> <% end %> - <%= dtext_field "comment", "body", classes: "autocomplete-mentions", value: comment.body, input_id: "comment_body_for_#{comment.id}", preview_id: "dtext-preview-for-#{comment.id}", hint: link_to_wiki("Comment rules", "howto:comment").html_safe %> + <%= f.input :body, as: :dtext %> <%= f.button :submit, "Submit" %> - <%= dtext_preview_button "comment", "body", :input_id => "comment_body_for_#{comment.id}", :preview_id => "dtext-preview-for-#{comment.id}" %> + <%= dtext_preview_button "comment_body" %> <% if comment.new_record? %> <%= f.input :do_not_bump_post, :label => "No bump" %> <% end %> diff --git a/app/views/dmails/_form.html.erb b/app/views/dmails/_form.html.erb index f3efb498c..2a5b2925d 100644 --- a/app/views/dmails/_form.html.erb +++ b/app/views/dmails/_form.html.erb @@ -1,7 +1,7 @@ <%= edit_form_for(dmail) do |f| %> <%= f.input :to_name, :label => "To", :input_html => { value: dmail.to.try(:name), data: { autocomplete: "user" } } %> <%= f.input :title, :as => :string %> - <%= dtext_field "dmail", "body" %> + <%= f.input :body, as: :dtext %> <%= f.button :submit, "Send", :data => { :disable_with => "Sending..." } %> - <%= dtext_preview_button "dmail", "body" %> + <%= dtext_preview_button "dmail_body" %> <% end %> diff --git a/app/views/dtext/_form.html.erb b/app/views/dtext/_form.html.erb deleted file mode 100644 index ef79e084e..000000000 --- a/app/views/dtext/_form.html.erb +++ /dev/null @@ -1,19 +0,0 @@ -<%# name, input_id, input_name, preview_id, value, type %> -<div class="input text optional <%= classes %>"> - <label class="text optional" for="<%= input_id %>"><%= name %></label> - - <div class="dtext-previewable"> - <% if type == "text" %> - <textarea id="<%= input_id %>" class="text optional" rows="20" name="<%= input_name %>" cols="30"><%= value %></textarea> - <% else %> - <input type="text" id="<%= input_id %>" class="text optional" name="<%= input_name %>" value="<%= value %>"> - <% end %> - <div id="<%= preview_id %>" class="dtext-preview prose"></div> - </div> - <span class="hint"> - <%= link_to "Formatting help", dtext_help_path, remote: true, method: :get %>. - <% if hint.present? %> - <%= hint %>. - <% end %> - </span> -</div> diff --git a/app/views/forum_posts/partials/edit/_form.html.erb b/app/views/forum_posts/partials/edit/_form.html.erb index 617219d23..3a1b641d7 100644 --- a/app/views/forum_posts/partials/edit/_form.html.erb +++ b/app/views/forum_posts/partials/edit/_form.html.erb @@ -1,8 +1,8 @@ <%= error_messages_for("forum_post") %> <%= edit_form_for(forum_post) do |f| %> - <%= dtext_field "forum_post", "body", :value => forum_post.body, :classes => "autocomplete-mentions", :input_id => "forum_post_body_for_#{forum_post.id}", :preview_id => "dtext-preview-for-#{forum_post.id}" %> + <%= f.input :body, as: :dtext %> <%= f.button :submit, "Submit" %> - <%= dtext_preview_button "forum_post", "body", :input_id => "forum_post_body_for_#{forum_post.id}", :preview_id => "dtext-preview-for-#{forum_post.id}" %> + <%= dtext_preview_button "forum_post_body" %> <% end %> diff --git a/app/views/forum_posts/partials/new/_form.html.erb b/app/views/forum_posts/partials/new/_form.html.erb index 622d01056..cbfa63350 100644 --- a/app/views/forum_posts/partials/new/_form.html.erb +++ b/app/views/forum_posts/partials/new/_form.html.erb @@ -6,8 +6,8 @@ <% else %> <%= f.input :topic_id, :label => "Topic ID" %> <% end %> - <%= dtext_field "forum_post", "body", :classes => "autocomplete-mentions" %> + <%= f.input :body, as: :dtext %> <%= f.button :submit, "Submit" %> - <%= dtext_preview_button "forum_post", "body" %> + <%= dtext_preview_button "forum_post_body" %> <% end %> diff --git a/app/views/forum_topics/_form.html.erb b/app/views/forum_topics/_form.html.erb index f4e78c493..468befdca 100644 --- a/app/views/forum_topics/_form.html.erb +++ b/app/views/forum_topics/_form.html.erb @@ -10,7 +10,7 @@ </div> <%= f.simple_fields_for :original_post do |pf| %> - <%= dtext_field "forum_post", "body", :classes => "autocomplete-mentions", :input_name => "forum_topic[original_post_attributes][body]", :value => forum_topic.original_post.body, :input_id => "forum_post_body_for_#{forum_topic.original_post.id}", :preview_id => "dtext-preview-for-#{forum_topic.original_post.id}" %> + <%= pf.input :body, as: :dtext %> <% end %> <% if policy(forum_topic).moderate? %> @@ -20,6 +20,6 @@ <% end %> <%= f.button :submit, "Submit" %> - <%= dtext_preview_button "forum_post", "body", :input_id => "forum_post_body_for_#{forum_topic.original_post.id}", :preview_id => "dtext-preview-for-#{forum_topic.original_post.id}" %> + <%= dtext_preview_button "forum_topic_original_post_body" %> <% end %> </div> diff --git a/app/views/moderation_reports/_new.html.erb b/app/views/moderation_reports/_new.html.erb index 0af8a497c..f433ee805 100644 --- a/app/views/moderation_reports/_new.html.erb +++ b/app/views/moderation_reports/_new.html.erb @@ -1,12 +1,10 @@ <div class="report-dialog-body"> <%= embed_wiki("help:report_notice") %> - <%# XXX dtext_field expects there to be a `moderation_report` instance variable. %> - <% @moderation_report = moderation_report %> - <%= edit_form_for(@moderation_report, format: :js, remote: true) do |f| %> + <%= edit_form_for(moderation_report, format: :js, remote: true) do |f| %> <%= f.hidden_field :model_type %> <%= f.hidden_field :model_id %> - <%= dtext_field "moderation_report", "reason", preview_id: "dtext-preview-for-moderation-report", type: "string" %> - <%= dtext_preview_button "moderation_report", "reason", preview_id: "dtext-preview-for-moderation-report" %> + <%= f.input :reason, as: :dtext, inline: true %> + <%= dtext_preview_button "moderation_report_reason" %> <% end %> </div> diff --git a/app/views/pools/edit.html.erb b/app/views/pools/edit.html.erb index 4202226de..2c2652d6a 100644 --- a/app/views/pools/edit.html.erb +++ b/app/views/pools/edit.html.erb @@ -6,11 +6,11 @@ <%= error_messages_for "pool" %> <%= f.input :name, :as => :string, :input_html => { :value => @pool.pretty_name } %> - <%= dtext_field "pool", "description" %> - <%= dtext_preview_button "pool", "description" %> + <%= f.input :description, as: :dtext %> <%= f.input :post_ids_string, as: :text, label: "Posts" %> <%= f.input :category, :collection => ["series", "collection"], :include_blank => false %> <%= f.button :submit %> + <%= dtext_preview_button "pool_description" %> <% end %> </div> </div> diff --git a/app/views/pools/new.html.erb b/app/views/pools/new.html.erb index 165ec4497..d2f595aee 100644 --- a/app/views/pools/new.html.erb +++ b/app/views/pools/new.html.erb @@ -6,11 +6,11 @@ <%= edit_form_for(@pool) do |f| %> <%= f.input :name, :as => :string, :required => true %> - <%= dtext_field "pool", "description" %> + <%= f.input :description, as: :dtext %> <%= f.input :post_ids_string, as: :text, label: "Posts" %> <%= f.input :category, :collection => ["series", "collection"], :include_blank => true, :selected => "", :required => true %> <%= f.button :submit, "Submit" %> - <%= dtext_preview_button "pool", "description" %> + <%= dtext_preview_button "pool_description" %> <% end %> </div> </div> diff --git a/app/views/post_appeals/_new.html.erb b/app/views/post_appeals/_new.html.erb index c418a1b8e..8cee19acc 100644 --- a/app/views/post_appeals/_new.html.erb +++ b/app/views/post_appeals/_new.html.erb @@ -1,11 +1,9 @@ <div class="appeal-dialog-body"> <%= embed_wiki("help:appeal_notice") %> - <%# XXX dtext_field expects there to be a `post_appeal` instance variable. %> - <% @post_appeal = post_appeal %> - <%= edit_form_for(@post_appeal, format: :js, remote: true) do |f| %> + <%= edit_form_for(post_appeal, format: :js, remote: true) do |f| %> <%= f.hidden_field :post_id %> - <%= dtext_field "post_appeal", "reason", preview_id: "dtext-preview-for-post-appeal", type: "string" %> - <%= dtext_preview_button "post_appeal", "reason", preview_id: "dtext-preview-for-post-appeal" %> + <%= f.input :reason, as: :dtext, inline: true %> + <%= dtext_preview_button "post_appeal_reason" %> <% end %> </div> diff --git a/app/views/post_flags/_new.html.erb b/app/views/post_flags/_new.html.erb index 00cc67449..1e2005ac3 100644 --- a/app/views/post_flags/_new.html.erb +++ b/app/views/post_flags/_new.html.erb @@ -1,11 +1,9 @@ <div class="flag-dialog-body"> <%= embed_wiki("help:flag_notice") %> - <%# XXX dtext_field expects there to be a `post_flag` instance variable. %> - <% @post_flag = post_flag %> - <%= edit_form_for(@post_flag, format: :js, remote: true) do |f| %> + <%= edit_form_for(post_flag, format: :js, remote: true) do |f| %> <%= f.hidden_field :post_id %> - <%= dtext_field "post_flag", "reason", preview_id: "dtext-preview-for-post-flag", type: "string" %> - <%= dtext_preview_button "post_flag", "reason", preview_id: "dtext-preview-for-post-flag" %> + <%= f.input :reason, as: :dtext, inline: true %> + <%= dtext_preview_button "post_flag_reason" %> <% end %> </div> diff --git a/app/views/user_feedbacks/edit.html.erb b/app/views/user_feedbacks/edit.html.erb index d7571c462..9758a573d 100644 --- a/app/views/user_feedbacks/edit.html.erb +++ b/app/views/user_feedbacks/edit.html.erb @@ -9,10 +9,10 @@ <%= edit_form_for(@user_feedback) do |f| %> <%= f.input :category, :collection => ["positive", "neutral", "negative"], :include_blank => false %> - <%= dtext_field "user_feedback", "body" %> + <%= f.input :body, as: :dtext %> <%= f.input :is_deleted, label: "Deleted" %> <%= f.button :submit, "Submit" %> - <%= dtext_preview_button "user_feedback", "body" %> + <%= dtext_preview_button "user_feedback_body" %> <% end %> </div> </div> diff --git a/app/views/user_feedbacks/new.html.erb b/app/views/user_feedbacks/new.html.erb index 67a3d89d5..debad85a6 100644 --- a/app/views/user_feedbacks/new.html.erb +++ b/app/views/user_feedbacks/new.html.erb @@ -16,9 +16,9 @@ <%= edit_form_for(@user_feedback) do |f| %> <%= f.input :user_name, :label => "User", :input_html => { value: @user_feedback.user.try(:name), data: { autocomplete: "user" } } %> <%= f.input :category, :collection => ["positive", "neutral", "negative"], :include_blank => false %> - <%= dtext_field "user_feedback", "body" %> + <%= f.input :body, as: :dtext %> <%= f.button :submit, "Submit" %> - <%= dtext_preview_button "user_feedback", "body" %> + <%= dtext_preview_button "user_feedback_body" %> <% end %> </div> </div> diff --git a/app/views/wiki_pages/_form.html.erb b/app/views/wiki_pages/_form.html.erb index bb0100e7d..8f5616dc7 100644 --- a/app/views/wiki_pages/_form.html.erb +++ b/app/views/wiki_pages/_form.html.erb @@ -5,7 +5,7 @@ <%= f.input :title, error: false, input_html: { data: { autocomplete: "tag" } }, hint: "Change to rename this wiki page. Update any wikis linking to this page first." %> <%= f.input :other_names_string, as: :text, input_html: { size: "30x1" }, label: "Other names (#{link_to_wiki "help", "help:translated_tags"})".html_safe, hint: "Names used for this tag on other sites such as Pixiv. Separate with spaces." %> - <%= dtext_field "wiki_page", "body" %> + <%= f.input :body, as: :dtext %> <% if policy(@wiki_page).can_edit_locked? %> <%= f.input :is_locked, label: "Locked", hint: "Locked wikis can only be edited by Builders." %> @@ -14,6 +14,6 @@ <%= f.input :is_deleted, label: "Deleted", hint: "Check to mark this wiki page as deleted." %> <%= f.submit "Submit" %> - <%= dtext_preview_button "wiki_page", "body" %> + <%= dtext_preview_button "wiki_page_body" %> <% end %> </div> diff --git a/app/views/wiki_pages/new.html.erb b/app/views/wiki_pages/new.html.erb index 1449e530b..aa796d340 100644 --- a/app/views/wiki_pages/new.html.erb +++ b/app/views/wiki_pages/new.html.erb @@ -14,9 +14,9 @@ <%= edit_form_for(@wiki_page) do |f| %> <%= f.input :title, error: false, input_html: { data: { autocomplete: "tag" } } %> <%= f.input :other_names_string, as: :text, input_html: { size: "30x1" }, label: "Other names (#{link_to_wiki "help", "help:translated_tags"})".html_safe, hint: "Names used for this tag on other sites such as Pixiv. Separate with spaces." %> - <%= dtext_field "wiki_page", "body" %> + <%= f.input :body, as: :dtext %> <%= f.submit "Submit" %> - <%= dtext_preview_button "wiki_page", "body" %> + <%= dtext_preview_button "wiki_page_body" %> <% end %> <%= render "tag_relationships/alias_and_implication_list", tag: @wiki_page.tag %> diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb index 80eee625f..9fd871574 100644 --- a/config/initializers/simple_form.rb +++ b/config/initializers/simple_form.rb @@ -5,6 +5,9 @@ # See https://github.com/heartcombo/simple_form#custom-components to know # more about custom components. # Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f } + +require "dtext_input" + # # Use this setup block to configure all options available in SimpleForm. SimpleForm.setup do |config| From 5e23861bea7132c07b79147b7bcd716678fcba4a Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Thu, 25 Jun 2020 16:28:18 -0500 Subject: [PATCH 101/173] simple form: enable HTML5 maxlength validations. Makes it so that models that have maximum length validations will add maxlength attributes to form fields. This includes flag reasons, appeal reasons, and forum topic titles. Partially fixes #4519 (Add "n/m characters remaining" character counter to the appeal reason). https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/maxlength --- config/initializers/simple_form.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb index 9fd871574..bde646c6e 100644 --- a/config/initializers/simple_form.rb +++ b/config/initializers/simple_form.rb @@ -39,7 +39,7 @@ SimpleForm.setup do |config| # Calculates maxlength from length validations for string inputs # and/or database column lengths - b.optional :maxlength + b.use :maxlength # Calculate minlength from length validations for string inputs b.optional :minlength @@ -129,7 +129,7 @@ SimpleForm.setup do |config| # in this configuration, which is recommended due to some quirks from different browsers. # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, # change this configuration to true. - config.browser_validations = false + config.browser_validations = true # Custom mappings for input types. This should be a hash containing a regexp # to match as key, and the input type that will be used when the field name From 804a2ef9a5bed802930c94bd961116806b41ed49 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 26 Jun 2020 14:57:19 -0500 Subject: [PATCH 102/173] Fix #4535: Comment edit forms contain duplicate IDs. Prefix comment form IDs with `post_<id>_comment_<id>` to ensure uniqueness. --- app/views/comments/_form.html.erb | 2 +- config/initializers/simple_form.rb | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/views/comments/_form.html.erb b/app/views/comments/_form.html.erb index 72f6185a6..3144f6cfe 100644 --- a/app/views/comments/_form.html.erb +++ b/app/views/comments/_form.html.erb @@ -1,6 +1,6 @@ <%= error_messages_for :comment %> -<%= edit_form_for(comment, html: { style: ("display: none;" if local_assigns[:hidden]), class: "edit_comment" }) do |f| %> +<%= edit_form_for(comment, namespace: "post_#{comment.post.id}_comment_#{comment.id || "new"}", html: { style: ("display: none;" if local_assigns[:hidden]), class: "edit_comment" }) do |f| %> <% if comment.new_record? %> <%= f.hidden_field :post_id %> <% end %> diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb index bde646c6e..d95d9c4a2 100644 --- a/config/initializers/simple_form.rb +++ b/config/initializers/simple_form.rb @@ -73,7 +73,13 @@ SimpleForm.setup do |config| # Defaults to :nested for bootstrap config. # inline: input + label # nested: label > input - config.boolean_style = :nested + # + # XXX We use inline instead of nested so that 1) the html structure for + # checkboxes is the same as the html structure of other input elements and 2) + # because if we use the `namespace` option to provide namespaced html IDs on + # a form with nested checkboxes, then SimpleForm generates incorrect IDs on + # the hidden checkbox inputs. + config.boolean_style = :inline # Default class for buttons config.button_class = 'btn' From f7f72e29b57338cea03a419f611120df63d4efef Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 26 Jun 2020 14:59:30 -0500 Subject: [PATCH 103/173] Fix #4458: Duplicate HTML IDs on post show page. --- app/views/posts/partials/show/_edit.html.erb | 6 ++--- app/views/posts/show.html.erb | 24 +++++++------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/app/views/posts/partials/show/_edit.html.erb b/app/views/posts/partials/show/_edit.html.erb index 295c1f22d..93a07e9d6 100644 --- a/app/views/posts/partials/show/_edit.html.erb +++ b/app/views/posts/partials/show/_edit.html.erb @@ -7,9 +7,9 @@ <%= render "sources/info" %> <%= edit_form_for(post, html: { id: "form" }) do |f| %> - <%= hidden_field_tag :tags_query, params[:q] %> - <%= hidden_field_tag :pool_id, params[:pool_id] %> - <%= hidden_field_tag :favgroup_id, params[:favgroup_id] %> + <%= f.input :tags_query, as: :hidden, input_html: { id: nil, name: "tags_query", value: params[:q] } %> + <%= f.input :pool_id, as: :hidden, input_html: { id: nil, name: "pool_id", value: params[:pool_id] } %> + <%= f.input :favgroup_id, as: :hidden, input_html: { id: nil, name: "favgroup_id", value: params[:favgroup_id] } %> <%= f.input :old_tag_string, as: :hidden, input_html: { value: post.tag_string } %> <%= f.input :old_parent_id, as: :hidden, input_html: { value: post.parent_id } %> <%= f.input :old_source, as: :hidden, input_html: { value: post.source } %> diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb index a6c77fbdc..3aa5e7798 100644 --- a/app/views/posts/show.html.erb +++ b/app/views/posts/show.html.erb @@ -61,25 +61,17 @@ <% end %> <section id="mark-as-translated-section" style="display: none;"> - <%= form_tag(mark_as_translated_post_path(@post), :class => "simple_form", :method => :put) do |f| %> - <%= hidden_field_tag :tags_query, params[:q] %> - <%= hidden_field_tag :pool_id, params[:pool_id] %> + <%= edit_form_for(@post, url: mark_as_translated_post_path(@post), method: :put) do |f| %> + <%= f.input :tags_query, as: :hidden, input_html: { id: nil, name: "tags_query", value: params[:q] } %> + <%= f.input :pool_id, as: :hidden, input_html: { id: nil, name: "pool_id", value: params[:pool_id] } %> + <%= f.input :favgroup_id, as: :hidden, input_html: { id: nil, name: "favgroup_id", value: params[:favgroup_id] } %> - <fieldset> - <label for="post_check_translation"> - <%= check_box "post", "check_translation", :checked => @post.has_tag?("check_translation") %> - Check translation - </label> - - <label for="post_partially_translated"> - <%= check_box "post", "partially_translated", :checked => @post.has_tag?("partially_translated") %> - Partially translated - </label> + <fieldset class="inline-fieldset"> + <%= f.input :check_translation, as: :boolean, input_html: { checked: @post.has_tag?("check_translation") } %> + <%= f.input :partially_translated, as: :boolean, input_html: { checked: @post.has_tag?("partially_translated") } %> </fieldset> - <div class="input"> - <%= submit_tag "Mark as translated" %> - </div> + <%= f.submit "Mark as translated" %> <% end %> </section> From 3a6e4732bfcbce9225ca775b9366ea6dde379d4e Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 00:36:48 -0500 Subject: [PATCH 104/173] Fix #4511: Remove delete/undelete controls from the artist index. * Only show the delete artist button on the artist edit page. * Only show the delete pool button on the pool edit page. * Only show the delete wiki button on the wiki edit page. --- app/views/artists/_secondary_links.html.erb | 2 +- app/views/artists/index.html.erb | 6 ------ app/views/pools/_secondary_links.html.erb | 10 ++++++---- app/views/wiki_pages/_secondary_links.html.erb | 10 ++++++---- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/app/views/artists/_secondary_links.html.erb b/app/views/artists/_secondary_links.html.erb index 8093e374e..c09aa9280 100644 --- a/app/views/artists/_secondary_links.html.erb +++ b/app/views/artists/_secondary_links.html.erb @@ -15,7 +15,7 @@ <%= subnav_link_to "Edit", edit_artist_path(@artist), :"data-shortcut" => "e" %> <% end %> <%= subnav_link_to "History", artist_versions_path(:search => {:artist_id => @artist.id}) %> - <% if policy(@artist).update? %> + <% if current_page?(action: "edit") && policy(@artist).update? %> <% if @artist.is_deleted? %> <%= subnav_link_to "Undelete", artist_path(@artist, format: "js"), method: :put, data: {confirm: "Are you sure you want to undelete this artist?", params: "artist[is_deleted]=false"}, remote: true %> <% else %> diff --git a/app/views/artists/index.html.erb b/app/views/artists/index.html.erb index 49f2577e1..4ba542b68 100644 --- a/app/views/artists/index.html.erb +++ b/app/views/artists/index.html.erb @@ -32,12 +32,6 @@ <% t.column column: "control" do |artist| %> <% if policy(artist).update? %> <%= link_to "Edit", edit_artist_path(artist) %> - - <% if artist.is_deleted? %> - | <%= link_to "Undelete", artist_path(artist, artist: { is_deleted: false }), method: :put, remote: true %> - <% else %> - | <%= link_to "Delete", artist_path(artist, artist: { is_deleted: true }), method: :put, remote: true %> - <% end %> <% end %> <% end %> <% end %> diff --git a/app/views/pools/_secondary_links.html.erb b/app/views/pools/_secondary_links.html.erb index 618c9070e..4a6b77711 100644 --- a/app/views/pools/_secondary_links.html.erb +++ b/app/views/pools/_secondary_links.html.erb @@ -13,10 +13,12 @@ <% if policy(@pool).update? %> <%= subnav_link_to "Edit", edit_pool_path(@pool), "data-shortcut": "e" %> <% end %> - <% if policy(@pool).undelete? %> - <%= subnav_link_to "Undelete", undelete_pool_path(@pool), :method => :post, :remote => true %> - <% elsif policy(@pool).destroy? %> - <%= subnav_link_to "Delete", pool_path(@pool), :method => :delete, :"data-shortcut" => "shift+d", :"data-confirm" => "Are you sure you want to delete this pool?", :remote => true %> + <% if current_page?(action: :edit) %> + <% if policy(@pool).undelete? %> + <%= subnav_link_to "Undelete", undelete_pool_path(@pool), :method => :post, :remote => true %> + <% elsif policy(@pool).destroy? %> + <%= subnav_link_to "Delete", pool_path(@pool), :method => :delete, :"data-shortcut" => "shift+d", :"data-confirm" => "Are you sure you want to delete this pool?", :remote => true %> + <% end %> <% end %> <% if PoolVersion.enabled? %> <%= subnav_link_to "History", pool_versions_path(:search => {:pool_id => @pool.id}) %> diff --git a/app/views/wiki_pages/_secondary_links.html.erb b/app/views/wiki_pages/_secondary_links.html.erb index 276591965..683438372 100644 --- a/app/views/wiki_pages/_secondary_links.html.erb +++ b/app/views/wiki_pages/_secondary_links.html.erb @@ -16,10 +16,12 @@ <% if policy(@wiki_page).edit? %> <%= subnav_link_to "Edit", edit_wiki_page_path(@wiki_page.id), "data-shortcut": "e" %> - <% if @wiki_page.is_deleted? %> - <%= subnav_link_to "Undelete", wiki_page_path(@wiki_page.id), remote: true, method: :put, "data-params": "wiki_page[is_deleted]=false", "data-shortcut": "shift+d", "data-confirm": "Are you sure you want to undelete this wiki?" %> - <% else %> - <%= subnav_link_to "Delete", wiki_page_path(@wiki_page.id), remote: true, method: :put, "data-params": "wiki_page[is_deleted]=true", "data-shortcut": "shift+d", "data-confirm": "Are you sure you want to delete this wiki?" %> + <% if current_page?(action: :edit) %> + <% if @wiki_page.is_deleted? %> + <%= subnav_link_to "Undelete", wiki_page_path(@wiki_page.id), remote: true, method: :put, "data-params": "wiki_page[is_deleted]=false", "data-shortcut": "shift+d", "data-confirm": "Are you sure you want to undelete this wiki?" %> + <% else %> + <%= subnav_link_to "Delete", wiki_page_path(@wiki_page.id), remote: true, method: :put, "data-params": "wiki_page[is_deleted]=true", "data-shortcut": "shift+d", "data-confirm": "Are you sure you want to delete this wiki?" %> + <% end %> <% end %> <% end %> <% elsif @wiki_page_version %> From f06e322dc0d86a7e12be65004b165928430fc65a Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 01:20:05 -0500 Subject: [PATCH 105/173] Fix #4507: Clicking "View wiki" on Artists with no wiki redirects to the posts page. --- app/views/posts/partials/index/_excerpt.html.erb | 8 +++++--- test/functional/posts_controller_test.rb | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/views/posts/partials/index/_excerpt.html.erb b/app/views/posts/partials/index/_excerpt.html.erb index bda961e4b..89d5636e4 100644 --- a/app/views/posts/partials/index/_excerpt.html.erb +++ b/app/views/posts/partials/index/_excerpt.html.erb @@ -16,8 +16,10 @@ <%= render "tag_relationships/alias_and_implication_list", tag: artist.tag %> <p class="links"> - <%= link_to "View wiki", artist.wiki_page %> | - <%= link_to "View artist", artist_path(artist.id) %> + <% if artist.wiki_page.present? %> + <%= link_to "View wiki", artist.wiki_page, id: "view-wiki-link" %> | + <% end %> + <%= link_to "View artist", artist, id: "view-artist-link" %> </p> </div> <% end %> @@ -32,7 +34,7 @@ <%= render "tag_relationships/alias_and_implication_list", tag: wiki_page.tag %> <p class="links"> - <%= link_to_wiki "View wiki", wiki_page.title %> + <%= link_to_wiki "View wiki", wiki_page.title, id: "view-wiki-link" %> </p> </div> <% end %> diff --git a/test/functional/posts_controller_test.rb b/test/functional/posts_controller_test.rb index 548ecacab..173798edd 100644 --- a/test/functional/posts_controller_test.rb +++ b/test/functional/posts_controller_test.rb @@ -34,6 +34,8 @@ class PostsControllerTest < ActionDispatch::IntegrationTest get posts_path, params: { tags: "bkub" } assert_response :success assert_select "#show-excerpt-link", count: 1, text: "Artist" + assert_select "#view-wiki-link", count: 0 + assert_select "#view-artist-link", count: 1 artist.update(is_banned: true) get posts_path, params: { tags: "bkub" } @@ -49,6 +51,8 @@ class PostsControllerTest < ActionDispatch::IntegrationTest get posts_path, params: { tags: "bkub" } assert_response :success assert_select "#show-excerpt-link", count: 1, text: "Wiki" + assert_select "#view-wiki-link", count: 1 + assert_select "#view-artist-link", count: 0 end should "render for a tag with a wiki page" do From ff096b8adc704d574545d8ffd780d4d828e216d2 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 02:01:34 -0500 Subject: [PATCH 106/173] Fix #4508: Specific tag not showing a wiki. Fix the wiki excerpt not appearing when searching for a tag that doesn't exist in the tag list. This could happen if someone created a wiki for a tag that has never been used on a post. --- app/logical/post_sets/post.rb | 5 ++--- test/functional/posts_controller_test.rb | 7 +++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/logical/post_sets/post.rb b/app/logical/post_sets/post.rb index 30d76717e..79585110b 100644 --- a/app/logical/post_sets/post.rb +++ b/app/logical/post_sets/post.rb @@ -24,9 +24,8 @@ module PostSets end def wiki_page - return nil unless tag.present? && tag.wiki_page.present? - return nil unless !tag.wiki_page.is_deleted? - tag.wiki_page + return nil unless normalized_query.has_single_tag? + @wiki_page ||= WikiPage.undeleted.find_by(title: normalized_query.tags.first.name) end def tag diff --git a/test/functional/posts_controller_test.rb b/test/functional/posts_controller_test.rb index 173798edd..41bdc1c88 100644 --- a/test/functional/posts_controller_test.rb +++ b/test/functional/posts_controller_test.rb @@ -96,6 +96,13 @@ class PostsControllerTest < ActionDispatch::IntegrationTest assert_response :success end + should "show the wiki excerpt for a wiki page without a tag" do + as(@user) { create(:wiki_page, title: "no_tag") } + get posts_path(tags: "no_tag") + assert_select "#show-excerpt-link", count: 1 + assert_select "#excerpt", count: 1 + end + should "show a notice for a single tag search with a pending BUR" do create(:bulk_update_request, script: "create alias foo -> bar") get_auth posts_path(tags: "foo"), @user From dd857c669546222171f2bc03180a2020f057564b Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 02:09:09 -0500 Subject: [PATCH 107/173] popular searches: fix blank search counts on /explore/posts/searches. --- app/controllers/explore/posts_controller.rb | 2 +- app/logical/reportbooru_service.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/explore/posts_controller.rb b/app/controllers/explore/posts_controller.rb index a3a47423c..1d411103b 100644 --- a/app/controllers/explore/posts_controller.rb +++ b/app/controllers/explore/posts_controller.rb @@ -28,7 +28,7 @@ module Explore def searches @date, @scale, @min_date, @max_date = parse_date(params) - @searches = ReportbooruService.new.popular_searches(@date) + @searches = ReportbooruService.new.post_search_rankings(@date) end def missed_searches diff --git a/app/logical/reportbooru_service.rb b/app/logical/reportbooru_service.rb index 9ae703367..898a67c98 100644 --- a/app/logical/reportbooru_service.rb +++ b/app/logical/reportbooru_service.rb @@ -30,6 +30,7 @@ class ReportbooruService def popular_searches(date, limit: 100) ranking = post_search_rankings(date) + ranking = post_search_rankings(date.yesterday) if ranking.blank? ranking.take(limit).map(&:first) end From 24e299cf9348762a7a47f40aa7ab26fbe3289ec7 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 02:09:51 -0500 Subject: [PATCH 108/173] api: add /explore/posts/searches.json, /explore/posts/missed_searches.json. --- app/controllers/explore/posts_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/explore/posts_controller.rb b/app/controllers/explore/posts_controller.rb index 1d411103b..f733bf6e1 100644 --- a/app/controllers/explore/posts_controller.rb +++ b/app/controllers/explore/posts_controller.rb @@ -29,10 +29,12 @@ module Explore def searches @date, @scale, @min_date, @max_date = parse_date(params) @searches = ReportbooruService.new.post_search_rankings(@date) + respond_with(@searches) end def missed_searches @missed_searches = ReportbooruService.new.missed_search_rankings + respond_with(@missed_searches) end private From da40a1cb735974215e3754fa6324d02fa3f36fbe Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 02:18:33 -0500 Subject: [PATCH 109/173] Fix #4463: Missing link to recent changes in pools' topbar. --- app/views/pools/_secondary_links.html.erb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/pools/_secondary_links.html.erb b/app/views/pools/_secondary_links.html.erb index 4a6b77711..d2b1ca5c9 100644 --- a/app/views/pools/_secondary_links.html.erb +++ b/app/views/pools/_secondary_links.html.erb @@ -5,6 +5,9 @@ <% if policy(Pool).create? %> <%= subnav_link_to "New", new_pool_path %> <% end %> + <% if PoolVersion.enabled? %> + <%= subnav_link_to "Recent changes", pool_versions_path %> + <% end %> <%= subnav_link_to "Help", wiki_page_path("help:pools") %> <% if @pool && !@pool.new_record? %> <li>|</li> From 09dff5b929570c3ba8167c312f60029ba228a2fa Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 11:52:19 -0500 Subject: [PATCH 110/173] ci: track coverage with codecov.io instead of codeclimate.com. Comparison: * Codecov has a simpler integration and a better UI. * Codeclimate tracks both linter warnings (Rubocop, ESLint) and code coverage, but its UI for code coverage is worse than Codecov's. * Codeclimate doesn't support Simplecov 0.18 because Codeclimate doesn't support 0.18's new coverage format yet. --- .codecov.yml | 1 + .github/workflows/test.yaml | 25 ++----------------------- .simplecov | 6 ++++++ Gemfile | 1 + Gemfile.lock | 6 ++++++ README.md | 2 +- 6 files changed, 17 insertions(+), 24 deletions(-) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..69cb76019 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1 @@ +comment: false diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 03a6c2d3f..3790dd084 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -34,10 +34,7 @@ jobs: RUBYOPT: -W0 # silence ruby warnings VIPS_WARNING: 0 # silence libvips warnings - # Code Climate configuration. https://docs.codeclimate.com/docs/finding-your-test-coverage-token - CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} - GIT_COMMIT_SHA: ${{ github.sha }} - GIT_BRANCH: ${{ github.ref }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} DATABASE_URL: postgresql://danbooru:danbooru@postgres/danbooru ARCHIVE_DATABASE_URL: postgresql://danbooru:danbooru@postgres/danbooru @@ -74,7 +71,7 @@ jobs: - name: Install OS dependencies run: | apt-get update - apt-get -y install --no-install-recommends build-essential ruby ruby-dev ruby-bundler git nodejs yarnpkg webpack ffmpeg mkvtoolnix libvips-dev libxml2-dev libxslt-dev zlib1g-dev postgresql-server-dev-all wget + apt-get -y install --no-install-recommends build-essential ruby ruby-dev ruby-bundler git nodejs yarnpkg webpack ffmpeg mkvtoolnix libvips-dev libxml2-dev libxslt-dev zlib1g-dev postgresql-server-dev-all wget curl ln -sf /usr/bin/yarnpkg /usr/bin/yarn - name: Install Ruby dependencies @@ -86,23 +83,5 @@ jobs: - name: Prepare database run: config/docker/prepare-tests.sh - # https://docs.codeclimate.com/docs/configuring-test-coverage - - name: Prepare test coverage for Code Climate - run: | - wget https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 - chmod +x test-reporter-latest-linux-amd64 - ./test-reporter-latest-linux-amd64 before-build - - name: Run tests run: bin/rails test - - - name: Upload test coverage to Code Climate - if: ${{ !cancelled() }} - run: GIT_COMMITTED_AT="$(date -u +%s)" ./test-reporter-latest-linux-amd64 after-build --debug - - # https://docs.codecov.io/docs - - name: Upload test coverage to Codecov.io - if: ${{ !cancelled() }} - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.simplecov b/.simplecov index 628307e3c..66536caa3 100644 --- a/.simplecov +++ b/.simplecov @@ -5,4 +5,10 @@ SimpleCov.start "rails" do #minimum_coverage line: 85, branch: 75 #minimum_coverage_by_file 50 #coverage_dir "tmp/coverage" + + # https://github.com/codecov/codecov-ruby#submit-only-in-ci-example + if ENV["CODECOV_TOKEN"] + require "codecov" + SimpleCov.formatter = SimpleCov::Formatter::Codecov + end end diff --git a/Gemfile b/Gemfile index 86bb62c2d..18bd95879 100644 --- a/Gemfile +++ b/Gemfile @@ -86,4 +86,5 @@ group :test do gem "mock_redis" gem "capybara" gem "selenium-webdriver" + gem "codecov", require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 9ae511510..e7c754a04 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -120,6 +120,10 @@ GEM xpath (~> 3.2) childprocess (3.0.0) chronic (0.10.2) + codecov (0.1.17) + json + simplecov + url coderay (1.1.3) concurrent-ruby (1.1.6) crass (1.0.6) @@ -367,6 +371,7 @@ GEM unicorn-worker-killer (0.4.4) get_process_mem (~> 0) unicorn (>= 4, < 6) + url (0.3.2) webpacker (5.1.1) activesupport (>= 5.2) rack-proxy (>= 0.6.1) @@ -399,6 +404,7 @@ DEPENDENCIES capistrano-rbenv capistrano3-unicorn capybara + codecov daemons delayed_job delayed_job_active_record diff --git a/README.md b/README.md index 6685ad20b..b6834fdff 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Test Coverage](https://api.codeclimate.com/v1/badges/a51f46fb460a35104d82/test_coverage)](https://codeclimate.com/github/danbooru/danbooru/test_coverage) [![Discord](https://img.shields.io/discord/310432830138089472?label=Discord)](https://discord.gg/eSVKkUF) +[![codecov](https://codecov.io/gh/danbooru/danbooru/branch/master/graph/badge.svg)](https://codecov.io/gh/danbooru/danbooru) [![Discord](https://img.shields.io/discord/310432830138089472?label=Discord)](https://discord.gg/eSVKkUF) ## Installation From 6020277608b714ddf95294d0b94a1f37cfa4817a Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 12:36:44 -0500 Subject: [PATCH 111/173] Update simplecov gem. --- .simplecov | 6 ++---- Gemfile | 2 +- Gemfile.lock | 9 ++++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.simplecov b/.simplecov index 66536caa3..e527eadf6 100644 --- a/.simplecov +++ b/.simplecov @@ -1,10 +1,8 @@ SimpleCov.start "rails" do add_group "Libraries", ["app/logical", "lib"] add_group "Presenters", "app/presenters" - #enable_coverage :branch - #minimum_coverage line: 85, branch: 75 - #minimum_coverage_by_file 50 - #coverage_dir "tmp/coverage" + add_group "Policies", "app/policies" + enable_coverage :branch # https://github.com/codecov/codecov-ruby#submit-only-in-ci-example if ENV["CODECOV_TOKEN"] diff --git a/Gemfile b/Gemfile index 18bd95879..02cde7ccd 100644 --- a/Gemfile +++ b/Gemfile @@ -80,7 +80,7 @@ group :test do gem "factory_bot" gem "mocha", require: "mocha/minitest" gem "ffaker" - gem "simplecov", "~> 0.17.0", require: false + gem "simplecov", require: false gem "minitest-ci" gem "minitest-reporters", require: "minitest/reporters" gem "mock_redis" diff --git a/Gemfile.lock b/Gemfile.lock index e7c754a04..918dd045c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -337,11 +337,10 @@ GEM simple_form (5.0.2) actionpack (>= 5.0) activemodel (>= 5.0) - simplecov (0.17.1) + simplecov (0.18.5) docile (~> 1.1) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.2) + simplecov-html (~> 0.11) + simplecov-html (0.12.2) sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -454,7 +453,7 @@ DEPENDENCIES shoulda-context shoulda-matchers simple_form - simplecov (~> 0.17.0) + simplecov stackprof streamio-ffmpeg stripe From 63ca421f3e77ce2ca96cafc73f8630ddbd0410b0 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 13:05:36 -0500 Subject: [PATCH 112/173] comments: fix exception in /comments/new page. Fix regression from 804a2ef9a. --- app/views/comments/_form.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/comments/_form.html.erb b/app/views/comments/_form.html.erb index 3144f6cfe..0b643d6b0 100644 --- a/app/views/comments/_form.html.erb +++ b/app/views/comments/_form.html.erb @@ -1,6 +1,6 @@ <%= error_messages_for :comment %> -<%= edit_form_for(comment, namespace: "post_#{comment.post.id}_comment_#{comment.id || "new"}", html: { style: ("display: none;" if local_assigns[:hidden]), class: "edit_comment" }) do |f| %> +<%= edit_form_for(comment, namespace: "post_#{comment&.post_id}_comment_#{comment.id || "new"}", html: { style: ("display: none;" if local_assigns[:hidden]), class: "edit_comment" }) do |f| %> <% if comment.new_record? %> <%= f.hidden_field :post_id %> <% end %> From e2ec01560301f950925c4a2ba05f80156d5a612a Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 13:23:25 -0500 Subject: [PATCH 113/173] seo: index frontpage, images, and legacy redirects. --- app/views/robots/index.text.erb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/views/robots/index.text.erb b/app/views/robots/index.text.erb index c7f1c0983..1f9ebe910 100644 --- a/app/views/robots/index.text.erb +++ b/app/views/robots/index.text.erb @@ -4,12 +4,18 @@ User-agent: * Disallow: / <% if Rails.env.production? && Danbooru.config.hostname == request.host %> +Disallow: /*.atom +Disallow: /*.json +Disallow: /*.xml + +Allow: /$ Allow: /artists Allow: /artist_commentaries Allow: /comments Allow: /explore Allow: /forum_posts Allow: /forum_topics +Allow: /login Allow: /notes Allow: /pools Allow: /posts @@ -20,4 +26,22 @@ Allow: /uploads Allow: /user_upgrades Allow: /users Allow: /wiki_pages + +<%# Legacy redirects %> +Allow: /artist +Allow: /comment +Allow: /forum +Allow: /note +Allow: /pool +Allow: /post +Allow: /tag +Allow: /user +Allow: /wiki + +<%# Images %> +Allow: /crop +Allow: /preview +Allow: /original +Allow: /sample +Allow: /data <% end %> From c739e2b226431664985f83914d5a0fd8ebdbed6a Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 18:10:14 -0500 Subject: [PATCH 114/173] seo: allow crawling /posts up to page 50. --- app/logical/post_sets/post.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logical/post_sets/post.rb b/app/logical/post_sets/post.rb index 79585110b..b6ed3c379 100644 --- a/app/logical/post_sets/post.rb +++ b/app/logical/post_sets/post.rb @@ -111,7 +111,7 @@ module PostSets end def hide_from_crawler? - return true if current_page > 1 + return true if current_page > 50 return false if query.is_empty_search? || query.is_simple_tag? || query.is_metatag?(:order, :rank) true end From 580211ee64ea0978f226589c6615a66666b7173f Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 19:07:34 -0500 Subject: [PATCH 115/173] seo: fix canonical tags on post index and show page. * Fix incorrect canonical tags. Before we were using `<meta name="canonical" content="...">`. This is wrong, it should have been `<link rel="canonical" href="...">`. * Add a default canonical tag on all pages. Fixes Google treating the same content on different subdomains (safebooru, shima, kagamihara, etc) as duplicate content. Also fixes Google sometimes treating similar but distinct content on the same domain as duplicate content. --- app/helpers/application_helper.rb | 13 ++++++++ app/views/layouts/default.html.erb | 1 + .../partials/index/_seo_meta_tags.html.erb | 6 ++-- app/views/posts/show.html.erb | 5 ++- test/functional/posts_controller_test.rb | 33 ++++++++++++++++--- test/test_helper.rb | 1 + 6 files changed, 49 insertions(+), 10 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 7c010de1d..4ad0202d8 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -340,6 +340,19 @@ module ApplicationHelper end end + def canonical_url(url = nil) + if url.present? + content_for(:canonical_url) { url } + elsif content_for(:canonical_url).present? + content_for(:canonical_url) + else + request_params = request.params.sort.to_h.with_indifferent_access + request_params.delete(:page) if request_params[:page].to_i == 1 + request_params.delete(:limit) + url_for(**request_params, host: Danbooru.config.hostname, only_path: false) + end + end + def atom_feed_tag(title, url = {}) content_for(:html_header, auto_discovery_link_tag(:atom, url, title: title)) end diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index bf1a94442..ea2c05e17 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -5,6 +5,7 @@ <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"> <%= render "meta_links", collection: @current_item %> + <%= tag.link rel: "canonical", href: canonical_url %> <%= csrf_meta_tag %> <% unless CurrentUser.enable_desktop_mode? %> diff --git a/app/views/posts/partials/index/_seo_meta_tags.html.erb b/app/views/posts/partials/index/_seo_meta_tags.html.erb index 8fbfe0a8c..1c58f8d5a 100644 --- a/app/views/posts/partials/index/_seo_meta_tags.html.erb +++ b/app/views/posts/partials/index/_seo_meta_tags.html.erb @@ -10,6 +10,10 @@ <% atom_feed_tag "Posts: #{@post_set.tag_string}", posts_url(tags: @post_set.tag_string, format: :atom) %> <% end %> +<% if params[:tags].blank? && @post_set.current_page == 1 %> + <% canonical_url root_url(host: Danbooru.config.hostname) %> +<% end %> + <% if @post_set.hide_from_crawler? %> <meta name="robots" content="nofollow,noindex"> <% end %> @@ -18,8 +22,6 @@ <meta name="rating" content="adult"> <% end %> -<%= tag.meta name: "canonical", content: posts_url(tags: params[:tags], host: Danbooru.config.hostname, protocol: "https") %> - <% if @post_set.best_post.present? %> <%= tag.meta property: "og:image", content: @post_set.best_post.open_graph_image_url %> <%= tag.meta name: "twitter:image", content: @post_set.best_post.open_graph_image_url %> diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb index 3aa5e7798..9a4112cfe 100644 --- a/app/views/posts/show.html.erb +++ b/app/views/posts/show.html.erb @@ -1,7 +1,8 @@ <% page_title @post.presenter.humanized_essential_tag_string %> <% meta_description "View this #{@post.image_width}x#{@post.image_height} #{number_to_human_size(@post.file_size)} image" %> - +<% canonical_url post_url(@post, host: Danbooru.config.hostname) %> <% atom_feed_tag "Comments for post ##{@post.id}", comments_url(:atom, search: { post_id: @post.id }) %> + <%= render "posts/partials/common/secondary_links" %> <% content_for(:sidebar) do %> @@ -148,8 +149,6 @@ <%= tag.meta property: "og:image", content: @post.open_graph_image_url %> <% end %> - <%= tag.meta name: "canonical", content: post_url(@post, host: Danbooru.config.hostname, protocol: "https") %> - <% if @post.twitter_card_supported? %> <meta name="twitter:card" content="summary_large_image"> diff --git a/test/functional/posts_controller_test.rb b/test/functional/posts_controller_test.rb index 41bdc1c88..1e673a532 100644 --- a/test/functional/posts_controller_test.rb +++ b/test/functional/posts_controller_test.rb @@ -1,6 +1,10 @@ require "test_helper" class PostsControllerTest < ActionDispatch::IntegrationTest + def assert_canonical_url_equals(expected) + assert_equal(expected, response.parsed_body.css("link[rel=canonical]").attribute("href").value) + end + context "The posts controller" do setup do @user = travel_to(1.month.ago) {create(:user)} @@ -10,11 +14,29 @@ class PostsControllerTest < ActionDispatch::IntegrationTest context "index action" do setup do mock_post_search_rankings(Date.today, [["1girl", 100], ["original", 50]]) + create_list(:post, 2) end - should "render" do - get posts_path - assert_response :success + context "for an empty search" do + should "render the first page" do + get root_path + assert_response :success + assert_canonical_url_equals(root_url(host: Danbooru.config.hostname)) + + get posts_path + assert_response :success + assert_canonical_url_equals(root_url(host: Danbooru.config.hostname)) + + get posts_path(page: 1) + assert_response :success + assert_canonical_url_equals(root_url(host: Danbooru.config.hostname)) + end + + should "render the second page" do + get posts_path(page: 2, limit: 1) + assert_response :success + assert_canonical_url_equals(posts_url(page: 2, host: Danbooru.config.hostname)) + end end context "with a single tag search" do @@ -22,6 +44,7 @@ class PostsControllerTest < ActionDispatch::IntegrationTest get posts_path, params: { tags: "does_not_exist" } assert_response :success assert_select "#show-excerpt-link", count: 0 + assert_canonical_url_equals(posts_url(tags: "does_not_exist", host: Danbooru.config.hostname)) end should "render for an artist tag" do @@ -261,7 +284,7 @@ class PostsControllerTest < ActionDispatch::IntegrationTest get posts_path(format: :atom) assert_response :success - assert_select "entry", 1 + assert_select "entry", 3 end should "render with tags" do @@ -272,7 +295,7 @@ class PostsControllerTest < ActionDispatch::IntegrationTest end should "hide restricted posts" do - @post.update(is_banned: true) + Post.update_all(is_banned: true) get posts_path(format: :atom) assert_response :success diff --git a/test/test_helper.rb b/test/test_helper.rb index 892046f7e..6fed3f46b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -64,6 +64,7 @@ class ActionDispatch::IntegrationTest extend ControllerHelper register_encoder :xml, response_parser: ->(body) { Nokogiri.XML(body) } + register_encoder :html, response_parser: ->(body) { Nokogiri.HTML5(body) } def method_authenticated(method_name, url, user, **options) post session_path, params: { name: user.name, password: user.password } From 136bb9d8aebc2e4605f80214c780b0505829c3df Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sat, 27 Jun 2020 19:55:49 -0500 Subject: [PATCH 116/173] ci: fix codecov.io uploader not working. We need to check out the full repo so the codecov.io uploader can detect the branch and commit hash. --- .github/workflows/test.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3790dd084..4dd39cc50 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -65,15 +65,17 @@ jobs: POSTGRES_PASSWORD: danbooru steps: - - name: Check out code - uses: actions/checkout@v2 - - name: Install OS dependencies run: | apt-get update - apt-get -y install --no-install-recommends build-essential ruby ruby-dev ruby-bundler git nodejs yarnpkg webpack ffmpeg mkvtoolnix libvips-dev libxml2-dev libxslt-dev zlib1g-dev postgresql-server-dev-all wget curl + apt-get -y install --no-install-recommends build-essential ruby ruby-dev ruby-bundler git nodejs yarnpkg webpack ffmpeg mkvtoolnix libvips-dev libxml2-dev libxslt-dev zlib1g-dev postgresql-server-dev-all wget curl git ln -sf /usr/bin/yarnpkg /usr/bin/yarn + - name: Check out code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Install Ruby dependencies run: BUNDLE_DEPLOYMENT=true bundle install --jobs 4 From cc73f2468b28dbd13dbe4d554d1754ed12821201 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 28 Jun 2020 00:39:31 -0500 Subject: [PATCH 117/173] disapprovals: don't dmail uploaders about disapprovals. Remove sending dmail notifications to uploaders when an upload is disapproved. These messages are usually confusing and frustrating to uploaders. They don't know who is sending them and they usually feel insulted when they get negative messages from anonymous users. --- app/logical/danbooru_maintenance.rb | 1 - app/models/post_disapproval.rb | 18 ------------------ test/unit/post_disapproval_test.rb | 27 --------------------------- 3 files changed, 46 deletions(-) diff --git a/app/logical/danbooru_maintenance.rb b/app/logical/danbooru_maintenance.rb index 330ba8c0f..d03339344 100644 --- a/app/logical/danbooru_maintenance.rb +++ b/app/logical/danbooru_maintenance.rb @@ -9,7 +9,6 @@ module DanbooruMaintenance safely { Upload.prune! } safely { Delayed::Job.where('created_at < ?', 45.days.ago).delete_all } safely { PostDisapproval.prune! } - safely { PostDisapproval.dmail_messages! } safely { regenerate_post_counts! } safely { TokenBucket.prune! } safely { BulkUpdateRequestPruner.warn_old } diff --git a/app/models/post_disapproval.rb b/app/models/post_disapproval.rb index 6e594c4cb..bf00344fe 100644 --- a/app/models/post_disapproval.rb +++ b/app/models/post_disapproval.rb @@ -18,24 +18,6 @@ class PostDisapproval < ApplicationRecord PostDisapproval.where("post_id in (select _.post_id from post_disapprovals _ where _.created_at < ?)", DELETION_THRESHOLD.ago).delete_all end - def self.dmail_messages! - disapprovals = PostDisapproval.with_message.where("created_at >= ?", 1.day.ago).group_by do |pd| - pd.post.uploader - end - - disapprovals.each do |uploader, list| - message = list.map do |x| - "* post ##{x.post_id}: #{x.message}" - end.join("\n") - - Dmail.create_automated( - :to_id => uploader.id, - :title => "Someone has commented on your uploads", - :body => message - ) - end - end - concerning :SearchMethods do class_methods do def search(params) diff --git a/test/unit/post_disapproval_test.rb b/test/unit/post_disapproval_test.rb index f16203e48..4e16f401e 100644 --- a/test/unit/post_disapproval_test.rb +++ b/test/unit/post_disapproval_test.rb @@ -67,33 +67,6 @@ class PostDisapprovalTest < ActiveSupport::TestCase end end - context "when sending dmails" do - setup do - @uploaders = FactoryBot.create_list(:user, 2, created_at: 2.weeks.ago) - @disapprovers = FactoryBot.create_list(:mod_user, 2) - - # 2 uploaders, with 2 uploads each, and 2 disapprovals on each upload. - @uploaders.each do |uploader| - FactoryBot.create_list(:post, 2, is_pending: true, uploader: uploader).each do |post| - FactoryBot.create(:post_disapproval, post: post, user: @disapprovers[0]) - FactoryBot.create(:post_disapproval, post: post, user: @disapprovers[1]) - end - end - end - - should "dmail the uploaders" do - bot = FactoryBot.create(:user) - User.stubs(:system).returns(bot) - - assert_difference(["@uploaders[0].dmails.count", "@uploaders[1].dmails.count"], 1) do - PostDisapproval.dmail_messages! - end - - assert(@uploaders[0].dmails.exists?(from: bot, to: @uploaders[0])) - assert(@uploaders[1].dmails.exists?(from: bot, to: @uploaders[1])) - end - end - context "#search" do should "work" do disapproval1 = FactoryBot.create(:post_disapproval, user: @alice, post: @post_1, reason: "breaks_rules") From ad02b26f3dfa4d47ff8377e894bcb67c677ed707 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 28 Jun 2020 02:45:05 -0500 Subject: [PATCH 118/173] pagination: fix exception on empty pages in seq. pagination. Fix exception when the page is empty during sequential pagination. Caused because the paginator can't figure out the next or previous page when the current page is empty. * https://danbooru.donmai.us/posts?page=b0 * https://danbooru.donmai.us/posts?page=a10000000 --- app/views/application/_meta_links.html.erb | 12 ++++---- test/functional/posts_controller_test.rb | 34 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/app/views/application/_meta_links.html.erb b/app/views/application/_meta_links.html.erb index 7b398ed83..d0ddc7cd4 100644 --- a/app/views/application/_meta_links.html.erb +++ b/app/views/application/_meta_links.html.erb @@ -1,11 +1,13 @@ <%# collection %> <% content_for(:html_header) do %> - <% if collection.try(:prev_page) %> - <%= tag.link rel: "prev", href: url_for(nav_params_for(collection.prev_page)) %> - <% end %> + <% if collection.try(:records).present? %> + <% if collection.try(:prev_page) %> + <%= tag.link rel: "prev", href: url_for(nav_params_for(collection.prev_page)) %> + <% end %> - <% if collection.try(:next_page) %> - <%= tag.link rel: "next", href: url_for(nav_params_for(collection.next_page)) %> + <% if collection.try(:next_page) %> + <%= tag.link rel: "next", href: url_for(nav_params_for(collection.next_page)) %> + <% end %> <% end %> <% end %> diff --git a/test/functional/posts_controller_test.rb b/test/functional/posts_controller_test.rb index 1e673a532..a1c7d3851 100644 --- a/test/functional/posts_controller_test.rb +++ b/test/functional/posts_controller_test.rb @@ -17,6 +17,40 @@ class PostsControllerTest < ActionDispatch::IntegrationTest create_list(:post, 2) end + context "when using sequential pagination" do + should "work with page=a0" do + get posts_path(page: "a0") + assert_response :success + assert_select ".post-preview", count: 3 + assert_select "#paginator-prev", count: 0 + assert_select "#paginator-next", count: 1 + end + + should "work with page=b0" do + get posts_path(page: "b0") + assert_response :success + assert_select ".post-preview", count: 0 + assert_select "#paginator-prev", count: 0 + assert_select "#paginator-next", count: 0 + end + + should "work with page=b100000" do + get posts_path(page: "b100000") + assert_response :success + assert_select ".post-preview", count: 3 + assert_select "#paginator-prev", count: 1 + assert_select "#paginator-next", count: 0 + end + + should "work with page=a100000" do + get posts_path(page: "a100000") + assert_response :success + assert_select ".post-preview", count: 0 + assert_select "#paginator-prev", count: 0 + assert_select "#paginator-next", count: 0 + end + end + context "for an empty search" do should "render the first page" do get root_path From b85b4b190ac67cad9e7a19c7a81e5f78811f6353 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Sun, 28 Jun 2020 02:59:32 -0500 Subject: [PATCH 119/173] pagination: remove switch_to_sequential option. --- app/helpers/pagination_helper.rb | 4 ++-- app/views/artist_versions/index.html.erb | 2 +- app/views/tag_aliases/index.html.erb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb index 1930adf07..7e11f41a7 100644 --- a/app/helpers/pagination_helper.rb +++ b/app/helpers/pagination_helper.rb @@ -20,8 +20,8 @@ module PaginationHelper params[:page] =~ /[ab]/ || records.current_page >= Danbooru.config.max_numbered_pages end - def numbered_paginator(records, switch_to_sequential = true) - if use_sequential_paginator?(records) && switch_to_sequential + def numbered_paginator(records) + if use_sequential_paginator?(records) return sequential_paginator(records) end diff --git a/app/views/artist_versions/index.html.erb b/app/views/artist_versions/index.html.erb index 9c4d4fe06..0e8559b0f 100644 --- a/app/views/artist_versions/index.html.erb +++ b/app/views/artist_versions/index.html.erb @@ -6,7 +6,7 @@ <%= render "listing" %> - <%= numbered_paginator(@artist_versions, :search_count => params[:search]) %> + <%= numbered_paginator(@artist_versions) %> </div> </div> diff --git a/app/views/tag_aliases/index.html.erb b/app/views/tag_aliases/index.html.erb index 3d8fdfe58..c28772782 100644 --- a/app/views/tag_aliases/index.html.erb +++ b/app/views/tag_aliases/index.html.erb @@ -3,7 +3,7 @@ <%= render "tag_relationships/search", url: tag_aliases_path %> <%= render "listing", :tag_aliases => @tag_aliases %> - <%= numbered_paginator(@tag_aliases) if @tag_aliases.respond_to?(:current_page) %> + <%= numbered_paginator(@tag_aliases) %> </div> </div> From f5c9a78797abffb299bdf7bcee59ee8680fd8a48 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Mon, 29 Jun 2020 14:14:41 -0500 Subject: [PATCH 120/173] danbooru::http: fix SSLError exceptions not being caught. Bug: The frontpage failed due to a SSL error. We couldn't fetch the popular tag list from Reportbooru because Reportbooru's SSL certificate had expired and HTTP.rb raised an SSLError exception that we didn't catch. Fix: Convert the SSLError to a 5xx HTTP error to prevent SSL exceptions from leaking through HTTP.rb. --- app/logical/danbooru/http.rb | 10 ++++++++-- test/unit/danbooru_http_test.rb | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/logical/danbooru/http.rb b/app/logical/danbooru/http.rb index 43530522e..6147218f7 100644 --- a/app/logical/danbooru/http.rb +++ b/app/logical/danbooru/http.rb @@ -120,11 +120,17 @@ module Danbooru def request(method, url, **options) http.send(method, url, **options) + rescue OpenSSL::SSL::SSLError + fake_response(590, "") rescue ValidatingSocket::ProhibitedIpError - fake_response(597, "") + fake_response(591, "") rescue HTTP::Redirector::TooManyRedirectsError - fake_response(598, "") + fake_response(596, "") rescue HTTP::TimeoutError + fake_response(597, "") + rescue HTTP::ConnectionError + fake_response(598, "") + rescue HTTP::Error fake_response(599, "") end diff --git a/test/unit/danbooru_http_test.rb b/test/unit/danbooru_http_test.rb index 08ad71f64..a981bc0ea 100644 --- a/test/unit/danbooru_http_test.rb +++ b/test/unit/danbooru_http_test.rb @@ -19,21 +19,31 @@ class DanbooruHttpTest < ActiveSupport::TestCase should "fail if redirected too many times" do skip "Skipping test (https://github.com/postmanlabs/httpbin/issues/617)" response = Danbooru::Http.get("https://httpbin.org/absolute-redirect/10") - assert_equal(598, response.status) + assert_equal(596, response.status) end should "fail if the request takes too long to connect" do response = Danbooru::Http.timeout(1).get("https://httpbin.org/delay/5") - assert_equal(599, response.status) + assert_equal(597, response.status) end should "fail if the request takes too long to download" do - # XXX should return status 599 instead + # XXX should return status 597 instead assert_raises(HTTP::TimeoutError) do response = Danbooru::Http.timeout(1).get("https://httpbin.org/drip?duration=10&numbytes=10").flush end end + should "return a 5xx error if the domain can't be resolved" do + response = Danbooru::Http.get("http://doesnotexist.donmai.us") + assert_equal(598, response.status) + end + + should "return a 5xx error if the SSL certificate is expired" do + response = Danbooru::Http.get("https://expired.badssl.com") + assert_equal(590, response.status) + end + should "automatically decompress gzipped responses" do response = Danbooru::Http.get("https://httpbin.org/gzip") assert_equal(200, response.status) From fde6e3921357d143b7656fb8c8ef3234ff2ebdc1 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Mon, 29 Jun 2020 17:35:21 -0500 Subject: [PATCH 121/173] Update ruby gems and yarn packages. --- Gemfile.lock | 20 ++++----- yarn.lock | 122 ++++++++++++++++++--------------------------------- 2 files changed, 53 insertions(+), 89 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 918dd045c..f69dc9815 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,16 +77,16 @@ GEM ansi (1.5.0) ast (2.4.1) aws-eventstream (1.1.0) - aws-partitions (1.332.0) - aws-sdk-core (3.100.0) + aws-partitions (1.337.0) + aws-sdk-core (3.102.1) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-sqs (1.27.1) + aws-sdk-sqs (1.29.0) aws-sdk-core (~> 3, >= 3.99.0) aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.0) + aws-sigv4 (1.2.1) aws-eventstream (~> 1, >= 1.0.2) bcrypt (3.1.13) bootsnap (1.4.6) @@ -133,7 +133,7 @@ GEM delayed_job_active_record (4.1.4) activerecord (>= 3.0, < 6.1) delayed_job (>= 3.0, < 5) - diff-lcs (1.3) + diff-lcs (1.4.3) docile (1.3.2) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) @@ -206,7 +206,7 @@ GEM minitest (>= 5.0) ruby-progressbar mocha (1.11.2) - mock_redis (0.24.0) + mock_redis (0.25.0) msgpack (1.3.3) msgpack (1.3.3-x64-mingw32) multi_json (1.14.1) @@ -301,16 +301,16 @@ GEM railties (>= 5.0) retriable (3.1.2) rexml (3.2.4) - rubocop (0.85.1) + rubocop (0.86.0) parallel (~> 1.10) parser (>= 2.7.0.1) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.7) rexml - rubocop-ast (>= 0.0.3) + rubocop-ast (>= 0.0.3, < 1.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.0.3) + rubocop-ast (0.1.0) parser (>= 2.7.0.1) rubocop-rails (2.6.0) activesupport (>= 4.2.0) @@ -383,7 +383,7 @@ GEM chronic (>= 0.6.3) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.3.0) + zeitwerk (2.3.1) PLATFORMS ruby diff --git a/yarn.lock b/yarn.lock index c8abadcae..b28f64d11 100644 --- a/yarn.lock +++ b/yarn.lock @@ -923,9 +923,9 @@ integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= "@types/node@*": - version "14.0.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.13.tgz#ee1128e881b874c371374c1f72201893616417c9" - integrity sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA== + version "14.0.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce" + integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -1393,13 +1393,13 @@ atob@^2.1.2: integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== autoprefixer@^9.6.1, autoprefixer@^9.8.0: - version "9.8.2" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.2.tgz#7347396ee576b18687041bfbacd76d78e27baa56" - integrity sha512-9UwMMU8Rg7Fj0c55mbOpXrr/2WrRqoOwOlLNTyyYt+nhiyQdIBWipp5XWzt+Lge8r3DK5y+EHMc1OBf8VpZA6Q== + version "9.8.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.4.tgz#736f1012673a70fa3464671d78d41abd54512863" + integrity sha512-84aYfXlpUe45lvmS+HoAWKCkirI/sw4JK0/bTeeqgHYco3dcsOn0NqdejISjptsYwNji/21dnkDri9PsYKk89A== dependencies: browserslist "^4.12.0" - caniuse-lite "^1.0.30001084" - kleur "^4.0.1" + caniuse-lite "^1.0.30001087" + colorette "^1.2.0" normalize-range "^0.1.2" num2fraction "^1.2.2" postcss "^7.0.32" @@ -1510,9 +1510,9 @@ binary-extensions@^1.0.0: integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== + version "2.1.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" + integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== bindings@^1.5.0: version "1.5.0" @@ -1682,14 +1682,14 @@ browserify-zlib@^0.2.0: pako "~1.0.5" browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.6.4, browserslist@^4.8.5: - version "4.12.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" - integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== + version "4.12.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.2.tgz#76653d7e4c57caa8a1a28513e2f4e197dc11a711" + integrity sha512-MfZaeYqR8StRZdstAK9hCKDd2StvePCYp5rHzQCPicUjfFliDgmuaBNPHYUTpAywBN8+Wc/d7NYVFkO0aqaBUw== dependencies: - caniuse-lite "^1.0.30001043" - electron-to-chromium "^1.3.413" - node-releases "^1.1.53" - pkg-up "^2.0.0" + caniuse-lite "^1.0.30001088" + electron-to-chromium "^1.3.483" + escalade "^3.0.1" + node-releases "^1.1.58" buffer-alloc-unsafe@^1.1.0: version "1.1.0" @@ -1887,10 +1887,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001084: - version "1.0.30001085" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001085.tgz#bed28bd51ff7425d33ee23e730c7f3b703711db6" - integrity sha512-x0YRFRE0pmOD90z+9Xk7jwO58p4feVNXP+U8kWV+Uo/HADyrgESlepzIkUqPgaXkpyceZU6siM1gsK7sHgplqA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001087, caniuse-lite@^1.0.30001088: + version "1.0.30001090" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001090.tgz#ff7766332f60e80fea4903f30d360622e5551850" + integrity sha512-QzPRKDCyp7RhjczTPZaqK3CjPA5Ht2UnXhZhCI4f7QiB5JK6KEuZBxIzyWnB3wO4hgAj4GMRxAhuiacfw0Psjg== case-sensitive-paths-webpack-plugin@^2.3.0: version "2.3.0" @@ -2134,6 +2134,11 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" +colorette@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.0.tgz#45306add826d196e8c87236ac05d797f25982e63" + integrity sha512-soRSroY+OF/8OdA3PTQXwaDJeMc7TfknKKrxeSCencL2a4+Tx5zhxmmv7hdpCjhKBjehzp8+bwe/T68K0hpIjw== + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -2923,10 +2928,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.413: - version "1.3.480" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.480.tgz#190ae45074578349a4c4f336fba29e76b20e9ef5" - integrity sha512-wnuUfQCBMAdzu5Xe+F4FjaRK+6ToG6WvwG72s8k/3E6b+hoGVYGiQE7JD1NhiCMcqF3+wV+c2vAnaLGRSSWVqA== +electron-to-chromium@^1.3.483: + version "1.3.483" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.483.tgz#9269e7cfc1c8e72709824da171cbe47ca5e3ca9e" + integrity sha512-+05RF8S9rk8S0G8eBCqBRBaRq7+UN3lDs2DAvnG8SBSgQO3hjy0+qt4CmRk5eiuGbTcaicgXfPmBi31a+BD3lg== elliptic@^6.0.0, elliptic@^6.5.2: version "6.5.3" @@ -3044,6 +3049,11 @@ es6-object-assign@^1.0.3: resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= +escalade@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.1.tgz#52568a77443f6927cd0ab9c73129137533c965ed" + integrity sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -3113,9 +3123,9 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2 integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== eslint@^7.0.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.3.0.tgz#f9f1fc3dc1227985d0db88769f2bbac7b4b875d7" - integrity sha512-dJMVXwfU5PT1cj2Nv2VPPrKahKTGdX+5Dh0Q3YuKt+Y2UhdL2YbzsVaBMyG9HC0tBismlv/r1+eZqs6SMIV38Q== + version "7.3.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.3.1.tgz#76392bd7e44468d046149ba128d1566c59acbe19" + integrity sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -3508,13 +3518,6 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= - dependencies: - locate-path "^2.0.0" - find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -4721,9 +4724,9 @@ jquery@>=1.6.0: integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== js-base64@^2.1.8: - version "2.6.1" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.1.tgz#c328374225d2e65569791ded73c258e2c59334c7" - integrity sha512-G5x2saUTupU9D/xBY9snJs3TxvwX8EkpLFiYlPpDt/VmMHOXprnSU1nxiTmFbijCX4BLF/cMRIfAcC5BiMYgFQ== + version "2.6.2" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.2.tgz#cf9301bc5cc756892a9a6c8d7138322e5944fb0d" + integrity sha512-1hgLrLIrmCgZG+ID3VoLNLOSwjGnoZa8tyrUdEteMeIzsT6PH7PMLyUvbDwzNE56P3PNxyvuIOx4Uh2E5rzQIw== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -4856,11 +4859,6 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -kleur@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.0.1.tgz#3d4948534b666e2578f93b6fafb62108e64f05ef" - integrity sha512-Qs6SqCLm63rd0kNVh+wO4XsWLU6kgfwwaPYsLiClWf0Tewkzsa6MvB21bespb8cz+ANS+2t3So1ge3gintzhlw== - known-css-properties@^0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.19.0.tgz#5d92b7fa16c72d971bda9b7fe295bdf61836ee5b" @@ -4950,14 +4948,6 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -5566,7 +5556,7 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-releases@^1.1.53: +node-releases@^1.1.58: version "1.1.58" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.58.tgz#8ee20eef30fa60e52755fcc0942def5a734fe935" integrity sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg== @@ -5908,13 +5898,6 @@ p-is-promise@^2.0.0: resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== - dependencies: - p-try "^1.0.0" - p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -5922,13 +5905,6 @@ p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.3.0: dependencies: p-try "^2.0.0" -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= - dependencies: - p-limit "^1.1.0" - p-locate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" @@ -5962,11 +5938,6 @@ p-retry@^3.0.1: dependencies: retry "^0.12.0" -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= - p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -6200,13 +6171,6 @@ pkg-dir@^4.1.0: dependencies: find-up "^4.0.0" -pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" - integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= - dependencies: - find-up "^2.1.0" - pnp-webpack-plugin@^1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" From 048bc7faf5591e8087ef69169e2a60803e98d01b Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 30 Jun 2020 13:03:51 -0500 Subject: [PATCH 122/173] Revert "simple form: enable HTML5 maxlength validations." This incorrectly added maxlength=4 to parent id fields. This reverts commit 5e23861bea7132c07b79147b7bcd716678fcba4a. --- config/initializers/simple_form.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb index d95d9c4a2..2aaf05ca0 100644 --- a/config/initializers/simple_form.rb +++ b/config/initializers/simple_form.rb @@ -39,7 +39,7 @@ SimpleForm.setup do |config| # Calculates maxlength from length validations for string inputs # and/or database column lengths - b.use :maxlength + b.optional :maxlength # Calculate minlength from length validations for string inputs b.optional :minlength @@ -135,7 +135,7 @@ SimpleForm.setup do |config| # in this configuration, which is recommended due to some quirks from different browsers. # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, # change this configuration to true. - config.browser_validations = true + config.browser_validations = false # Custom mappings for input types. This should be a hash containing a regexp # to match as key, and the input type that will be used when the field name From 1a8729e0d96526f2e55d83c802236eb93ca99e88 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 30 Jun 2020 14:26:47 -0500 Subject: [PATCH 123/173] Fix #4462: "You have already favorited this post" on upload. Make adding the fav:self metatag ignore all errors, including when the post was already favorited before editing the post. --- app/models/post.rb | 11 +++++++++-- test/unit/post_test.rb | 8 ++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/models/post.rb b/app/models/post.rb index e1d40ef91..56278e9c2 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -625,10 +625,10 @@ class Post < ApplicationRecord add_pool!(pool) if pool when /^fav:(.+)$/i - add_favorite!(CurrentUser.user) + add_favorite(CurrentUser.user) when /^-fav:(.+)$/i - remove_favorite!(CurrentUser.user) + remove_favorite(CurrentUser.user) when /^(up|down)vote:(.+)$/i vote!($1) @@ -792,6 +792,13 @@ class Post < ApplicationRecord rescue PostVote::Error end + def remove_favorite(user) + remove_favorite!(user) + true + rescue Favorite::Error + false + end + # users who favorited this post, ordered by users who favorited it first def favorited_users favorited_user_ids = fav_string.scan(/\d+/).map(&:to_i) diff --git a/test/unit/post_test.rb b/test/unit/post_test.rb index ab160680f..3f73b5934 100644 --- a/test/unit/post_test.rb +++ b/test/unit/post_test.rb @@ -954,6 +954,14 @@ class PostTest < ActiveSupport::TestCase @post.update(tag_string: "aaa -fav:self") assert_equal("", @post.fav_string) end + + should "not fail when the fav: metatag is used twice" do + @post.update(tag_string: "aaa fav:self fav:me") + assert_equal("fav:#{@user.id}", @post.fav_string) + + @post.update(tag_string: "aaa -fav:self -fav:me") + assert_equal("", @post.fav_string) + end end context "for a child" do From 5f05a41fbca4e294434f5afe13efe411578dad6d Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 30 Jun 2020 15:20:59 -0500 Subject: [PATCH 124/173] Fix #4468: Tag edit mode on posts view applies taglist to wrong post. --- app/javascript/src/javascripts/post_mode_menu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/src/javascripts/post_mode_menu.js b/app/javascript/src/javascripts/post_mode_menu.js index ea0a474c4..328a50043 100644 --- a/app/javascript/src/javascripts/post_mode_menu.js +++ b/app/javascript/src/javascripts/post_mode_menu.js @@ -70,7 +70,7 @@ PostModeMenu.initialize_edit_form = function() { $(document).on("click.danbooru", "#quick-edit-form input[type=submit]", async function(e) { e.preventDefault(); - let post_id = $("#quick-edit-form").data("post-id"); + let post_id = $("#quick-edit-form").attr("data-post-id"); await Post.update(post_id, "quick-edit", { post: { tag_string: $("#post_tag_string").val() }}); }); } From 94490eb57fab45586dbcaf26c7c21e59ae2bb651 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 30 Jun 2020 22:40:40 -0500 Subject: [PATCH 125/173] Fix #4492: Switch from qtip2 to tippy.js. --- .../src/javascripts/post_tooltips.js | 164 +++++++----------- app/javascript/src/styles/base/040_colors.css | 3 +- .../src/styles/specific/post_tooltips.scss | 59 +++++-- app/views/posts/update.js.erb | 3 - package.json | 2 +- yarn.lock | 37 ++-- 6 files changed, 130 insertions(+), 138 deletions(-) diff --git a/app/javascript/src/javascripts/post_tooltips.js b/app/javascript/src/javascripts/post_tooltips.js index 4be1754ec..dca379fd8 100644 --- a/app/javascript/src/javascripts/post_tooltips.js +++ b/app/javascript/src/javascripts/post_tooltips.js @@ -1,28 +1,75 @@ -import CurrentUser from './current_user' -import Utility from './utility' - -require('qtip2'); -require('qtip2/dist/jquery.qtip.css'); +import CurrentUser from './current_user'; +import Utility from './utility'; +import { delegate, hideAll } from 'tippy.js'; +import 'tippy.js/dist/tippy.css'; let PostTooltip = {}; -PostTooltip.render_tooltip = async function (event, qtip) { +PostTooltip.POST_SELECTOR = "*:not(.ui-sortable-handle) > article.post-preview img, .dtext-post-id-link"; +PostTooltip.SHOW_DELAY = 500; +PostTooltip.HIDE_DELAY = 125; +PostTooltip.DURATION = 250; +PostTooltip.MAX_WIDTH = 400; + +PostTooltip.initialize = function () { + if (PostTooltip.disabled()) { + return; + } + + delegate("body", { + allowHTML: true, + appendTo: document.body, + delay: [PostTooltip.SHOW_DELAY, PostTooltip.HIDE_DELAY], + duration: PostTooltip.DURATION, + interactive: true, + maxWidth: PostTooltip.MAX_WIDTH, + target: PostTooltip.POST_SELECTOR, + theme: "post-tooltip", + touch: false, + + onCreate: PostTooltip.on_create, + onShow: PostTooltip.on_show, + onHide: PostTooltip.on_hide, + }); + + $(document).on("click.danbooru.postTooltip", ".post-tooltip-disable", PostTooltip.on_disable_tooltips); +}; + +PostTooltip.on_create = function (instance) { + let title = instance.reference.getAttribute("title"); + + if (title) { + instance.reference.setAttribute("data-title", title); + instance.reference.setAttribute("title", ""); + } +}; + +PostTooltip.on_show = async function (instance) { let post_id = null; let preview = false; + let $target = $(instance.reference); + let $tooltip = $(instance.popper); - if ($(this).is(".dtext-post-id-link")) { + // skip if tooltip has already been rendered. + if ($tooltip.has(".post-tooltip-body").length) { + return; + } + + if ($target.is(".dtext-post-id-link")) { preview = true; - post_id = /\/posts\/(\d+)/.exec($(this).attr("href"))[1]; + post_id = /\/posts\/(\d+)/.exec($target.attr("href"))[1]; } else { - post_id = $(this).parents("[data-id]").data("id"); + post_id = $target.parents("[data-id]").data("id"); } try { - qtip.cache.request = $.get(`/posts/${post_id}`, { variant: "tooltip", preview: preview }); - let html = await qtip.cache.request; + $tooltip.addClass("post-tooltip-loading"); - qtip.set("content.text", html); - qtip.elements.tooltip.removeClass("post-tooltip-loading"); + instance._request = $.get(`/posts/${post_id}`, { variant: "tooltip", preview: preview }); + let html = await instance._request; + instance.setContent(html); + + $tooltip.removeClass("post-tooltip-loading"); } catch (error) { if (error.status !== 0 && error.statusText !== "abort") { Utility.error(`Error displaying tooltip for post #${post_id} (error: ${error.status} ${error.statusText})`); @@ -30,98 +77,19 @@ PostTooltip.render_tooltip = async function (event, qtip) { } }; -// Hide the tooltip the first time it is shown, while we wait on the ajax call to complete. -PostTooltip.on_show = function (event, qtip) { - if (!qtip.cache.hasBeenShown) { - qtip.elements.tooltip.addClass("post-tooltip-loading"); - qtip.cache.hasBeenShown = true; +PostTooltip.on_hide = function (instance) { + if (instance._request?.state() === "pending") { + instance._request.abort(); } -}; - -PostTooltip.POST_SELECTOR = "*:not(.ui-sortable-handle) > .post-preview img, .dtext-post-id-link"; - -// http://qtip2.com/options -PostTooltip.QTIP_OPTIONS = { - style: { - classes: "qtip-light post-tooltip", - tip: false - }, - content: PostTooltip.render_tooltip, - overwrite: false, - position: { - viewport: true, - my: "bottom left", - at: "top left", - effect: false, - adjust: { - y: -2, - method: "shift", - }, - }, - show: { - solo: true, - delay: 750, - effect: false, - ready: true, - event: "mouseenter", - }, - hide: { - delay: 250, - fixed: true, - effect: false, - event: "unfocus click mouseleave", - }, - events: { - show: PostTooltip.on_show, - }, -}; - -PostTooltip.initialize = function () { - $(document).on("mouseenter.danbooru.postTooltip", PostTooltip.POST_SELECTOR, function (event) { - if (PostTooltip.disabled()) { - $(this).qtip("disable"); - } else { - $(this).qtip(PostTooltip.QTIP_OPTIONS, event); - } - }); - - // Cancel pending ajax requests when we mouse out of the thumbnail. - $(document).on("mouseleave.danbooru.postTooltip", PostTooltip.POST_SELECTOR, function (event) { - let qtip = $(event.target).qtip("api"); - - if (qtip && qtip.cache && qtip.cache.request && qtip.cache.request.state() === "pending") { - qtip.cache.request.abort(); - } - }); - - $(document).on("click.danbooru.postTooltip", ".post-tooltip-disable", PostTooltip.on_disable_tooltips); - - // Hide tooltips when pressing keys or clicking thumbnails. - $(document).on("keydown.danbooru.postTooltip", PostTooltip.hide); - $(document).on("click.danbooru.postTooltip", PostTooltip.POST_SELECTOR, PostTooltip.hide); - - // Disable tooltips on touch devices. https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent - PostTooltip.isTouching = false; - $(document).on("touchstart.danbooru.postTooltip", function (event) { PostTooltip.isTouching = true; }); - $(document).on("touchend.danbooru.postTooltip", function (event) { PostTooltip.isTouching = false; }); -}; - -PostTooltip.hide = function (event) { - // Hide on any key except control (user may be control-clicking link inside tooltip). - if (event.type === "keydown" && event.ctrlKey === true) { - return; - } - - $(".post-tooltip:visible").qtip("hide"); -}; +} PostTooltip.disabled = function (event) { - return PostTooltip.isTouching || CurrentUser.data("disable-post-tooltips"); + return CurrentUser.data("disable-post-tooltips"); }; PostTooltip.on_disable_tooltips = async function (event) { event.preventDefault(); - $(event.target).parents(".qtip").qtip("hide"); + hideAll(); if (CurrentUser.data("is-anonymous")) { Utility.notice('You must <a href="/session/new">login</a> to disable tooltips'); diff --git a/app/javascript/src/styles/base/040_colors.css b/app/javascript/src/styles/base/040_colors.css index ab97b39f3..561f86e60 100644 --- a/app/javascript/src/styles/base/040_colors.css +++ b/app/javascript/src/styles/base/040_colors.css @@ -72,7 +72,8 @@ --comment-sticky-background-color: var(--subnav-menu-background-color); --post-tooltip-background-color: var(--body-background-color); - --post-tooltip-border-color: #767676; + --post-tooltip-border-color: hsla(210, 100%, 3%, 0.15); + --post-tooltip-box-shadow: 0 4px 14px -2px hsla(210, 100%, 3%, 0.10); --post-tooltip-header-background-color: var(--subnav-menu-background-color); --post-tooltip-info-color: #555; --post-tooltip-scrollbar-background: #999999; diff --git a/app/javascript/src/styles/specific/post_tooltips.scss b/app/javascript/src/styles/specific/post_tooltips.scss index 2a98dfc1c..fa843763a 100644 --- a/app/javascript/src/styles/specific/post_tooltips.scss +++ b/app/javascript/src/styles/specific/post_tooltips.scss @@ -1,6 +1,5 @@ $tooltip-line-height: 16px; -$tooltip-body-height: $tooltip-line-height * 6; // 6 lines high. -$tooltip-width: 164px * 3 - 10; // 3 thumbnails wide. +$tooltip-body-height: $tooltip-line-height * 4; // 4 lines high. @mixin thin-scrollbar { &::-webkit-scrollbar { @@ -46,16 +45,20 @@ $tooltip-width: 164px * 3 - 10; // 3 thumbnails wide. } } -.post-tooltip { - max-width: $tooltip-width; - min-width: $tooltip-width; +.tippy-box[data-theme~="post-tooltip"] { + min-width: 200px; box-sizing: border-box; font-size: 11px; line-height: $tooltip-line-height; - border-color: var(--post-tooltip-border-color); - background-color: var(--post-tooltip-background-color); - .qtip-content { + border: 1px solid var(--post-tooltip-border-color); + border-radius: 4px; + + background-color: var(--post-tooltip-background-color); + background-clip: padding-box; + box-shadow: var(--post-tooltip-box-shadow); + + .tippy-content { padding: 0; > * { @@ -116,7 +119,43 @@ $tooltip-width: 164px * 3 - 10; // 3 thumbnails wide. } } - &.post-tooltip-loading { - visibility: hidden; + /* bordered arrow styling; see https://github.com/atomiks/tippyjs/blob/master/src/scss/themes/light-border.scss */ + &[data-placement^=bottom] { + > .tippy-arrow:before { + border-bottom-color: var(--post-tooltip-background-color); + bottom: 16px; + } + + > .tippy-arrow:after { + border-bottom-color: var(--post-tooltip-border-color); + border-width: 0 7px 7px; + top: -8px; + left: 1px; + } + } + + &[data-placement^=top] { + > .tippy-arrow:before { + border-top-color: var(--post-tooltip-background-color); + } + + > .tippy-arrow:after { + border-top-color: var(--post-tooltip-border-color); + border-width: 7px 7px 0; + top: 17px; + left: 1px; + } + } + + > .tippy-arrow:after { + border-color: transparent; + border-style: solid; + content: ""; + position: absolute; + z-index: -1; } } + +div[data-tippy-root].post-tooltip-loading { + visibility: hidden !important; +} diff --git a/app/views/posts/update.js.erb b/app/views/posts/update.js.erb index 1da54c638..606380c4a 100644 --- a/app/views/posts/update.js.erb +++ b/app/views/posts/update.js.erb @@ -1,8 +1,5 @@ <% if @post.valid? %> var $post = $("#post_<%= @post.id %>"); - <% if !CurrentUser.disable_post_tooltips %> - $post.find("img").qtip("destroy", true); - <% end %> var $new_post = $("<%= j PostPresenter.preview(@post, show_deleted: true) %>"); Danbooru.Blacklist.apply_post($new_post.get(0)); $("#post_<%= @post.id %>").replaceWith($new_post); diff --git a/package.json b/package.json index e715e9198..1d7b508c4 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ "hammerjs": "^2.0.8", "jquery-hotkeys": "^0.2.2", "jquery-ui": "^1.12.1", - "qtip2": "^3.0.3", "rails-erb-loader": "^5.5.0", "script-loader": "^0.7.2", "spark-md5": "^3.0.0", "stupid-table-plugin": "^1.1.3", + "tippy.js": "^6.2.3", "typeface-anton": "^0.0.72", "typeface-archivo-narrow": "^1.0.0", "typeface-ibm-plex-mono": "^0.0.61", diff --git a/yarn.lock b/yarn.lock index b28f64d11..544a14be1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -830,6 +830,11 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@popperjs/core@^2.3.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.2.tgz#7c6dc4ecef16149fd7a736710baa1b811017fdca" + integrity sha512-JlGTGRYHC2QK+DDbePyXdBdooxFq2+noLfWpRqJtkxcb/oYWzOF0kcbfvvbWrwevCC1l6hLUg1wHYT+ona5BWQ== + "@rails/ujs@^6.0.2-1": version "6.0.3" resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.0.3.tgz#e68a03278e30daea6a110aac5dfa33c60c53055d" @@ -3212,11 +3217,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -ev-emitter@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ev-emitter/-/ev-emitter-1.1.1.tgz#8f18b0ce5c76a5d18017f71c0a795c65b9138f2a" - integrity sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q== - eventemitter3@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -4196,13 +4196,6 @@ ignore@^5.1.4, ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -imagesloaded@>=3.0.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/imagesloaded/-/imagesloaded-4.1.4.tgz#1376efcd162bb768c34c3727ac89cc04051f3cc7" - integrity sha512-ltiBVcYpc/TYTF5nolkMNsnREHW+ICvfQ3Yla2Sgr71YFwQ86bDwV9hgpFhFtrGPuwEx5+LqOHIrdXBdoWwwsA== - dependencies: - ev-emitter "^1.0.0" - import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -4718,11 +4711,6 @@ jquery-ui@^1.12.1: resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" integrity sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE= -jquery@>=1.6.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" - integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== - js-base64@^2.1.8: version "2.6.2" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.2.tgz#cf9301bc5cc756892a9a6c8d7138322e5944fb0d" @@ -7020,14 +7008,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -qtip2@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/qtip2/-/qtip2-3.0.3.tgz#7df088ae4412c24a4064de69e824cb3cf76210dc" - integrity sha1-ffCIrkQSwkpAZN5p6CTLPPdiENw= - dependencies: - imagesloaded ">=3.0.0" - jquery ">=1.6.0" - query-string@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" @@ -8529,6 +8509,13 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tippy.js@^6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.2.3.tgz#0a5db67dc6bd9129233b26052b7ae2b2047fd73e" + integrity sha512-MzqHMrr2C0IC8ZUnG5kLQPxonWJ7V+Usqiy2W5b+dCvAfousio0mA85h+Ea5wRq94AQGd8mbFGeciRgkP+F+7w== + dependencies: + "@popperjs/core" "^2.3.2" + to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" From efe5111a9ec54702d0f7b34979499810b2cca21f Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 30 Jun 2020 23:00:44 -0500 Subject: [PATCH 126/173] js: bundle jQuery with Webpack. Include jQuery in our Webpack bundle instead of loading it from CDNJS. This means we no longer load any Javascript libraries from third party sites. --- app/javascript/packs/application.js | 2 ++ app/views/layouts/blank.html.erb | 1 - app/views/layouts/default.html.erb | 3 ++- config/webpack/environment.js | 6 +++--- package.json | 1 + yarn.lock | 5 +++++ 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 519fd03fa..16b3f2518 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -12,6 +12,7 @@ require('jquery-hotkeys'); // should start looking for nodejs replacements importAll(require.context('../vendor', true, /\.js$/)); +require('jquery'); require("jquery-ui/ui/effects/effect-shake"); require("jquery-ui/ui/widgets/autocomplete"); require("jquery-ui/ui/widgets/button"); @@ -33,6 +34,7 @@ require("@fortawesome/fontawesome-free/css/regular.css"); importAll(require.context('../src/javascripts', true, /\.js(\.erb)?$/)); importAll(require.context('../src/styles', true, /\.s?css(?:\.erb)?$/)); +export { default as jQuery } from "jquery"; export { default as Autocomplete } from '../src/javascripts/autocomplete.js.erb'; export { default as Blacklist } from '../src/javascripts/blacklists.js'; export { default as Comment } from '../src/javascripts/comments.js'; diff --git a/app/views/layouts/blank.html.erb b/app/views/layouts/blank.html.erb index 955d07f4d..b907fc2f7 100644 --- a/app/views/layouts/blank.html.erb +++ b/app/views/layouts/blank.html.erb @@ -6,7 +6,6 @@ <link rel="top" title="<%= Danbooru.config.app_name %>" href="/"> <%= csrf_meta_tag %> <%= raw Danbooru.config.custom_html_header_content %> - <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <%= javascript_pack_tag "application" %> <%= stylesheet_pack_tag "application" %> <%= yield :html_header %> diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index ea2c05e17..1c8540062 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -14,7 +14,6 @@ <% if CurrentUser.user.blacklisted_tags.present? %> <meta name="blacklisted-tags" content="<%= CurrentUser.user.blacklisted_tags.gsub(/(?:\r|\n)+/, ",") %>"> <% end %> - <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <%= javascript_pack_tag "application" %> <%= stylesheet_pack_tag "application" %> <% if CurrentUser.user.custom_style.present? && params.fetch(:css, "true").truthy? %> @@ -123,6 +122,8 @@ window.Danbooru.notice = Danbooru.Utility.notice; window.Danbooru.error = Danbooru.Utility.error; + window.$ = Danbooru.jQuery; + window.jQuery = Danbooru.jQuery; </script> <%= render "static/footer" %> diff --git a/config/webpack/environment.js b/config/webpack/environment.js index 466faa138..607382c5e 100644 --- a/config/webpack/environment.js +++ b/config/webpack/environment.js @@ -6,8 +6,8 @@ environment.loaders.append('erb', erb); environment.config.output.library = ["Danbooru"]; -environment.config.externals = { - jquery: "jQuery" -} +environment.config.set("resolve.alias", { + "jquery": "jquery/src/jquery.js" +}); module.exports = environment diff --git a/package.json b/package.json index 1d7b508c4..639b801d0 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "dropzone": "^5.5.1", "expose-loader": "^0.7.5", "hammerjs": "^2.0.8", + "jquery": "3.4.1", "jquery-hotkeys": "^0.2.2", "jquery-ui": "^1.12.1", "rails-erb-loader": "^5.5.0", diff --git a/yarn.lock b/yarn.lock index 544a14be1..1074324ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4711,6 +4711,11 @@ jquery-ui@^1.12.1: resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" integrity sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE= +jquery@3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== + js-base64@^2.1.8: version "2.6.2" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.2.tgz#cf9301bc5cc756892a9a6c8d7138322e5944fb0d" From 1760d1fc739cc620112471bd6a9cbe09c3174c7f Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 30 Jun 2020 23:20:32 -0500 Subject: [PATCH 127/173] js: upgrade jquery to 3.5.1. Changelog: * https://blog.jquery.com/2020/05/04/jquery-3-5-1-released-fixing-a-regression/ * https://blog.jquery.com/2020/04/10/jquery-3-5-0-released/ --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 639b801d0..ed7196f8d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "dropzone": "^5.5.1", "expose-loader": "^0.7.5", "hammerjs": "^2.0.8", - "jquery": "3.4.1", + "jquery": "3.5.1", "jquery-hotkeys": "^0.2.2", "jquery-ui": "^1.12.1", "rails-erb-loader": "^5.5.0", diff --git a/yarn.lock b/yarn.lock index 1074324ed..4981f88d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4711,10 +4711,10 @@ jquery-ui@^1.12.1: resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" integrity sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE= -jquery@3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" - integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== +jquery@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" + integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== js-base64@^2.1.8: version "2.6.2" From 57dcd9ee1ae2a3adc82cf64b9053cb81447eef79 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Tue, 30 Jun 2020 23:29:59 -0500 Subject: [PATCH 128/173] Remove unused gems and yarn packages. --- Gemfile | 2 -- Gemfile.lock | 7 ------- package.json | 3 --- yarn.lock | 49 ------------------------------------------------- 4 files changed, 61 deletions(-) diff --git a/Gemfile b/Gemfile index 02cde7ccd..9f109c6ae 100644 --- a/Gemfile +++ b/Gemfile @@ -30,10 +30,8 @@ gem 'addressable' gem 'rakismet' gem 'recaptcha', require: "recaptcha/rails" gem 'activemodel-serializers-xml' -gem 'jquery-rails' gem 'webpacker', '>= 4.0.x' gem 'rake' -gem 'retriable' gem 'redis' gem 'request_store' gem 'builder' diff --git a/Gemfile.lock b/Gemfile.lock index f69dc9815..407e3784d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -171,10 +171,6 @@ GEM concurrent-ruby (~> 1.0) ipaddress_2 (0.13.0) jmespath (1.4.0) - jquery-rails (4.3.5) - rails-dom-testing (>= 1, < 3) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) json (2.3.0) jwt (2.2.1) kgio (2.11.3) @@ -299,7 +295,6 @@ GEM responders (3.0.1) actionpack (>= 5.0) railties (>= 5.0) - retriable (3.1.2) rexml (3.2.4) rubocop (0.86.0) parallel (~> 1.10) @@ -415,7 +410,6 @@ DEPENDENCIES flamegraph http ipaddress_2 - jquery-rails listen mail memoist @@ -442,7 +436,6 @@ DEPENDENCIES redis request_store responders - retriable rubocop rubocop-rails ruby-vips diff --git a/package.json b/package.json index ed7196f8d..9a58e9b9b 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,12 @@ "@fortawesome/fontawesome-free": "^5.11.2", "@rails/ujs": "^6.0.2-1", "@rails/webpacker": "^5.0.0", - "debug-loader": "^0.0.1", "dropzone": "^5.5.1", - "expose-loader": "^0.7.5", "hammerjs": "^2.0.8", "jquery": "3.5.1", "jquery-hotkeys": "^0.2.2", "jquery-ui": "^1.12.1", "rails-erb-loader": "^5.5.0", - "script-loader": "^0.7.2", "spark-md5": "^3.0.0", "stupid-table-plugin": "^1.1.3", "tippy.js": "^6.2.3", diff --git a/yarn.lock b/yarn.lock index 4981f88d1..816fd8f5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1499,11 +1499,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -big.js@^3.1.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" - integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== - big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -2608,13 +2603,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -debug-loader@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/debug-loader/-/debug-loader-0.0.1.tgz#44dc37e09e3c39e6af334681960f70a534a9d056" - integrity sha1-RNw34J48OeavM0aBlg9wpTSp0FY= - dependencies: - loader-utils "^0.2.12" - debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -2961,11 +2949,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= - emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -3282,11 +3265,6 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -expose-loader@^0.7.5: - version "0.7.5" - resolved "https://registry.yarnpkg.com/expose-loader/-/expose-loader-0.7.5.tgz#e29ea2d9aeeed3254a3faa1b35f502db9f9c3f6f" - integrity sha512-iPowgKUZkTPX5PznYsmifVj9Bob0w2wTHVkt/eYNPSzyebkUgIedmskf/kcfEIWpiWjg3JRjnW+a17XypySMuw== - express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -4787,11 +4765,6 @@ json3@^3.3.2: resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== -json5@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= - json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -4913,16 +4886,6 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@^0.2.12: - version "0.2.17" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" - integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= - dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" - object-assign "^4.0.1" - loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" @@ -7079,11 +7042,6 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -raw-loader@~0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" - integrity sha1-DD0L6u2KAclm2Xh793goElKpeao= - read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -7587,13 +7545,6 @@ schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7 ajv "^6.12.2" ajv-keywords "^3.4.1" -script-loader@^0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/script-loader/-/script-loader-0.7.2.tgz#2016db6f86f25f5cf56da38915d83378bb166ba7" - integrity sha512-UMNLEvgOAQuzK8ji8qIscM3GIrRCWN6MmMXGD4SD5l6cSycgGsCo0tX5xRnfQcoghqct0tjHjcykgI1PyBE2aA== - dependencies: - raw-loader "~0.5.1" - scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" From e822d5f16ef638e3aa7df9a1928d87f1658f5434 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 3 Jul 2020 12:06:12 -0500 Subject: [PATCH 129/173] tests: fix invalid artist url test. --- test/unit/artist_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/artist_test.rb b/test/unit/artist_test.rb index 0e55f6e6f..58cd234ea 100644 --- a/test/unit/artist_test.rb +++ b/test/unit/artist_test.rb @@ -128,7 +128,8 @@ class ArtistTest < ActiveSupport::TestCase should "not allow invalid urls" do artist = FactoryBot.build(:artist, :url_string => "blah") assert_equal(false, artist.valid?) - assert_equal(["'blah' must begin with http:// or https:// "], artist.errors["urls.url"]) + assert_includes(artist.errors["urls.url"], "'blah' must begin with http:// or https:// ") + assert_includes(artist.errors["urls.url"], "'blah' has a hostname '' that does not contain a dot") end should "allow fixing invalid urls" do From f97c62c71daf7d11dcfaaffefadd7519c04b183e Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 3 Jul 2020 12:42:04 -0500 Subject: [PATCH 130/173] search: fix search timeout error page not appearing. Bug: when a search timed out we got the generic failbooru page instead of the search timeout error page. Cause: when rendering the <link rel="next"> / <link rel="prev"> tags in the header, we may need to evaluate the search to determine the next or previous page, but if the searches times out then this fails, which caused Rails to throw a ActionView::Template::Error because an exception was thrown while rendering the template. Likewise, rendering the attributes for the <body> tag could fail with an ActionView::Template::Error because the call to `current_item.present?` forced evaluation of the search. --- app/controllers/application_controller.rb | 2 ++ app/helpers/application_helper.rb | 2 +- app/logical/pagination_extension.rb | 28 ++++++++++++++-------- app/logical/post_sets/post.rb | 2 +- app/views/application/_meta_links.html.erb | 12 ++++------ test/functional/posts_controller_test.rb | 22 +++++++++++++++++ 6 files changed, 49 insertions(+), 19 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a0825ee0d..9afec6b03 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -88,6 +88,8 @@ class ApplicationController < ActionController::Base def rescue_exception(exception) case exception + when ActionView::Template::Error + rescue_exception(exception.cause) when ActiveRecord::QueryCanceled render_error_page(500, exception, template: "static/search_timeout", message: "The database timed out running your query.") when ActionController::BadRequest diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 4ad0202d8..26be0501f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -253,7 +253,7 @@ module ApplicationHelper def body_attributes(user, params, current_item = nil) current_user_data_attributes = data_attributes_for(user, "current-user", current_user_attributes) - if current_item.present? && current_item.respond_to?(:html_data_attributes) && current_item.respond_to?(:model_name) + if !current_item.nil? && current_item.respond_to?(:html_data_attributes) && current_item.respond_to?(:model_name) model_name = current_item.model_name.singular.dasherize model_attributes = current_item.html_data_attributes current_item_data_attributes = data_attributes_for(current_item, model_name, model_attributes) diff --git a/app/logical/pagination_extension.rb b/app/logical/pagination_extension.rb index dcb89ee5b..686cdcd27 100644 --- a/app/logical/pagination_extension.rb +++ b/app/logical/pagination_extension.rb @@ -61,27 +61,35 @@ module PaginationExtension end def prev_page - return nil if is_first_page? - - if paginator_mode == :numbered + if is_first_page? + nil + elsif paginator_mode == :numbered current_page - 1 - elsif paginator_mode == :sequential_before + elsif paginator_mode == :sequential_before && records.present? "a#{records.first.id}" - elsif paginator_mode == :sequential_after + elsif paginator_mode == :sequential_after && records.present? "b#{records.last.id}" + else + nil end + rescue ActiveRecord::QueryCanceled + nil end def next_page - return nil if is_last_page? - - if paginator_mode == :numbered + if is_last_page? + nil + elsif paginator_mode == :numbered current_page + 1 - elsif paginator_mode == :sequential_before + elsif paginator_mode == :sequential_before && records.present? "b#{records.last.id}" - elsif paginator_mode == :sequential_after + elsif paginator_mode == :sequential_after && records.present? "a#{records.first.id}" + else + nil end + rescue ActiveRecord::QueryCanceled + nil end # XXX Hack: in sequential pagination we fetch one more record than we diff --git a/app/logical/post_sets/post.rb b/app/logical/post_sets/post.rb index b6ed3c379..c13a1f5fb 100644 --- a/app/logical/post_sets/post.rb +++ b/app/logical/post_sets/post.rb @@ -105,7 +105,7 @@ module PostSets if is_random? get_random_posts else - normalized_query.build.paginate(page, count: post_count, search_count: !post_count.nil?, limit: per_page) + normalized_query.build.paginate(page, count: post_count, search_count: !post_count.nil?, limit: per_page).load end end end diff --git a/app/views/application/_meta_links.html.erb b/app/views/application/_meta_links.html.erb index d0ddc7cd4..7b398ed83 100644 --- a/app/views/application/_meta_links.html.erb +++ b/app/views/application/_meta_links.html.erb @@ -1,13 +1,11 @@ <%# collection %> <% content_for(:html_header) do %> - <% if collection.try(:records).present? %> - <% if collection.try(:prev_page) %> - <%= tag.link rel: "prev", href: url_for(nav_params_for(collection.prev_page)) %> - <% end %> + <% if collection.try(:prev_page) %> + <%= tag.link rel: "prev", href: url_for(nav_params_for(collection.prev_page)) %> + <% end %> - <% if collection.try(:next_page) %> - <%= tag.link rel: "next", href: url_for(nav_params_for(collection.next_page)) %> - <% end %> + <% if collection.try(:next_page) %> + <%= tag.link rel: "next", href: url_for(nav_params_for(collection.next_page)) %> <% end %> <% end %> diff --git a/test/functional/posts_controller_test.rb b/test/functional/posts_controller_test.rb index a1c7d3851..65cd5d335 100644 --- a/test/functional/posts_controller_test.rb +++ b/test/functional/posts_controller_test.rb @@ -380,6 +380,28 @@ class PostsControllerTest < ActionDispatch::IntegrationTest assert_select "title", text: "1girl Art | Safebooru" end end + + context "for a search that times out" do + context "during numbered pagination" do + should "show the search timeout error page" do + Post::const_get(:ActiveRecord_Relation).any_instance.stubs(:records).raises(ActiveRecord::QueryCanceled) + + get posts_path(page: "1") + assert_response 500 + assert_select "h1", text: "Search Timeout" + end + end + + context "during sequential pagination" do + should "show the search timeout error page" do + Post::const_get(:ActiveRecord_Relation).any_instance.stubs(:records).raises(ActiveRecord::QueryCanceled) + + get posts_path(page: "a0") + assert_response 500 + assert_select "h1", text: "Search Timeout" + end + end + end end context "show_seq action" do From 629a634b22cf270b3cb362837dace48fd08bc87d Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 3 Jul 2020 14:46:32 -0500 Subject: [PATCH 131/173] seo: don't block css in robots.txt. /packs was blocked by robots.txt, which prevented Googlebot from fetching CSS when indexing pages, which made Google penalize pages for being mobile unfriendly because it couldn't load the CSS and it thought the layout was broken. --- app/views/robots/index.text.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/robots/index.text.erb b/app/views/robots/index.text.erb index 1f9ebe910..59b141a27 100644 --- a/app/views/robots/index.text.erb +++ b/app/views/robots/index.text.erb @@ -44,4 +44,6 @@ Allow: /preview Allow: /original Allow: /sample Allow: /data +Allow: /images +Allow: /packs <% end %> From 99e88b3dae48254d78959d8ff6c48baf1ad9d720 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 3 Jul 2020 14:39:23 -0500 Subject: [PATCH 132/173] seo: add rel=nofollow to various internal search links. Add rel=nofollow to various internal search links to prevent Google from attempting to crawl things like `<tag> status:deleted` or `approver:<name>` searches. --- app/presenters/user_presenter.rb | 14 +++++++------- app/views/artists/index.html.erb | 2 +- app/views/artists/show_or_new.html.erb | 2 +- app/views/posts/partials/index/_excerpt.html.erb | 2 +- app/views/posts/partials/index/_related.html.erb | 8 ++++---- .../posts/partials/show/_child_notice.html.erb | 2 +- .../posts/partials/show/_parent_notice.html.erb | 4 ++-- app/views/wiki_pages/show.html.erb | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/presenters/user_presenter.rb b/app/presenters/user_presenter.rb index 3a3add269..1b63c7989 100644 --- a/app/presenters/user_presenter.rb +++ b/app/presenters/user_presenter.rb @@ -56,15 +56,15 @@ class UserPresenter end def upload_count(template) - template.link_to(user.post_upload_count, template.posts_path(:tags => "user:#{user.name}")) + template.link_to(user.post_upload_count, template.posts_path(tags: "user:#{user.name}"), rel: "nofollow") end def deleted_upload_count(template) - template.link_to(user.posts.deleted.count, template.posts_path(:tags => "status:deleted user:#{user.name}")) + template.link_to(user.posts.deleted.count, template.posts_path(tags: "status:deleted user:#{user.name}"), rel: "nofollow") end def favorite_count(template) - template.link_to(user.favorite_count, template.posts_path(tags: "ordfav:#{user.name}")) + template.link_to(user.favorite_count, template.posts_path(tags: "ordfav:#{user.name}"), rel: "nofollow") end def favorite_group_count(template) @@ -78,7 +78,7 @@ class UserPresenter def commented_posts_count(template) count = PostQueryBuilder.new("commenter:#{user.name}").fast_count count = "?" if count.nil? - template.link_to(count, template.posts_path(:tags => "commenter:#{user.name} order:comment_bumped")) + template.link_to(count, template.posts_path(tags: "commenter:#{user.name} order:comment_bumped"), rel: "nofollow") end def post_version_count(template) @@ -92,7 +92,7 @@ class UserPresenter def noted_posts_count(template) count = PostQueryBuilder.new("noteupdater:#{user.name}").fast_count count = "?" if count.nil? - template.link_to(count, template.posts_path(:tags => "noteupdater:#{user.name} order:note")) + template.link_to(count, template.posts_path(tags: "noteupdater:#{user.name} order:note"), rel: "nofollow") end def wiki_page_version_count(template) @@ -108,7 +108,7 @@ class UserPresenter end def forum_post_count(template) - template.link_to(user.forum_post_count, template.forum_posts_path(:search => {:creator_id => user.id})) + template.link_to(user.forum_post_count, template.forum_posts_path(search: { creator_id: user.id }), rel: "nofollow") end def pool_version_count(template) @@ -128,7 +128,7 @@ class UserPresenter end def approval_count(template) - template.link_to(Post.where("approver_id = ?", user.id).count, template.posts_path(:tags => "approver:#{user.name}")) + template.link_to(Post.where(approver: user).count, template.posts_path(tags: "approver:#{user.name}"), rel: "nofollow") end def feedbacks(template) diff --git a/app/views/artists/index.html.erb b/app/views/artists/index.html.erb index 4ba542b68..ed8edf733 100644 --- a/app/views/artists/index.html.erb +++ b/app/views/artists/index.html.erb @@ -14,7 +14,7 @@ <% end %> <% t.column "Other Names", td: {class: "col-expand"} do |artist| %> <% artist.other_names.each do |name| %> - <%= link_to name, artists_path(search: { any_name_matches: name }), class: "artist-other-name" %> + <%= link_to name, artists_path(search: { any_name_matches: name }), class: "artist-other-name", rel: "nofollow" %> <% end %> <% end %> <% t.column "Status" do |artist| %> diff --git a/app/views/artists/show_or_new.html.erb b/app/views/artists/show_or_new.html.erb index 05b1f0479..73e8265e3 100644 --- a/app/views/artists/show_or_new.html.erb +++ b/app/views/artists/show_or_new.html.erb @@ -1,5 +1,5 @@ <%= render layout: "show" do %> <div> - <p>This artist entry does not exist. <%= link_to "Create new artist entry", new_artist_path(artist: { name: params[:name] }) %>.</p> + <p>This artist entry does not exist. <%= link_to "Create new artist entry", new_artist_path(artist: { name: params[:name] }), rel: "nofollow" %>.</p> </div> <% end %> diff --git a/app/views/posts/partials/index/_excerpt.html.erb b/app/views/posts/partials/index/_excerpt.html.erb index 89d5636e4..cbd048b85 100644 --- a/app/views/posts/partials/index/_excerpt.html.erb +++ b/app/views/posts/partials/index/_excerpt.html.erb @@ -63,7 +63,7 @@ </h4> Creator: <%= link_to_user post_set.favgroup.creator %> <% elsif post_set.has_blank_wiki? %> - <p>There is currently no wiki page for the tag <%= link_to_wiki post_set.tag.pretty_name %>. You can <%= link_to "create one", new_wiki_page_path(wiki_page: { title: post_set.tag.name }) %>.</p> + <p>There is currently no wiki page for the tag <%= link_to_wiki post_set.tag.pretty_name %>. You can <%= link_to "create one", new_wiki_page_path(wiki_page: { title: post_set.tag.name }), rel: "nofollow" %>.</p> <%= render "tag_relationships/alias_and_implication_list", tag: post_set.tag %> <% end %> diff --git a/app/views/posts/partials/index/_related.html.erb b/app/views/posts/partials/index/_related.html.erb index cf0461b3f..cd0923567 100644 --- a/app/views/posts/partials/index/_related.html.erb +++ b/app/views/posts/partials/index/_related.html.erb @@ -9,11 +9,11 @@ <li><%= link_to "Viewed", viewed_explore_posts_path %></li> <% end %> - <li><%= link_to "Deleted", posts_path(tags: "#{params[:tags]} status:deleted") %></li> - <li><%= link_to "Random", random_posts_path(tags: params[:tags]), id: "random-post", "data-shortcut": "r" %></li> + <li><%= link_to "Deleted", posts_path(tags: "#{params[:tags]} status:deleted"), rel: "nofollow" %></li> + <li><%= link_to "Random", random_posts_path(tags: params[:tags]), id: "random-post", "data-shortcut": "r", rel: "nofollow" %></li> <% if post_set.query.is_simple_tag? %> - <li><%= link_to "History", post_versions_path(search: { changed_tags: params[:tags] }) %></li> + <li><%= link_to "History", post_versions_path(search: { changed_tags: params[:tags] }), rel: "nofollow" %></li> <% end %> - <li><%= link_to "Count", posts_counts_path(:tags => params[:tags]) %></li> + <li><%= link_to "Count", posts_counts_path(tags: params[:tags]), rel: "nofollow" %></li> </ul> </section> diff --git a/app/views/posts/partials/show/_child_notice.html.erb b/app/views/posts/partials/show/_child_notice.html.erb index c363b9c4c..4017e7012 100644 --- a/app/views/posts/partials/show/_child_notice.html.erb +++ b/app/views/posts/partials/show/_child_notice.html.erb @@ -1,4 +1,4 @@ -This post has <%= link_to pluralize(children.length, "child"), posts_path(tags: "parent:#{parent.id}") %> +This post has <%= link_to pluralize(children.length, "child"), posts_path(tags: "parent:#{parent.id}"), rel: "nofollow" %> (<%= link_to_wiki "learn more", "help:post_relationships" %>) <%= link_to("« hide", "#", id: "has-children-relationship-preview-link") %> diff --git a/app/views/posts/partials/show/_parent_notice.html.erb b/app/views/posts/partials/show/_parent_notice.html.erb index 639e83b30..d4388757f 100644 --- a/app/views/posts/partials/show/_parent_notice.html.erb +++ b/app/views/posts/partials/show/_parent_notice.html.erb @@ -1,8 +1,8 @@ -This post belongs to a <%= link_to "parent", posts_path(tags: "parent:#{parent.id}") %> <% "(deleted)" if parent.is_deleted? %> +This post belongs to a <%= link_to "parent", posts_path(tags: "parent:#{parent.id}"), rel: "nofollow" %> <% "(deleted)" if parent.is_deleted? %> <% children.length.tap do |children_count| %> <% if children_count > 1 %> - and has <%= link_to pluralize(children_count - 1, "sibling"), posts_path(tags: "parent:#{parent.id}") %> + and has <%= link_to pluralize(children_count - 1, "sibling"), posts_path(tags: "parent:#{parent.id}"), rel: "nofollow" %> <% end %> <% end %> diff --git a/app/views/wiki_pages/show.html.erb b/app/views/wiki_pages/show.html.erb index bd3b0bd22..af7285b70 100644 --- a/app/views/wiki_pages/show.html.erb +++ b/app/views/wiki_pages/show.html.erb @@ -23,7 +23,7 @@ <% end %> <% if @wiki_page.new_record? %> - <p>This wiki page does not exist. <%= link_to "Create new wiki page", new_wiki_page_path(wiki_page: { title: @wiki_page.title }) %>.</p> + <p>This wiki page does not exist. <%= link_to "Create new wiki page", new_wiki_page_path(wiki_page: { title: @wiki_page.title }), rel: "nofollow" %>.</p> <% else %> <%= format_text(@wiki_page.body) %> <% end %> From a0ee4c67a86ceaee8862878da539146aff3d9800 Mon Sep 17 00:00:00 2001 From: evazion <noizave@gmail.com> Date: Fri, 3 Jul 2020 15:59:45 -0500 Subject: [PATCH 133/173] Add lang="en" to <html> tag. --- app/views/layouts/blank.html.erb | 2 +- app/views/layouts/default.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/blank.html.erb b/app/views/layouts/blank.html.erb index b907fc2f7..293152b8b 100644 --- a/app/views/layouts/blank.html.erb +++ b/app/views/layouts/blank.html.erb @@ -1,5 +1,5 @@ <!doctype html> -<html> +<html lang="en"> <head> <title><%= page_title %> diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index 1c8540062..2c1f65349 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -1,5 +1,5 @@ - + <%= page_title %> From b6b0218e1db12ee4994736dbfd98269714b66637 Mon Sep 17 00:00:00 2001 From: evazion Date: Fri, 3 Jul 2020 16:04:13 -0500 Subject: [PATCH 134/173] seo: update JSON-LD site metadata. * Add the Danbooru logo and social media links to the Organization data. This is to make Danbooru eligible for Google's knowledge panel ([1], [2]). * Only include this metadata on the frontpage as per Google's recommendations ([3]). [1]: https://support.google.com/knowledgepanel/answer/9163198 [2], https://developers.google.com/search/docs/data-types/logo [3]: https://developers.google.com/search/docs/data-types/sitelinks-searchbox --- app/helpers/seo_helper.rb | 56 ++++++++++++++++++ app/views/layouts/default.html.erb | 40 ++----------- .../partials/index/_seo_meta_tags.html.erb | 2 +- config/danbooru_default_config.rb | 5 ++ public/images/danbooru-logo-500x500.png | Bin 0 -> 29388 bytes 5 files changed, 67 insertions(+), 36 deletions(-) create mode 100644 app/helpers/seo_helper.rb create mode 100644 public/images/danbooru-logo-500x500.png diff --git a/app/helpers/seo_helper.rb b/app/helpers/seo_helper.rb new file mode 100644 index 000000000..60330ada5 --- /dev/null +++ b/app/helpers/seo_helper.rb @@ -0,0 +1,56 @@ +# https://yoast.com/structured-data-schema-ultimate-guide/ +# https://technicalseo.com/tools/schema-markup-generator/ +# https://developers.google.com/search/docs/data-types/sitelinks-searchbox +# https://developers.google.com/search/docs/data-types/logo +# https://search.google.com/structured-data/testing-tool/u/0/ +# https://search.google.com/test/rich-results +# https://schema.org/Organization +# https://schema.org/WebSite + +module SeoHelper + def site_description + "#{Danbooru.config.canonical_app_name} is the original anime image booru. Search millions of anime pictures categorized by thousands of tags." + end + + def json_ld_website_data + urls = [ + Danbooru.config.twitter_url, + Danbooru.config.discord_server_url, + Danbooru.config.source_code_url, + "https://en.wikipedia.org/wiki/Danbooru" + ].compact + + json_ld_tag({ + "@context": "https://schema.org", + "@graph": [ + { + "@type": "Organization", + "@id": root_url(anchor: "organization", host: Danbooru.config.hostname), + url: root_url(host: Danbooru.config.hostname), + name: Danbooru.config.app_name, + logo: "#{root_url(host: Danbooru.config.hostname)}images/danbooru-logo-500x500.png", + sameAs: urls + }, + { + "@type": "WebSite", + "@id": root_url(anchor: "website", host: Danbooru.config.hostname), + "url": root_url(host: Danbooru.config.hostname), + "name": Danbooru.config.app_name, + "description": site_description, + "publisher": { + "@id": root_url(anchor: "organization", host: Danbooru.config.hostname), + }, + "potentialAction": [{ + "@type": "SearchAction", + "target": "#{posts_url(host: Danbooru.config.hostname)}?tags={search_term_string}", + "query-input": "required name=search_term_string" + }] + } + ] + }) + end + + def json_ld_tag(data) + tag.script(data.to_json.html_safe, type: "application/ld+json") + end +end diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index 2c1f65349..6dcb21581 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -19,42 +19,12 @@ <% if CurrentUser.user.custom_style.present? && params.fetch(:css, "true").truthy? %> <%= stylesheet_link_tag custom_style_users_path(md5: Digest::MD5.hexdigest(CurrentUser.user.custom_style)), media: "screen" %> <% end %> - <% if Danbooru.config.twitter_username.present? %> - - <% end %> - - - <%= tag.meta name: "description", content: meta_description %> + <% if current_page?(root_url) %> + <%= json_ld_website_data %> + <% end %> + + <%= tag.meta name: "description", content: meta_description %> <%= tag.meta property: "og:type", content: "website" %> <%= tag.meta property: "og:site_name", content: Danbooru.config.app_name %> <%= tag.meta property: "og:title", content: page_title %> diff --git a/app/views/posts/partials/index/_seo_meta_tags.html.erb b/app/views/posts/partials/index/_seo_meta_tags.html.erb index 1c58f8d5a..cedb48987 100644 --- a/app/views/posts/partials/index/_seo_meta_tags.html.erb +++ b/app/views/posts/partials/index/_seo_meta_tags.html.erb @@ -1,6 +1,6 @@ <% if @post_set.query.is_empty_search? %> <% page_title("#{Danbooru.config.app_name}: Anime Image Board", suffix: nil) %> - <% meta_description("#{Danbooru.config.canonical_app_name} is the original anime image 'booru. Find over 3.75 million anime pictures categorized by over 100 million tags.") %> + <% meta_description site_description %> <% atom_feed_tag "Posts", posts_url(format: :atom) %> <% else %> diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb index f9570949c..439673809 100644 --- a/config/danbooru_default_config.rb +++ b/config/danbooru_default_config.rb @@ -355,6 +355,11 @@ module Danbooru nil end + def twitter_url + return nil unless Danbooru.config.twitter_username.present? + "https://twitter.com/#{Danbooru.config.twitter_username}" + end + # include essential tags in image urls (requires nginx/apache rewrites) def enable_seo_post_urls false diff --git a/public/images/danbooru-logo-500x500.png b/public/images/danbooru-logo-500x500.png new file mode 100644 index 0000000000000000000000000000000000000000..5e652ad8235793d9136a11408df6981115b8ab9e GIT binary patch literal 29388 zcmeAS@N?(olHy`uVBq!ia0y~yVEh8Y9Bd2>45zQ%?_yw3VDNNt45^s&W-t35ne@u{ z|8Eyn1zNhthP~gG<2~(ZTkgZWn-ak}2OOBpTkcr26cq2g1EZr+`Fv%Ej;nf3W- zMkfx%7J=bLIC@U8W^NHka?A6YEZyU3^gT$wv?=xOyHZ7?X(`|5EeVXA_rg8RB)MF| zNsFCpu0T-GGL6XPliQXAAK0;UVpP_|s;`GL0$)!qTB*BzP2kGVr6qau)uz5G{%yMA z$_qv5Sgo(CuPhJR?&Yji`fS>#n+;yaL#!5F`gpZ`!S{;&ishclJC{zJ@}iypYtt_E z)OIKJmXvzNvjOZw}5o{mx8|&E?DCohFy;b2{p_cBwU^OZ{k*lX}aJWozY5zVvO|@p^)2#oOiQonPmkHV9sOJ}@&c&(e6i@6rIB zna{1)b;NbO|Gr|2^U~b?72MVPKbTZa(7hyYajV_Y?W2m0Mu}60^5iK8o^O@-dL_vH z?uW+I8GqPU7qA^WqU+gpKKs;+vrbc-pZUG_zPQ)rW4`CFhqG^d=GT`#`t?)Y`+age za%n97HBJkKop_=`_Ux_M6nK8E^bFHDTlUHO3lyKN{(4kPHzd<+6Ys@EPO2yGtaIHf za#TJjbGBFXlsn(=Ze>-!y36Hb?QLs=nl<}B^sBfmxWKtGN$Rr4nt5p)ds0)SqxNk) zmoZ@*LuBRYAeOl3b2e-XE~Vy``>gD~zw%Pp)UukNl`_>C{U77Lzjik)NNTufUD)QN zzNBk~C)4zER#*RKADScP^Jn>L1OK)9&SqTmTTkiPm3xb%=G}FRIxQcTx!ddZzbBb* zYCj6}t^Vq_RmRGa!|9mgLSdoebu+tNj1s@Ro_8UpxwAYbe~X!TrM{~fSH500+hKJ+ zqwUtqM9$_1zC7!1-`5;%&9ifUO^v==Rl6fctDlF=&M?nCdZ{iC-=yqc`TJX^?*}-#pRAVTnma+F@UG346&f|S zwy!^MrL(kc&-tKC%T0?{9i5vsVP~nP@3SS*cb?srKlbe$qfE$a_xfFDB@Xt5NQzh~ z3c9;KK08^ur!jh2?bF-{i>-p`%F*h2t}m|^^@cA#v-bT-jk4JxRezKD6aGKY;Jdq3 za!uxXrBzRp)K$V_Y}tE_XB+yiJ@@BMl+KLnSy$S-kDNB;ind&M=D2?9g;~0X7gspD zzO80+-|+w3SvPYFp<;~^C#6H8=?l^xx+Gg%y}5mUGpn|mwChWyLdpL&q4(}g8xaLmadbTOjV_lB zYcDVD)mioT@Zm#Rn_3S4F7vjHj#~J3kyi2N!l&h@QjBKze@i?)eg1L1tu}5OkMn~J zdAvySK&x?2UfSmSI|V*p&7Sx}XzH_rw?!5Px}2|{cWQ0_>?qNn&t0yE@`f$Ex!1hu z
    BQe)50NsF8Hz42_-zVCCkecaqX{qd~()9swj-c|VLxJY#l zC{YUN&FH(b>*kE2qcS>y-kpbB9~mXh_R*DEo*jC`I?pTg@-DIMce6KN5MGu%bK@z^ z``MdceAzYc^&K10Rbsy;y!@-^y=n5^%bTVcZP#wSkaU03YVPQLzqRh&Dik{Ye%TFW z|2cU=EiRKxF9+1$wK=o1f^Ay7@Uy8{MQGb)!%Qs9%tLM{oTE+CsSQ|H11h&bn2W|Q~ot98^4b%0!=}IVSR7)m_nmiQX|CSPr@6hy4llZ6bbkB%%Nt!*rR{uuWxBW0&&1Pq zj;V4f_9tIA`X2vy%+~tjvCr&<4s0tJJ9%Cfr8i8-O1S89c6Y{r{p(pWYL0%--02p||qF#JPH<7q;ZD zOw~K_|FPt&g~gk^HpkhwUBCMJtk)}bMcbHuLHrSXcL%{X*RT3kYDx?A4l zQ*+ldWi+1NvMTDqwHLcXzW1#-b?4=bXV5& z6SI_~aN5pNc~7OliOWvJ9=W&l(#boo15a-&`~Cd%ZtEv$|q__0gQ!Vh@y3*c)axpIRe7T_Z-$O3Fa2bN1p7 zMfaswc|I$<-F>I@iQdtVO5glaTJxtCZcNFGo8I0nyI$s~cYKYMb^X@M8j%X3Zaej( zoRs|6cE#^6=Y91m*dpHbalZ4mt-E@jgvCEw6FKp9teoDgBJX17ZC+CU_ufj)i+xp9 zK5=(P=n=i9>$T55%Np~{e7)4h$79RCPtWHc%l`h(jM0C}xajlN zbiwz?&d2CUFCh5VlZQI!Mg12`@U%xe>VFR`+Lfb_#a;$-}rVy?pySF7OkI;)RsK@ z@$jq1)~k>IoKAa~B|6jlV%5@)n02Wu*{6HN9BaI;ds#d7k<_U>f6cCy-aQ}NR&3jp zv+>W0)cXdh>n5&h5YCr5zIuL@h_u}8>=4!oMnbLJb0=*2{5N~zth+s*ulpUpx>ZwI zX}aB$sRkyiZG191uPaMU+#!1E&ipfLZ+9lB7Kv??ci8qjdSl8w!=>|l)i*rO(4Y6~ z@$$kKH<_3JtqW3GRB}-`sAylcsgSo?;?<8JrsZE*MEX!Yx0 z_ImdD{$>et(a?kXS>&3Kl}LP zZC?)I)S0v6Upr>=DQkaYyqw79006pRaonw`bqO4S`V(VDnqw$8>Jv zUiSDGtMCTDlQB_iyFE^quI;E;k~q6ZZ~5+7FEd>h_e_>371!e*MGlR`Fn3m8TFi*v-@AWKYNz@^u1c(FU6Fjb~&4SFJD@F=Im=* zYyU@6=PBAOKXUKyF~bQ$f?sDdDK1oOJ``if{yNR{aoe#ihMQ({Y>5%vaKic3(Nw*X z%#Cs>(>8e>wmJKvA$6szZC2#0$6~Hf3w_#<@dJa%@bcdDJ8GY z|KGbycXRbOt$oDpdAI+}*Ij?=-`%?0di>gd^}Za-!l-?of(tHqUj4lF^n_iRjA8zn zN6vaZF=C$8u_fm4+%5%|N5Vy~1N)AZUe~fcJfo5;&^q&W=dIq5Ip01i6>mQ-CM5B{ ze$n&Ve6yv$?);vrQ2aUQ-?2{*RATRzDQo}Okh*T-q=v=6t3N!LYJc#a?A+zYmZ`fc ztup6wa(bveJ?Y}{@JG+uOs*+KuZiuG4d^Kk?otyVtpG4APo8`}^eo#xL(}W50Qsr@ei< z@BaJz6We=Le(t~5`9|X7&wqJwsq3z{v$=h^A^D$w|2vrlR}L?ml&{gkqBPSW=+Wfo zi(cyM9~A9m$@5G}chr7bx&C$V(>7ft{qGA*{7&$OOBudgX{E{~&+~9bmFG-r`RjY8 z?E0p8VxIo9_I*jG@BG@84W2r%n7OyKCdJxpxjv+iB@vpq>l^XJ~N zZ}mr#r&Lu0yif_dvf_3^FmI52MAysv5$#`J>3Rx#&tTOp+R3?n@g|pb`KyFFUsrB* zeU|87?>Te#^qs+1vg5p#*qKgWQgJU+WzU9`yg09|-7c@nm@nOU`#kK~u^`u1(#!tG z+Z=rKyGq4YA~Rg|O6QlV{<>FlqPNIo`VzlA!pTRM z9sL)#S7-T|xA(etojJSy>72={WM=NVe&_B+38U+t=VWKTapiY2&e}Zpa(;=`wz$3$ z{)Zbk{}auZdGTM?9WL;wOijs>wKLfWH>eYNm{43 z;mT=00`|>y`d7Di$(gzR*>(0Abx&lbbi_(kz3Q+{ym|h7;aj`QuZ<4PDaoB&eA?=r z!@j@VwZ|`XGTQ9=c7Man=JP+=ozy2Tv1>hYC+x~RbKcEU&#ZUfw(d^ehQ6c;=W3_B zJXx#y%TK+BgZ-x1%UvFKZDP|_ep)bn%FfS)XWzHal+Scfe{1^WU{Jru^Dlez`e*0c zuiP}zBlcLceA&kRx9{K7S3VW{E&0tOgN^?dT=0AnVC26qnNjWkhpIWNW^HQm%V!;3dHE^g-R?Grx--pW70)5t4fZGU*m&N|)ab3J0y z3O2vK^~~)2v+I+pzI}hNF}uENZnU4s-Y$)@fEOx8SG4A?%W;0rSqieBK)QVrk0 z?RO-WPMm#Z^7PG5cis8$Wy+$Ueodw66K|g?eZ2V0-qUw{|202zU)O1DVD_$YtAyQ( zGhcU2;d<-5jdjnO=+{@dYuhguT7N5fcyV@J+uwW{>Fv6j46S}jjgu;q{?_>H$lfbB z_5QL;kA!O0#>f{rPib>k`c%~WF0Jp_(jCEz3T zO|9p-x$97pvblrcPLaf}GixXMXKue;rImG_$6_% z@4kDs=l(rm)4Y+h?a8eP?0%ouZzNk;%^{LzI!`7n9lRaV<%`Wm?u4SI^^8M=FbEa;NxXYC)$2&8+U%T{G@dVrT zuVbEEd}+M={iG6sn@SbtcW>SK)AsMJY{g^q_lJ*(^Rddc?a)eS@zWBjytF4%ahdka z_*G%`;Y(9he|YH@N#+HgZwS?T!nQ2kWb&@U&SOiv19o;_I~(xq;*^~#okdH3M7d}$ z_0OFCd**fS+Ua?T>y=Ai*TvO4R~6Yt-8bW|mF{1VoL_#_=1Q23h}BAqdm9;6isnX? zUFWSZ-}LpVaMz!yMa`3|++Mb&U7xUG#))3_z=zhvk9+yC_^R%v~gb>{eIeL0smV{ZYQRlIH2I@?%<%7ls5D>d@sIo?fZ?9TlbZB@?-)RPIU5i<`bmZsoU&kM))~o$6O8 zeN{C{VR>WvooA-DTLpQwZ(m&6Qdi`z-oeT>cSCJob z?7#7SW~aAX+^(!@|1WHRH*Jf(qMAlYP|=hV!q?9oo@5`_J$sqy!?YCF=$?$5GE+~N zzgVf(-FxRuiAx>N)L8etr9mliTqjvA*ZutKb;4g@=IheCx1FYK-6`Vo?3eM*XGD{8YMwWc6($t|9`@C$6&qa`w;!-3oY&*S#l$eMX&ds zm*`Y)7QKl(I%1SQ2c377W1F(`_v5grx=*>U-JexyKHoZRmtNg=ue;r6z6P!H-064r zY5=Iw^E!xAh5zfKPdG%am3J?Z+7qd{{fxKf&IQNUZu#@X+7A>Rmh+cZ#2nGPKE2N2n|ATu zb?lp79{F*JZ@ba;^}<3+^=J1us(yX!_3GK%RYF{IMR-*|e|df_>Fuwd74Mf+B_~at zrc~2=Ct-=`TDfgo8ISg#Qsok#=pCY`yvKEGB44WCOz~6wJAd4bSV z@9jL}ZFp<8h~TSzPpYP@UEwq{_*vX1<%zqdRn@+$wKux{{xy4{;I5s&XI{Ub@MUiP zs)Hvc+Rdr>T#+Kb&biS^ec|Gid&^Sx#hr}VwZ!xPrw#1ghP&1&XctMoT`PFB_tev8 zivItN3cEzse^2^zRxK;w*}Ls4TmI}g8^19{@5Q5X!DTVk*XmyVtZTmffAy#C5KB>KW;v0QGcf*}wPu$dkSGz?%3!78def#^{ZJ&#L%#Ulk)iv!Azxrp+w%%Pc z++JUPG5dN~v-z3h(%QFsC#kC&o}Rk2)ROz=>kI48e60@JWF~vFGp5OAg2~#9!*4=^ zFMgE29k4a|MBob*k-XTZC%eTSuGxIMaPp*|KTn)fmoxk1qQCa~?JJ@&^()v<%I7(~ z)h}C+wtmvjla4A?Z4)H){4Sexs&rjsYnZTUVaq&Ebwitte6jv-4j=lifArYxX%D}K z9NM^XmdlIdn|4bD<+s$&tARG;e;++H;G_#dc0U zaAyr)vpm1@Elw;KzZGxYCRxN8Yw$PZNRE2M-2guot=!}_$@_Ot-C3u5eBvej z*e`i^&*g?XuG-7E(!6i+?~k=tZ#wijsW(=hgupjyQO#MJTF#)7Yr)|7CIIcTW@lRmVFw-|96}mRfD|wWH|M z0^QyxYc_A$Id!L4h3`9wxn{q2P29Pxtc;tD|NFwny~z`$mKus^lsuYUuJ80ZKBj54 zn4gJT-R$;9ArC!dXP?*;nP+j^PvNJ#|H6k8Z9TsCPf2MKbz0Tw@?1fx-B=>lR5agr z-FfRpvpIs7=sB%7-upiE*`{^w6>rX}F0WnlK6*Om)aO@Dw_p5v<=-o5$37?ZeLE@z z)&73Hy`k{(t`>u9ASuW>%I2mNc^9=c@^3p^t zuG7KOE=~W#aE!nDP3W^K>&z7MtIv#IZ~UY?xj5ROG;Fisg^*Ckg~I-Kb}$(4ud}>o zzx=4g$c zl|Q>warXJds<$e|fA>xK(s|*v?(LmZcgE%aeWJBxf4*nntuG-uB36ZGzkW9O{{DX7 z{W*7gUI)y{%M6OwGs+Lr)C_-iPfx|}pMuo903-P|s}31Eooe3_E_rg!wW_Z>{3Y*R?LsjC1#$)|9`N+j8WLnon-N0aNp;#=jRl9q-k#3Ekhv)IBTv z!^O;Ax9wXt%&$^?HSJNUWXH2B5f|BaP5g19HlymD!X@XxIp10)bWHL{VV`?)uZ~Lb z-YL7j)SkE}f5zFidg4xn#$`R`ibi%1Km0BG+4!{d^z&FdoCc5|vhndQ4=rpRr^-|g*AM&@;GyYhXr*BbA-FMIBDaWZrFola%( z^Ddu`n@bt(kKJn{{P1)Pi=OM7T!WK;Iwt*GyWi#VXLikEr&Enx7hGRdZ82TGDBpUg z*U=eUdc*}!|Eo(`H*wW=O}_h&x2!LnJxxEp>09*bC3DLHKCpW^eS z@&un?^{X3GEEPpN#b>_0eyR1Xz%hTrH?Gg5G8N3P9{v9Gbnxr@`(=gmBZdBJrE$&e zdHkkqiTr-i^wsK7oo7-%wQktm=^4B8i($Tj{A?rjB}yNai{~!U+k5-VEl?%As5sLg zI=Sk8_nF?WyF_kXKDp?OVPN2qf1Ry6oX_mqB)e7rPTH9%-y~b^>-z3bK~8c2R0nsa`u-!f@6}h2UN6ar=$T*L$xnudFz)o^sId*)K*1sp}_e zlk*$jpPv5wJay)5KO!PF$sLzV4fz zO5I~Slf4xhF*CQDTuC}R>1D*$45^?kR-KHtf1N8b9_!`UUub+dagxk=F)*OB1dbx1jq{^Vb z4$~e?UUB)dd53%5-raMI$|o(on||%P>ZWOB#@F_}bW)u8`g)Q4M4^3m`R+egE6V@> zJbT0bYyn&Q+k)qBGquJY?Kl&@WufEAKSf_Z1#^@ikg&bjbfSjI=j&6W^q=P@d))gc zAf@WixB|8oV4h{etPO0v6DPH$iOyUfWf96*s}=OAS5;@eib~FtH*ep`9(la}?a^<4 zRj2Q?l&(wF^9b21f0J!nX2tZKeV0^kOsEc-pDLYwZ%&H-@dxwTa3^YSTYw!gJ* zT$phxX8q{{dO>g2y=RY0SvQfvvFqOVn{T2$o>kqvcdw!OYvz~luj9TMHm$w0T43j_ zCFg9y&&X{%^K{On?yzG?d5x~mqOB+Vk?&vhu=B4+yQ$gkX+JG3XKp?>$7OEMrpoa5 z4D;%0^|!s#pM9ox&5UVvsd^{)CorbFJ@9|FN$+jGir4y$Ij_T>ZOZv^ZfZuPx%Iq* zIXd+sC;iu%?lx(dZ1s6Vs@~DpLDy5)={K8H+%94|XqMoJe&sd~XY}TNB z(Ov2Dxp|B>`+p|vOs&)M=5T-3)wUCRoKOB~)%AK-t?l&8DZ=RbdvEh?dMVzY-gMpld@^Rj@AfJG zwjTKJlk+lv&Eb}_>IFe}C!bNi=_<#y*K5MYo2eGpXDu;gin(Dly-mw#R`A-N%sb`x z*72;=w{8E$-mL=iQlZ1?KTGGFVQ+WNN)S@LYVqDFC!%)EGkqFYJ#nYb(^b80;za^fI(O(*PdymcW(p%w^{%EYcHThU+Y~{Ao-=&`{bXdiydaB>I=UiU4;j_2t z3Wd+BbmoVopGmi4e|R!xQK8_@%1bQF6MwGnSNLkKzv$h**oUWUbd1ukn+WB)@y`4% z?)&Wd`Wez+4319Psgr7Pqptn2*rc5bN5A$;WXC*reO6`p@sZ?;*&>T)UXomRd`5nJ zqw8DVA3LIAyFSR>+j2&=*Sg;2nZEYS|8Kp$o>@Cj+39umYvjz+8U>F&@i-R?C6=W8 z`_y-Mq4?(IL7p)s*J|$6owl<1{qucW;fW2YdOV7^OLL~B%9|Tqf8X8Z`f6_Fr^)*A zXXAHC^!C4VJ^z{gv0);&@|PJ!e{Ig>t~=;{^;OXfH5bl~7>BnzQ?x(t4>sB#esYhw z%1rLFZuXzooROS4c}4N8Gwp|_{!~rZKcb#jYc2IUo9lhRjHDAW`O2~9+aAO|v+^vs zm{NIPbA6e&qZG$+wGB%zB%kQzHu(HMrt|hP(Oae`7zu9S?U#8!^JdF-#XW%!KAoy*p7hh)e9^n`{8ru1QmUUcEzs~MGzIZD{{#IeJi1V{wTYEm*X`cT$CnZm}O#Z{n{GSsa+ePG~>KWCxnzWV+_67RPJ%IZGB^NIp<4zWAfJ_~Wk1wL3T;cdu`na8CC0Hq+l0 zGx=L}ibZFxpL2bl^tq{4>K9%4r9Qvj-Y4ezEPPF58-Gl>o!WLYsaWf7myIdwreA8! zl{&U4XS>n$6MsFs?){eCR{AS$@4Y|L+7s@5wqfi0rg$mfh2ZmPsw@6p<}Q5M#JNNA zW@q4`9c#`W-Z}5u`5I@#)OAXqH$U4ZdGt+)Wv9_6om&@;JEoV*9KBZi@bsTkpQk^p zy}rirH;d>@@$=7)PTQ$d9s0^~+tFR&Gf(fjvv|ovhvjFt`Q6X1+yCxcalrroanAFF zs+R0tANWG>*)>&%O0&pK$zi9fHnr#;>qzV6r+Pu-(ac1}$?6*J9f-*1CGr(=}AueO?9 z%XKnl{pmNkiM`hY%__e4XvoL3RiB{`~aJ5s{54C-%4>OSA9E{q6efA#07&=WM-*#_us3xh|`@hdq;={ryS5%H4Cj zKG(EQKJ)ck5&s(P3E|8CaP-c%Rd7!K{w&c!KIv zwLix{Z!6ww{>3qQ6Yqm*=VrCcDhY~>oO#GvFlb@N8UJs5Nr%l;KQqfNJeQt#;?E(` zitD?Ir=E#ktn<0%_!Oo0VzYC(PTtY^(sI_U^^@|Fyy&)H`u6?%lm*whr?{*>Jpr30U5;JrA zRG(*gDLJuh6L6S^Hz?xoZPpHJ5dUeIQlJ!?VgzKUg&Dy@#r%T)Q)aVGvo5r;KjS)1PH?5St& z&zxMj$z|fslNC!R?2J2Qvg}gf8peAm>$XqUh<%sNtoJ$j@4pk=wwGqESDSK1*y~yL zlryOhBr-3`9h<5v-oW$XLQY+=ORU^_dFNNB&x`v%>z;Dvb(Ye7w$>2Kq{esq6m9g4 z>;qJ0#@FdBn;!5?%}cY`@n1z%U9tMi*QKk^Jl)E&qV-s5`J|moj;)VtOQ~*oeNl3u zdrR@Mgqf3m_GXnD9?I<1JT5TP+j&QDd6!^m)c!)5li*stTv>MCndKTY{R52B=UR5! z+5SCTk@uNVbmsgcA3P?e+=~;NIa{;!%-N&wa+f7XrSi33uexF(xT{hkV!M&K9LKu( z?YovqB{$6UxHm`R3&)3tPiNF#W?B=z`nl2^&hRHK)04h+Za7ghdD74McFk?<-xco4 z7hT^H%DpV%A&=t|A*O?Ih`S-K)OK$9y7L;Y1=Cr=RZi~#?`TH5d&t@N8y5UMOyF=9G z(;fxOSH-wp)!h4(Wzzh5E?)lbGwz3`?mRPVn?d*)zv+`#Twb2?@5}7pQ_t+~Qu zy-q(51gZS{qqN3q@llPe`Er*Mql=DPef_UzbpK-KljZWxhm(I#kmHDb_BLWgO5Eua zbB;7LE)=x+c+2jM)urBv7dNEt+auF_?8)LQM&czKcgLCMM|(V*lx1+u;N0pnt-E$# zQ?7hm&losoMeEu-+EZqpji_-e)4Jly@%+E%t9$8(OqQ%>Ic@E)^z)>9NJ<>fO#O`W z*Jf@uweDn$ocTW7=sj2LGb6L#)$1c-J&mqU33?WFCFqG=(#>nVXH-wh`?uunOW!qh z=f)V*s~MZ41OId8CAP#hu6R-NO8xv%)2vXpChp{j@SpY5*5!Tu#cNqyUanaA+c~4+ z6+;%Aa}JY|k$t+OnNhl-btg0PVdaWHSO4B94pI92;as|H_N}$ybK5m1cUfKZ67W(j z{u_0yWctd0uYXUiJySY~!|7e6GT&{{Q~fOVcO~ZU_i$JkF7ZCP>Dc<1uG7oS9%;>B zkzv^B+;Qf(>9-r#TaO%6EHcy_hK)HBg<&Dy3`SF`Y-cQ#I z=(f&|67bS3*3H!{Ti;$j(_3i$QkiYVljUt&a-J->pxyCZ&+XOg>5u=l=zZ#;vH8!5Wo{FH_-{PqRw&xkb?$l1*PuPlYv*+q{EJ^Qdv=Mx z_16n(M*E*m&!~EsaKp&|@texr2&3Xfip5Tg{0vj;Ztm>=naONh4Q92R z&Nw3Ubbi*wb*Xws-zY1xo$oEpTr=l(-kDiyM>}%rO>2#Rao7Y;tCDUs~WRb+0rf6H<3~wLc7* zS$UjkNnL?#nC8s;Jd+!k71lmVKN!Lv5;Al3iJF&h)|}z&v=W*r?)vPcieB7KhW(%4 zoi=DRD&O4y=i}PV%Vw&l6`pFpcP!@3J9YcSFM3**Jr_=Q#9Vo)GVRK;0_{zucN1lT zls+H3W5uUE^L~`d%=xC8?uBrU|9i_6f zq?B&nTXn_g`pWxT){D>NzSdS4_gUD}+|B56p@U_>qR%hwqxxT8JuRvzTPC$>?c>Qs zVRA3r9&M}cQv58X`uSwU!j!xoQ{U;6J1*N>^ei`*<%xavGK5Qfo+%x-&~nf6pvdOHG~E<8e??vGPT*QGMCxg%{X+S9&hr@a@crQ+Ip> z41eEr*H1lWVsB*5eK)@@;)&m#!jnEK@AnCwzWRz?&~NWflcvcp_Jm0-ef!Gk(_{Wc z4|fVgZ|>eD{ra-k6XA_D-&7`)F~uy4cH4P9q;8wfp`yuRn~hP0zLe;jL4 z^3+3?r%D)!mw11la@Oo;%DUEh4gdcv5j?mqTcoOUrLUx}#`6_dzbk1!TySFY5}zHH z6b>GZp6gNfNYKP)ul*ju2b+x6A1qS+ESm0e`jO$@AeOmzA4|Rb&)#Is_}W2 z$u*t%&Hl9tpNy_gaenrzRD^ ze?BaT-rRlWym3mMYN}k!vrqEMN8goQUifVC&Yu;XHuImJ_W#wB_Q0jS%2!@*Icxto_X`m^f!|Fv~jUz zr%}y&-clP3yF%&l=suYx`FW;FcF`f8SBdT?f1i}SKPI=7_${2y zqWbxWwOGO@{`Ka&R_O9QoKSQ;Biv|x{bz|u^`Cg84hrOQtb)8tbZN}WSH$A6Jyd0kL@7KFEXWE^nR%%^m=rhvau=cW9 z+56eo+lybvrRtq#;c|cR)2{o@hv_0!6Ic2xSKc#8wkyiKDpxW?!_rcnr$6b?>*E2x z#m_JNr#SIv!9=j%=d3xyKJ|=$Li!0%>pgqg`K4#Nm5cvIwY+nGc1`2R>NBidb9(}p zC%r7!cRKxjonhCl>1_ok*i=7zu9tH?9KW|=7kioVUGYU{QdxE!^?G(%vsj3ENy^KV z_X79s1k21ky};1$MS5IE(W#hA3(lJ5rNn7Cl}~y!rwc*?E>?b;wbL!|_wR|aCv`bIRX=y`w`=l@ujb@k zcuMAGXPC<~qXHgKkF`s)c&6#wLz~4OOnzTqeJ54#{FgkX43P-7i93TY%@+@THnGJ? zpUKqoLG$SgKjvx|HdpObdGJuk>)Ba-dB@xL_q9x`E?>0FykzF-f_1{ZonaC)CTlG2 zzG?pUHOJl=cW0R9K4tpzZ|%nEJEs<%j9K4*tLELZxVts;zbF@9TPVC>C7Y(|=TMHs zM^hIDUfEkC5xj37|;?6zJvnRhO(lWY#y>o8+;psnBpX(fX zzwfz`iD|&IWu?ngBaEi2@IF2hyl~gVotMjZPTl#nQ9&r+h2msCrH_qEkKK4B?@;zZ z{JzG_<>qp3zs=1T{hc>=1#j>(p_%%oDf4vGmY?#Ra?$AinpC-KYg|s8`g5sR{NeWe zI_W5>*Ur{?EE?O-yuJ5X>$6j3>8XAdw-;A?LhS#a^Sbiq?o&7A9ZwzmmZr?RQ@|m- z|EJNid!NN#9qv0bebtJZmuEWXoS)+|FWBh5{Jt|w8SkX77JIzdJhq4PtmEuMF0+bG z#Yi3VY!+OB+<6;yLC#*(GB3a=zOiBLt*K{(^<|x2Z#PR?)^#S@X=-II z*XgNemI}&%;;m%v&uL00!3_>+^O%m#v%L#t&6}nyo^f}Np3`@&;{8Y8_|11^;<9zI zt6XR3wtGv~{1@LPls=2|FLXUw^KGxSp=QoZZ^7S_B!!D6Rz5iw?&O!~@~pP`hSJeD z?z^0)|Ga%&=P3L7=x$G4U$^V$w3f~>I$thhZlpiud(HjCv*{`eE;v5{EmO?ect%T)Q4G1GhQ&NH*b%IrUDpNntfk14jhr53XF zOl#4pm}zy&#h>@prJUo~JL~70pBB!sKei{VFg-x<|Yo^X@ zQ%3IP+tb*4JMUF`ozVPp=hfHrj_Zc6JvPPj>{Y#DU&GXQTP^OaJ~Q`Q;i>F>HZDwDwk&pArLr%1 zu4(SC>uy%|u&EbV8(+b7ZH0i`%j+@FY zPY&vQewn*=%1+y-Q+8f9ymR8tuem?_qF1%VcJIU3>^<*p8#a7dfA7$e z>U&PoseT?W1!u;uPWk88c}BC?dJao?*38+_t8aVGF*@(K@b{DK>55-BZZ0T}<+}Vp z+3Nn?)?NSav}xXT{$sDOsyTN;IeBY2uaTAdk|_bt=4mcJ^VO*H<0Q5_qMK(gjnm*S`^mZ9 zKc#a)Kuy6>nKjuy(!39A{{MBm`qIee6uXv_S8E=__j9E>Y2Q6SOPQBVG3rm*Xx;j5 zB53h>?bV^(|?KS%DWSP2d`ltC%-Jg|B-nHYW>@QR0FFT44mc*=I z^ttkb<&kW@Oi}qSjST{im~1n6b}y@3CX|wQ?_PGwyONsiKNntje5QQ;u|KTBJ%SIL z&McjA;_drtH#4UWYnOXQ7rS~GjyopT1pCc={eMH??DJirXYy?GQW7?<_nvY___iFZKl^rZ1yS%6Q z9MV$#{P{{&ZNuRc_oLf7QVXpWRUMXbO;ajAb;tbaq@8j85q8DepKDw{<)y6KuN@Td zLhr}FtsZml=9#p7Htp!)>^yh-;C^dG)kZ1))}oU!9y8;tU2pptZO;~Y9`;P9s9$^Y z?83TQDenoQo8q4wnbVLUXs6gRg=_b&CqXA;^tUcPv-Zwiw`a;5>Nz(2*4usGBB^r! zqeR<0#<~jIpzP(3v$k59K3Mo_Lhm_dc3$1y(jRNfDl} zcWw?`J0Ui&eVYs)4_v^*$%9(-WG zsiI2M4$%ddpF5_ko6dQa&=saUDHOZg7rq*>%vnOT*zG!YPhxQjDCpOv|XPS zzTPV`^5fTxZV?Fbdee2oTeUb+&Cu-fEb*DMw|`eIRxILu#KLvA?R{+XF6n2Fi&*;4 zEu9?|Yq3+QWx}Ljy_wvZKPK-CedSkqcKZAirX9i>CERPbI@EGKSDy2t#tW=$Q()Fi z?@0|F?-tgj=!v{)j?=L3x$yYF_G=6O9b>JS9W__-fkDqXPQ}6nGDg=Uq>t>Jx>INA z>02CcKEG147pc0Ena?mQtFSL>>a@3xi;8cWzi=>V5eU+p67|gK)Tf!zGjE@5eP?Pv zQCih+!3E!%pF&seZeb8#c{P%ePuYL!WPvrSr4?JINLxovb9z=~%UfM2K51vJA#CkA?h1{GmWzv>JY+Q5&wSl9;lkH#VKZNUpVL-TI(fdwwgPdD zlA~{*C#>r_civ@#V$|23tQG+wb1%)}g>2uH^i_%-#drQb%ik&&I?dOuDo*>_imR=R z{70v~ZJA&ZIFJ$ip`%O%j^JnC{JfF-Y>Tn8{6>H+()BV|Tgv)Q^+?&;G?qyzS5EiM#3)f3xq%B~J_QwR#*C8Z!fRJ;KeM zI995>NqM=2C;D0D>9V}kb@t`Kciv5zf6?Ej=}c#x%cNCezjVC~693jLe*bd|L;J-2 z3r*U*TPABeacJ7lyd2)GTfBFR*!I`wxlYDhD$x6R);}eB2Y0A$NecVK$fk{oY=19I z@dkTTw>WZH?zU3pV%fRRwe$3+%Yk^wSoF9&1*fhhGDhNA z5B#4w?PDv<`{caPed6n9iRVmK~#Zz=e#@LITjcCTjmIm1Pt6=Kh(tSipEd+x;?0ikfv0MjnMe<^vQyXOCK z+2uGVpwwx923x^VnZ=58TwXY)*a#-y)#gxC$(s`OtjK;E$KC!jXPv&!p6_veK6j}4 z7w+^0uC6aG?W%QaIDEp>objL>htgja4~~^wUaG|hGuupGe=9RG-(K*2_WTp)HZ5Ce zwNN`v`F5!834^skSM*$+>ss~HV_UMbI21#jYK6O;U~2(Zrs!RfYGF9>pP_{*udB&0>Pc=w6hef{}rlKV!}*$Ul%<
    <%= link_to tags, posts_path(:tags => tags) %> <%= count.to_i %>