diff --git a/app/helpers/domain/domains_helper.rb b/app/helpers/domain/domains_helper.rb index 920628f9..e5a2ec56 100644 --- a/app/helpers/domain/domains_helper.rb +++ b/app/helpers/domain/domains_helper.rb @@ -53,6 +53,7 @@ module Domain::DomainsHelper weasyl.com x.com youtube.com + sofurry.com ] + ALLOWED_PLAIN_TEXT_URL_DOMAINS ).freeze, T::Array[String], diff --git a/app/helpers/domain/posts_helper.rb b/app/helpers/domain/posts_helper.rb index 7b6efee4..fcbba86d 100644 --- a/app/helpers/domain/posts_helper.rb +++ b/app/helpers/domain/posts_helper.rb @@ -43,6 +43,11 @@ module Domain::PostsHelper domain_icon_path: "domain-icons/inkbunny.png", domain_icon_title: "Inkbunny", ), + Domain::DomainType::Sofurry => + DomainData.new( + domain_icon_path: "domain-icons/sofurry.png", + domain_icon_title: "SoFurry", + ), }, T::Hash[Domain::DomainType, DomainData], ) diff --git a/app/jobs/domain/sofurry/job/scan_gallery_job.rb b/app/jobs/domain/sofurry/job/scan_gallery_job.rb index 3feb0410..26a76431 100644 --- a/app/jobs/domain/sofurry/job/scan_gallery_job.rb +++ b/app/jobs/domain/sofurry/job/scan_gallery_job.rb @@ -19,6 +19,7 @@ class Domain::Sofurry::Job::ScanGalleryJob < Domain::Sofurry::Job::Base const :tags, T::Array[String] const :description, T.nilable(String) const :posted_at, Time + const :gallery_log_entry, T.nilable(HttpLogEntry) end sig { override.params(args: T::Hash[Symbol, T.untyped]).returns(T.untyped) } @@ -121,24 +122,17 @@ class Domain::Sofurry::Job::ScanGalleryJob < Domain::Sofurry::Job::Base end posts.each do |post| - if (existing = existing_posts[post.sofurry_id]) - existing.title = post.title - existing.tags_array = post.tags - existing.content_level = post.content_level - existing.description = post.description - existing.content_level = post.content_level - existing.posted_at = post.posted_at.in_time_zone("UTC") - else - user.posts.build( - sofurry_id: post.sofurry_id, - title: post.title, - tags_array: post.tags, - media_type: post.media_type.serialize, - description: post.description, - content_level: post.content_level, - posted_at: post.posted_at, - ) - end + post_model = + existing_posts[post.sofurry_id] || + user.posts.build({ sofurry_id: post.sofurry_id }) + + post_model.title = post.title + post_model.tags_array = post.tags + post_model.content_level = post.content_level + post_model.description = post.description + post_model.posted_at = post.posted_at.in_time_zone("UTC") + post_model.media_type = post.media_type.serialize + post_model.last_gallery_log_entry = post.gallery_log_entry end ReduxApplicationRecord.transaction do @@ -256,6 +250,7 @@ class Domain::Sofurry::Job::ScanGalleryJob < Domain::Sofurry::Job::Base content_level: post_json["contentLevel"].to_i, posted_at: Time.at(post_json["postTime"].to_i), media_type: media_type, + gallery_log_entry: response.log_entry, ) end, ) diff --git a/app/jobs/domain/sofurry/job/scan_user_job.rb b/app/jobs/domain/sofurry/job/scan_user_job.rb index ccb6dd28..c272e6bd 100644 --- a/app/jobs/domain/sofurry/job/scan_user_job.rb +++ b/app/jobs/domain/sofurry/job/scan_user_job.rb @@ -7,14 +7,24 @@ class Domain::Sofurry::Job::ScanUserJob < Domain::Sofurry::Job::Base user = user_from_args! logger.push_tags(make_arg_tag(user)) logger.info(format_tags(make_arg_tag(user.page_scan, name: "page_scan"))) - scan_user_profile(user) if force_scan? || user.page_scan.due? + user = scan_user_profile(user) if force_scan? || user.page_scan.due? + + if user.avatar.nil? && (sfid = user.sofurry_id) + avatar = + user.create_avatar!( + url_str: "https://www.sofurry.com/std/avatar?user=#{sfid}", + ) + defer_job(Domain::Sofurry::Job::UserAvatarJob, { avatar: }) + end ensure user.save! if user end private - sig { params(user: Domain::User::SofurryUser).returns(T.untyped) } + sig do + params(user: Domain::User::SofurryUser).returns(Domain::User::SofurryUser) + end def scan_user_profile(user) logger.info("Scanning user page for #{user.username}") user_sofurry_id = user.sofurry_id @@ -22,7 +32,7 @@ class Domain::Sofurry::Job::ScanUserJob < Domain::Sofurry::Job::Base if user_sofurry_id.nil? && user_sofurry_url_name.nil? logger.error("user has no sofurry_id or username") - return + return user end url = @@ -35,7 +45,7 @@ class Domain::Sofurry::Job::ScanUserJob < Domain::Sofurry::Job::Base response = http_client.get(url) if response.status_code != 200 logger.error("failed to get user profile: #{response.status_code}") - return + return user end user.last_scan_log_entry = response.log_entry @@ -46,7 +56,7 @@ class Domain::Sofurry::Job::ScanUserJob < Domain::Sofurry::Job::Base logger.error(format_tags("user not found")) user.scanned_page_at = Time.now user.state_error! - return + return user else user.state_error! fatal_error( @@ -55,8 +65,20 @@ class Domain::Sofurry::Job::ScanUserJob < Domain::Sofurry::Job::Base end end - user.sofurry_id ||= user_profile_json["userID"]&.to_i - user.username = user_profile_json["username"] + json_sofurry_id = user_profile_json["userID"]&.to_i + if user_sofurry_id.present? && user_sofurry_id != json_sofurry_id + fatal_error( + "sofurry_id mismatch: #{user_sofurry_id} != #{json_sofurry_id}", + ) + end + + if user_sofurry_id.nil? && user.new_record? + by_sofurry_id = + Domain::User::SofurryUser.find_by(sofurry_id: json_sofurry_id) + user = by_sofurry_id if by_sofurry_id.present? + end + + user.sofurry_id ||= json_sofurry_id registered_at = user_profile_json["registrationDate"] if registered_at == "long long ago" user.registered_at = Time.at(0) @@ -85,12 +107,6 @@ class Domain::Sofurry::Job::ScanUserJob < Domain::Sofurry::Job::Base user.state_ok! user.save! - if user.avatar.nil? - avatar = - user.create_avatar!( - url_str: "https://www.sofurry.com/std/avatar?id=#{user.sofurry_id}", - ) - defer_job(Domain::Sofurry::Job::UserAvatarJob, { avatar: }) - end + user end end diff --git a/app/lib/domain/sofurry/gallery_page_parser.rb b/app/lib/domain/sofurry/gallery_page_parser.rb index a630973e..95edaef3 100644 --- a/app/lib/domain/sofurry/gallery_page_parser.rb +++ b/app/lib/domain/sofurry/gallery_page_parser.rb @@ -55,15 +55,27 @@ class Domain::Sofurry::GalleryPageParser def posts @posts ||= begin - @doc - .css(".sf-browse-shortlist,.sf-browse-shortlist-zebra") - .map do |elem| - title_id = elem.css(".sf-browse-shortlist-title a") || next - id = title_id&.attr("href").value.split("/")&.last&.to_i || next - title = title_id&.text || next - ShortGalleryEntry.new(id:, title:) - end - .compact + [ + @doc + .css(".sf-browse-shortlist,.sf-browse-shortlist-zebra") + .map do |elem| + title_id = elem.css(".sf-browse-shortlist-title a").first || next + id = title_id&.attr("href").split("/")&.last&.to_i || next + title = title_id&.text || next + ShortGalleryEntry.new(id:, title:) + end + .compact, + @doc + .css("a.sfArtworkSmallInner") + .map do |elem| + id = elem.attr("href").split("/")&.last&.to_i || next + img_elem = elem.css("img.sfArtworkItem").first || next + title = + img_elem.attr("alt")&.strip&.rpartition("|by")&.first || next + ShortGalleryEntry.new(id:, title:) + end + .compact, + ].flatten end end diff --git a/app/models/domain/post.rb b/app/models/domain/post.rb index 1e596bf6..234618e4 100644 --- a/app/models/domain/post.rb +++ b/app/models/domain/post.rb @@ -188,6 +188,16 @@ class Domain::Post < ReduxApplicationRecord def primary_creator_name_fallback_for_view nil end + + class TagForView < T::Struct + const :category, Symbol + const :value, String + end + + sig { overridable.returns(T.nilable(T::Array[TagForView])) } + def tags_for_view + nil + end end # eager load all subclasses diff --git a/app/models/domain/post/fa_post.rb b/app/models/domain/post/fa_post.rb index d9e86457..a0225ff3 100644 --- a/app/models/domain/post/fa_post.rb +++ b/app/models/domain/post/fa_post.rb @@ -185,4 +185,9 @@ class Domain::Post::FaPost < Domain::Post end end end + + sig { override.returns(T.nilable(T::Array[TagForView])) } + def tags_for_view + keywords.map { |value| TagForView.new(category: :general, value:) } + end end diff --git a/app/models/domain/post/sofurry_post.rb b/app/models/domain/post/sofurry_post.rb index 0d1d9c24..8c2bb807 100644 --- a/app/models/domain/post/sofurry_post.rb +++ b/app/models/domain/post/sofurry_post.rb @@ -22,6 +22,11 @@ class Domain::Post::SofurryPost < Domain::Post attr_json :last_scan_log_entry_id, :integer belongs_to :last_scan_log_entry, class_name: "::HttpLogEntry", optional: true + attr_json :last_gallery_log_entry_id, :integer + belongs_to :last_gallery_log_entry, + class_name: "::HttpLogEntry", + optional: true + scope :for_media_type, ->(media_type) { where(media_type:) } attr_json_enum( @@ -69,7 +74,7 @@ class Domain::Post::SofurryPost < Domain::Post sig { override.returns(T.nilable(Addressable::URI)) } def external_url_for_view if sfid = self.sofurry_id - Addressable::URI.parse("https://#{sfid}.sofurry.com") + Addressable::URI.parse("https://www.sofurry.com/view/#{sfid}") end end @@ -97,4 +102,14 @@ class Domain::Post::SofurryPost < Domain::Post def self.view_prefix "sf" end + + sig { override.returns(T.nilable(T::Array[TagForView])) } + def tags_for_view + tags_array.map { |value| TagForView.new(category: :general, value:) } + end + + sig { override.returns(T.nilable(HttpLogEntry)) } + def scanned_post_log_entry_for_view + last_scan_log_entry + end end diff --git a/app/models/domain/user/sofurry_user.rb b/app/models/domain/user/sofurry_user.rb index 306e36f9..a2e7cecd 100644 --- a/app/models/domain/user/sofurry_user.rb +++ b/app/models/domain/user/sofurry_user.rb @@ -2,7 +2,7 @@ class Domain::User::SofurryUser < Domain::User include HasAttrJsonEnum - validates :sofurry_id, presence: true, uniqueness: true + validates :sofurry_id, uniqueness: true, allow_blank: true validates :username, uniqueness: true, allow_blank: true validates :useralias, uniqueness: true, allow_blank: true validate :username_and_useralias_match? diff --git a/app/policies/domain/post/sofurry_post_policy.rb b/app/policies/domain/post/sofurry_post_policy.rb new file mode 100644 index 00000000..2747c6ab --- /dev/null +++ b/app/policies/domain/post/sofurry_post_policy.rb @@ -0,0 +1,3 @@ +# typed: strict +class Domain::Post::SofurryPostPolicy < Domain::PostPolicy +end diff --git a/app/views/domain/posts/default/_section_tags.html.erb b/app/views/domain/posts/default/_section_tags.html.erb index 25cd2d4c..ef725c31 100644 --- a/app/views/domain/posts/default/_section_tags.html.erb +++ b/app/views/domain/posts/default/_section_tags.html.erb @@ -1 +1,24 @@ -<%# By default, posts do not have tags %> +<% tags = post.tags_for_view %> +<% return if tags.nil? %> +<%= sky_section_tag("Tags") do %> + <% if tags.any? %> + <% tags = tags.group_by(&:category) %> +
+ <% tag_category_order.map { |category| tags[category] || [] }.each do |tags| %> + <% tags.each do |tag| %> + + <% icon = font_awesome_category_icon(tag.category) %> + <% if icon %> + + <% end %> + <%= tag.value %> + + <% end %> + <% end %> +
+ <% else %> +
No tags
+ <% end %> +<% end %> diff --git a/app/views/domain/posts/default/_section_visualy_similar_posts.html.erb b/app/views/domain/posts/default/_section_visualy_similar_posts.html.erb index 6174b5e7..86dfb844 100644 --- a/app/views/domain/posts/default/_section_visualy_similar_posts.html.erb +++ b/app/views/domain/posts/default/_section_visualy_similar_posts.html.erb @@ -1,4 +1,5 @@ -<% post_file = post.primary_file_for_view %> +<% post_file = post.primary_file_for_view || return%> +<% return unless post_file && is_thumbable_content_type?(post_file.content_type) %>
Visually Similar Posts
diff --git a/app/views/domain/posts/fa/_section_tags.html.erb b/app/views/domain/posts/fa/_section_tags.html.erb deleted file mode 100644 index 4c6575dd..00000000 --- a/app/views/domain/posts/fa/_section_tags.html.erb +++ /dev/null @@ -1,20 +0,0 @@ -<%= sky_section_tag("Keywords") do %> - <% keywords = keywords_for_fa_post(post) %> - <% if keywords.any? %> -
- <% keywords.each do |keyword| %> - - <% icon = font_awesome_category_icon(:general) %> - <% if icon %> - - <% end %> - <%= keyword %> - - <% end %> -
- <% else %> -
No keywords
- <% end %> -<% end%> diff --git a/sorbet/rbi/dsl/domain/inkbunny/job/static_file_job.rbi b/sorbet/rbi/dsl/domain/inkbunny/job/static_file_job.rbi index 2eb7c2e8..a8eb2749 100644 --- a/sorbet/rbi/dsl/domain/inkbunny/job/static_file_job.rbi +++ b/sorbet/rbi/dsl/domain/inkbunny/job/static_file_job.rbi @@ -12,16 +12,5 @@ class Domain::Inkbunny::Job::StaticFileJob class << self sig { returns(ColorLogger) } def logger; end - - sig do - params( - args: T::Hash[::Symbol, T.untyped], - block: T.nilable(T.proc.params(job: Domain::Inkbunny::Job::StaticFileJob).void) - ).returns(T.any(Domain::Inkbunny::Job::StaticFileJob, FalseClass)) - end - def perform_later(args, &block); end - - sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) } - def perform_now(args); end end end diff --git a/sorbet/rbi/dsl/domain/inkbunny/job/user_avatar_job.rbi b/sorbet/rbi/dsl/domain/inkbunny/job/user_avatar_job.rbi index 5646189c..6f4831d0 100644 --- a/sorbet/rbi/dsl/domain/inkbunny/job/user_avatar_job.rbi +++ b/sorbet/rbi/dsl/domain/inkbunny/job/user_avatar_job.rbi @@ -12,16 +12,5 @@ class Domain::Inkbunny::Job::UserAvatarJob class << self sig { returns(ColorLogger) } def logger; end - - sig do - params( - args: T::Hash[::Symbol, T.untyped], - block: T.nilable(T.proc.params(job: Domain::Inkbunny::Job::UserAvatarJob).void) - ).returns(T.any(Domain::Inkbunny::Job::UserAvatarJob, FalseClass)) - end - def perform_later(args, &block); end - - sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) } - def perform_now(args); end end end diff --git a/sorbet/rbi/dsl/domain/post/sofurry_post.rbi b/sorbet/rbi/dsl/domain/post/sofurry_post.rbi index 65e13eb0..7012fa8d 100644 --- a/sorbet/rbi/dsl/domain/post/sofurry_post.rbi +++ b/sorbet/rbi/dsl/domain/post/sofurry_post.rbi @@ -544,6 +544,9 @@ class Domain::Post::SofurryPost sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::PostFile) } def build_file(*args, &blk); end + sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) } + def build_last_gallery_log_entry(*args, &blk); end + sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) } def build_last_scan_log_entry(*args, &blk); end @@ -565,6 +568,12 @@ class Domain::Post::SofurryPost sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::PostFile) } def create_file!(*args, &blk); end + sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) } + def create_last_gallery_log_entry(*args, &blk); end + + sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) } + def create_last_gallery_log_entry!(*args, &blk); end + sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) } def create_last_scan_log_entry(*args, &blk); end @@ -623,6 +632,18 @@ class Domain::Post::SofurryPost sig { params(value: T::Enumerable[::Domain::PostGroup::SofurryFolder]).void } def folders=(value); end + sig { returns(T.nilable(::HttpLogEntry)) } + def last_gallery_log_entry; end + + sig { params(value: T.nilable(::HttpLogEntry)).void } + def last_gallery_log_entry=(value); end + + sig { returns(T::Boolean) } + def last_gallery_log_entry_changed?; end + + sig { returns(T::Boolean) } + def last_gallery_log_entry_previously_changed?; end + sig { returns(T.nilable(::HttpLogEntry)) } def last_scan_log_entry; end @@ -667,6 +688,9 @@ class Domain::Post::SofurryPost sig { returns(T.nilable(::Domain::PostFile)) } def reload_file; end + sig { returns(T.nilable(::HttpLogEntry)) } + def reload_last_gallery_log_entry; end + sig { returns(T.nilable(::HttpLogEntry)) } def reload_last_scan_log_entry; end @@ -682,6 +706,9 @@ class Domain::Post::SofurryPost sig { void } def reset_file; end + sig { void } + def reset_last_gallery_log_entry; end + sig { void } def reset_last_scan_log_entry; end @@ -1304,6 +1331,51 @@ class Domain::Post::SofurryPost sig { void } def json_attributes_will_change!; end + sig { returns(T.nilable(::Integer)) } + def last_gallery_log_entry_id; end + + sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) } + def last_gallery_log_entry_id=(value); end + + sig { returns(T::Boolean) } + def last_gallery_log_entry_id?; end + + sig { returns(T.nilable(::Integer)) } + def last_gallery_log_entry_id_before_last_save; end + + sig { returns(T.untyped) } + def last_gallery_log_entry_id_before_type_cast; end + + sig { returns(T::Boolean) } + def last_gallery_log_entry_id_came_from_user?; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def last_gallery_log_entry_id_change; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def last_gallery_log_entry_id_change_to_be_saved; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def last_gallery_log_entry_id_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def last_gallery_log_entry_id_in_database; end + + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def last_gallery_log_entry_id_previous_change; end + + sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) } + def last_gallery_log_entry_id_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end + + sig { returns(T.nilable(::Integer)) } + def last_gallery_log_entry_id_previously_was; end + + sig { returns(T.nilable(::Integer)) } + def last_gallery_log_entry_id_was; end + + sig { void } + def last_gallery_log_entry_id_will_change!; end + sig { returns(T.nilable(::Integer)) } def last_scan_log_entry_id; end @@ -1529,6 +1601,9 @@ class Domain::Post::SofurryPost sig { void } def restore_json_attributes!; end + sig { void } + def restore_last_gallery_log_entry_id!; end + sig { void } def restore_last_scan_log_entry_id!; end @@ -1604,6 +1679,12 @@ class Domain::Post::SofurryPost sig { returns(T::Boolean) } def saved_change_to_json_attributes?; end + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } + def saved_change_to_last_gallery_log_entry_id; end + + sig { returns(T::Boolean) } + def saved_change_to_last_gallery_log_entry_id?; end + sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) } def saved_change_to_last_scan_log_entry_id; end @@ -2026,6 +2107,9 @@ class Domain::Post::SofurryPost sig { returns(T::Boolean) } def will_save_change_to_json_attributes?; end + sig { returns(T::Boolean) } + def will_save_change_to_last_gallery_log_entry_id?; end + sig { returns(T::Boolean) } def will_save_change_to_last_scan_log_entry_id?; end diff --git a/sorbet/rbi/dsl/domain/sofurry/job/static_file_job.rbi b/sorbet/rbi/dsl/domain/sofurry/job/static_file_job.rbi new file mode 100644 index 00000000..d4cde7ef --- /dev/null +++ b/sorbet/rbi/dsl/domain/sofurry/job/static_file_job.rbi @@ -0,0 +1,16 @@ +# typed: true + +# DO NOT EDIT MANUALLY +# This is an autogenerated file for dynamic methods in `Domain::Sofurry::Job::StaticFileJob`. +# Please instead update this file by running `bin/tapioca dsl Domain::Sofurry::Job::StaticFileJob`. + + +class Domain::Sofurry::Job::StaticFileJob + sig { returns(ColorLogger) } + def logger; end + + class << self + sig { returns(ColorLogger) } + def logger; end + end +end diff --git a/sorbet/rbi/dsl/domain/sofurry/job/user_avatar_job.rbi b/sorbet/rbi/dsl/domain/sofurry/job/user_avatar_job.rbi new file mode 100644 index 00000000..6fd86b04 --- /dev/null +++ b/sorbet/rbi/dsl/domain/sofurry/job/user_avatar_job.rbi @@ -0,0 +1,16 @@ +# typed: true + +# DO NOT EDIT MANUALLY +# This is an autogenerated file for dynamic methods in `Domain::Sofurry::Job::UserAvatarJob`. +# Please instead update this file by running `bin/tapioca dsl Domain::Sofurry::Job::UserAvatarJob`. + + +class Domain::Sofurry::Job::UserAvatarJob + sig { returns(ColorLogger) } + def logger; end + + class << self + sig { returns(ColorLogger) } + def logger; end + end +end diff --git a/sorbet/rbi/dsl/domain/static_file_job.rbi b/sorbet/rbi/dsl/domain/static_file_job.rbi new file mode 100644 index 00000000..ca948b68 --- /dev/null +++ b/sorbet/rbi/dsl/domain/static_file_job.rbi @@ -0,0 +1,27 @@ +# typed: true + +# DO NOT EDIT MANUALLY +# This is an autogenerated file for dynamic methods in `Domain::StaticFileJob`. +# Please instead update this file by running `bin/tapioca dsl Domain::StaticFileJob`. + + +class Domain::StaticFileJob + sig { returns(ColorLogger) } + def logger; end + + class << self + sig { returns(ColorLogger) } + def logger; end + + sig do + params( + args: T::Hash[::Symbol, T.untyped], + block: T.nilable(T.proc.params(job: Domain::StaticFileJob).void) + ).returns(T.any(Domain::StaticFileJob, FalseClass)) + end + def perform_later(args, &block); end + + sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) } + def perform_now(args); end + end +end diff --git a/sorbet/rbi/dsl/domain/user_avatar_job.rbi b/sorbet/rbi/dsl/domain/user_avatar_job.rbi new file mode 100644 index 00000000..c91a2eef --- /dev/null +++ b/sorbet/rbi/dsl/domain/user_avatar_job.rbi @@ -0,0 +1,27 @@ +# typed: true + +# DO NOT EDIT MANUALLY +# This is an autogenerated file for dynamic methods in `Domain::UserAvatarJob`. +# Please instead update this file by running `bin/tapioca dsl Domain::UserAvatarJob`. + + +class Domain::UserAvatarJob + sig { returns(ColorLogger) } + def logger; end + + class << self + sig { returns(ColorLogger) } + def logger; end + + sig do + params( + args: T::Hash[::Symbol, T.untyped], + block: T.nilable(T.proc.params(job: Domain::UserAvatarJob).void) + ).returns(T.any(Domain::UserAvatarJob, FalseClass)) + end + def perform_later(args, &block); end + + sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) } + def perform_now(args); end + end +end diff --git a/spec/jobs/domain/sofurry/job/scan_gallery_job_spec.rb b/spec/jobs/domain/sofurry/job/scan_gallery_job_spec.rb index 0716ca03..b282cc4a 100644 --- a/spec/jobs/domain/sofurry/job/scan_gallery_job_spec.rb +++ b/spec/jobs/domain/sofurry/job/scan_gallery_job_spec.rb @@ -28,6 +28,9 @@ describe Domain::Sofurry::Job::ScanGalleryJob do story_post = Domain::Post::SofurryPost.find_by(sofurry_id: 2_219_563) expect(story_post.title).to eq("Chapter 1: Beyond Reach") expect(story_post.media_type).to eq("stories") + expect(story_post.posted_at).to be_within(10.seconds).of( + Time.parse("2025-01-26 04:29:59 -0800"), + ) expect(story_post.folders.count).to eq(1) expect(story_post.tags_array).to eq( [ diff --git a/spec/lib/domain/sofurry/gallery_page_parser_spec.rb b/spec/lib/domain/sofurry/gallery_page_parser_spec.rb index db6add8e..2223b633 100644 --- a/spec/lib/domain/sofurry/gallery_page_parser_spec.rb +++ b/spec/lib/domain/sofurry/gallery_page_parser_spec.rb @@ -41,6 +41,17 @@ describe Domain::Sofurry::GalleryPageParser do expect(parser.username).to eq("Matkaja") end + it "extracts posts from art page" do + parser = + get_parser("browse_user_gallery/101763.art_page_1.html", user_id: 101_763) + expect(parser.username).to eq("gewitter") + expect(parser.posts.length).to eq(30) + expect(parser.posts.first.id).to eq(2_265_979) + expect(parser.posts.first.title).to eq("Trying Some Spanking with Clara") + expect(parser.posts.last.id).to eq(2_220_943) + expect(parser.posts.last.title).to eq("Patreon Sketch YCH 68") + end + def get_parser(file, user_id:) Domain::Sofurry::GalleryPageParser.new( File.read(Rails.root.join("test/fixtures/files/domain/sofurry/#{file}")), diff --git a/test/fixtures/files/domain/sofurry/browse_user_gallery/101763.art_page_1.html b/test/fixtures/files/domain/sofurry/browse_user_gallery/101763.art_page_1.html new file mode 100644 index 00000000..1adefae1 --- /dev/null +++ b/test/fixtures/files/domain/sofurry/browse_user_gallery/101763.art_page_1.html @@ -0,0 +1,629 @@ + + + + + + + + + + + + + + + + +SoFurry - Art User + + + + + + + + + + + + + + + + +
+
+ +
+ +
+
+ +
+
+ +
+ +
+
+
+ + +
+ + banner by Shalinka +
+
+ +
+ + +
+
+ + + + +
+
+
+
+Scraps

+ +Scraps +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ +
+
+ +
+
+
+
+ +
+ +