# typed: strict class Domain::Sofurry::GalleryPageParser extend T::Sig class Folder < T::Struct include T::Struct::ActsAsComparable const :name, String const :sofurry_id, Integer end class MediaType < T::Enum enums do Stories = new("stories") Art = new("art") Music = new("music") Journals = new("journals") Photos = new("photos") end end class ShortGalleryEntry < T::Struct include T::Struct::ActsAsComparable const :sofurry_id, Integer const :title, String end sig { params(page: String, user_id: Integer).void } def initialize(page, user_id:) @doc = T.let(Nokogiri.HTML(page), Nokogiri::HTML::Document) @page = T.let(page, String) @folders = T.let(nil, T.nilable(T::Array[String])) @user_id = T.let(user_id, Integer) @posts = T.let(nil, T.nilable(T::Array[ShortGalleryEntry])) end sig { returns(T::Array[Folder]) } def folders @folders ||= @doc .css(".sfBrowseListFolders .items .sfArtworkSmallWrapper strong a") .map do |elem| href = elem.attr("href") href_params = Addressable::URI.parse(href).query_values user_id = href_params["by"] if user_id&.to_i != @user_id raise "user_id mismatch: #{user_id} != #{@user_id}" end name = elem.text sofurry_id = href_params["folder"]&.to_i Folder.new(name:, sofurry_id:) end end sig { returns(T::Array[ShortGalleryEntry]) } 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").first || next sofurry_id = title_id&.attr("href").split("/")&.last&.to_i || next title = title_id&.text || next ShortGalleryEntry.new(sofurry_id:, title:) end .compact, @doc .css("a.sfArtworkSmallInner") .map do |elem| sofurry_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(sofurry_id:, title:) end .compact, ].flatten end end sig { returns(T.nilable(String)) } def username @doc.css(".user-info .user-text").first&.text&.strip&.chomp end end