furaffinity 0.2.0 → 26.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 486881425319cc8b49b196f58f5e4784462d39baa62aa27e985c91a0606c0f21
4
- data.tar.gz: 81eef14d9b5b32e2874ae3d1e83e6811086cdc902b47b3e861f0e70d0a1f6810
3
+ metadata.gz: 73eb5f5ac1b82da18816b27a511134753ee22ea2387f932e929540c639ed4f79
4
+ data.tar.gz: 578e0d9a28ebb0a991107dff1966d25ca05f2abdf47e0fe119922f0ad4dd3e91
5
5
  SHA512:
6
- metadata.gz: 1d4ea145d6886a771017f00189153022947575f5184d8328ec0252748b29d17802188ad307dc9abee02806ff7af2d4bc5552cfa94f5d9e6119ca87369d7c59d6
7
- data.tar.gz: 8e91c5403692b846cf7078735af81a67f4e462d8e04e99d51d4268fa13d7778e70ecb22c2ef2690690bfc1f25c8186d27903d179ab48c29e652a60f88855caa8
6
+ metadata.gz: c7beca8cd88a0bb0da1c2d4af70434c97a1d199f99a3c9aacd82b0539cc4ff224430783ffa8eb6aa768226c943a76629cfd3b2c4eb073277915591d068d93a32
7
+ data.tar.gz: 16f425069ee8c619328dcc1ce3a433df9a3bfd3684e97d3f140215565f5807f69f928c72d3db8dd8d901f04933e4e28edaf0cb557ef60027c3731519c1dd5589
data/.rubocop.yml CHANGED
@@ -1,5 +1,12 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.6
2
+ TargetRubyVersion: 4.0
3
+ NewCops: enable
4
+
5
+ Style/TrailingCommaInArrayLiteral:
6
+ EnforcedStyleForMultiline: diff_comma
7
+
8
+ Style/TrailingCommaInHashLiteral:
9
+ EnforcedStyleForMultiline: diff_comma
3
10
 
4
11
  Style/StringLiterals:
5
12
  Enabled: true
@@ -9,5 +16,12 @@ Style/StringLiteralsInInterpolation:
9
16
  Enabled: true
10
17
  EnforcedStyle: double_quotes
11
18
 
19
+ Style/RaiseArgs:
20
+ EnforcedStyle: compact
21
+
22
+ Layout/HashAlignment:
23
+ EnforcedColonStyle: table
24
+ EnforcedHashRocketStyle: table
25
+
12
26
  Layout/LineLength:
13
27
  Max: 120
data/CHANGELOG.md CHANGED
@@ -1,12 +1,23 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [26.1.0] - 2026-01-12
4
+
5
+ ### Added
6
+ - On unexpected errors received from FurAffinity an `Furaffinity::RemoteError`
7
+ error is raised now.
8
+
9
+ ### Changed
10
+ - Ruby 4.0 required
11
+ - Removed `gender` field handling as these are just keywords now
12
+ - Version scheme is now `year.month.patch`.
13
+
3
14
  ## [0.2.0] - 2023-12-21
4
15
 
5
- ## Added
16
+ ### Added
6
17
  - `fa edit` command to edit submissions
7
18
  - `after_upload` hook to queues
8
19
 
9
- ## Changed
20
+ ### Changed
10
21
  - `folder_name` is now called `create_folder_name` as it should
11
22
 
12
23
  ## [0.1.0] - 2023-11-04
data/README.md CHANGED
@@ -77,7 +77,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
77
77
 
78
78
  ## Contributing
79
79
 
80
- Bug reports and pull requests are welcome on GitHub at https://github.com/nilsding/furaffinity. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/nilsding/furaffinity/blob/main/CODE_OF_CONDUCT.md).
80
+ Bug reports and pull requests are welcome on Codeberg at https://codeberg.org/jyrki/furaffinity-cli.com/nilsding/furaffinity. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://codeberg.org/jyrki/furaffinity-cli/src/branch/main/CODE_OF_CONDUCT.md).
81
81
 
82
82
  ## License
83
83
 
@@ -85,4 +85,4 @@ The gem is available as open source under the terms of the [AGPLv3 License](http
85
85
 
86
86
  ## Code of Conduct
87
87
 
88
- Everyone interacting in the Furaffinity project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/nilsding/furaffinity/blob/main/CODE_OF_CONDUCT.md).
88
+ Everyone interacting in the Furaffinity project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://codeberg.org/jyrki/furaffinity-cli/src/branch/main/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
+ require "minitest/test_task"
4
5
 
5
6
  task :default
7
+
8
+ Minitest::TestTask.create
data/exe/fa CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  lib_dir = File.expand_path("../lib", __dir__)
4
5
  $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
@@ -10,14 +10,14 @@ module Furaffinity
10
10
  include CliBase
11
11
 
12
12
  class_option :log_level,
13
- type: :string,
14
- desc: "Log level to use",
15
- default: "info"
13
+ type: :string,
14
+ desc: "Log level to use",
15
+ default: "info"
16
16
 
17
17
  class_option :config,
18
- type: :string,
19
- desc: "Path to the config",
20
- default: File.join(Dir.home, ".farc")
18
+ type: :string,
19
+ desc: "Path to the config",
20
+ default: File.join(Dir.home, ".farc")
21
21
 
22
22
  desc "auth A_COOKIE B_COOKIE", "Store authentication info for FurAffinity"
23
23
  def auth(a_cookie, b_cookie)
@@ -38,37 +38,37 @@ module Furaffinity
38
38
 
39
39
  desc "upload FILE_PATH", "Upload a new submission"
40
40
  option :type,
41
- type: :string,
42
- desc: "Submission type. One of: #{Furaffinity::Client::SUBMISSION_TYPES.join(", ")}",
43
- default: "submission"
41
+ type: :string,
42
+ desc: "Submission type. One of: #{Furaffinity::Client::SUBMISSION_TYPES.join(", ")}",
43
+ default: "submission"
44
44
  option :title,
45
- type: :string,
46
- desc: "Submission title.",
47
- required: true
45
+ type: :string,
46
+ desc: "Submission title.",
47
+ required: true
48
48
  option :description,
49
- type: :string,
50
- desc: "Submission description.",
51
- required: true
49
+ type: :string,
50
+ desc: "Submission description.",
51
+ required: true
52
52
  option :rating,
53
- type: :string,
54
- desc: "Submission rating. One of: #{Furaffinity::Client::RATING_MAP.keys.join(", ")}",
55
- required: true
53
+ type: :string,
54
+ desc: "Submission rating. One of: #{Furaffinity::Client::RATING_MAP.keys.join(", ")}",
55
+ required: true
56
56
  option :lock_comments,
57
- type: :boolean,
58
- desc: "Disable comments on this submission.",
59
- default: false
57
+ type: :boolean,
58
+ desc: "Disable comments on this submission.",
59
+ default: false
60
60
  option :scrap,
61
- type: :boolean,
62
- desc: "Place this upload to your scraps.",
63
- default: false
61
+ type: :boolean,
62
+ desc: "Place this upload to your scraps.",
63
+ default: false
64
64
  option :keywords,
65
- type: :string,
66
- desc: "Keywords, separated by spaces.",
67
- default: ""
65
+ type: :string,
66
+ desc: "Keywords, separated by spaces.",
67
+ default: ""
68
68
  option :create_folder_name,
69
- type: :string,
70
- desc: "Create a new folder and place this submission into it.",
71
- default: ""
69
+ type: :string,
70
+ desc: "Create a new folder and place this submission into it.",
71
+ default: ""
72
72
  def upload(file_path)
73
73
  set_log_level(options)
74
74
  config = config_for(options)
@@ -78,14 +78,14 @@ module Furaffinity
78
78
  *client
79
79
  .method(:upload)
80
80
  .parameters
81
- .select { |(type, name)| %i[keyreq key].include?(type) }
81
+ .select { |(type, _name)| %i[keyreq key].include?(type) }
82
82
  .map(&:last)
83
83
  ).transform_keys(&:to_sym)
84
84
  url = client.upload(File.new(file_path), **upload_options)
85
85
  say "Submission uploaded! #{url}", :green
86
86
  end
87
87
 
88
- EDIT_TEMPLATE = <<~YAML
88
+ EDIT_TEMPLATE = <<~YAML.freeze
89
89
  ---
90
90
  # Submission info for %<id>s
91
91
 
@@ -118,11 +118,10 @@ module Furaffinity
118
118
  # write template
119
119
  file = Tempfile.new("facli_edit_#{id}")
120
120
  file.puts format(EDIT_TEMPLATE,
121
- id:,
122
- **submission_info.slice(:title, :keywords, :scrap, :lock_comments).transform_values(&:inspect),
123
- description: submission_info.fetch(:message).gsub(/^/, " "),
124
- rating: submission_info.fetch(:rating),
125
- )
121
+ id:,
122
+ **submission_info.slice(:title, :keywords, :scrap, :lock_comments).transform_values(&:inspect),
123
+ description: submission_info.fetch(:message).gsub(/^/, " "),
124
+ rating: submission_info.fetch(:rating))
126
125
  file.close
127
126
  CliUtils.open_editor file.path, fatal: true
128
127
  params = YAML.safe_load_file(file.path, permitted_classes: [Symbol]).transform_keys(&:to_sym)
@@ -76,7 +76,7 @@ module Furaffinity
76
76
  queue.reload
77
77
 
78
78
  if queue.queue.empty?
79
- say "Nothing is in the queue yet, use `#{File.basename $0} queue add ...` to add files.", :yellow
79
+ say "Nothing is in the queue yet, use `#{File.basename $PROGRAM_NAME} queue add ...` to add files.", :yellow
80
80
 
81
81
  print_uploaded_files
82
82
  return
@@ -91,7 +91,6 @@ module Furaffinity
91
91
  print_table queue_table, borders: true
92
92
 
93
93
  print_uploaded_files
94
-
95
94
  rescue Furaffinity::Error => e
96
95
  say e.message, :red
97
96
  exit 1
@@ -125,16 +124,16 @@ module Furaffinity
125
124
 
126
125
  def print_uploaded_files
127
126
  uploaded_files = queue.uploaded_files
128
- unless uploaded_files.empty?
129
- say
130
- say "Uploaded files (will be removed when you run `#{File.basename $0} queue clean`):"
131
- uploaded_table = [["File name", "Title", "Submission URL"], :separator]
132
- uploaded_files.each do |file_name, upload_status|
133
- file_info = queue.file_info.fetch(file_name)
134
- uploaded_table << [file_name, file_info[:title], upload_status[:url]]
135
- end
136
- print_table uploaded_table, borders: true
127
+ return if uploaded_files.empty?
128
+
129
+ say
130
+ say "Uploaded files (will be removed when you run `#{File.basename $PROGRAM_NAME} queue clean`):"
131
+ uploaded_table = [["File name", "Title", "Submission URL"], :separator]
132
+ uploaded_files.each do |file_name, upload_status|
133
+ file_info = queue.file_info.fetch(file_name)
134
+ uploaded_table << [file_name, file_info[:title], upload_status[:url]]
137
135
  end
136
+ print_table uploaded_table, borders: true
138
137
  end
139
138
  end
140
139
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "English"
3
4
  require "shellwords"
4
5
 
5
6
  module Furaffinity
@@ -9,18 +10,20 @@ module Furaffinity
9
10
  include SemanticLogger::Loggable
10
11
 
11
12
  def open_editor(file, fatal: false)
12
- editor = ENV["FA_EDITOR"] || ENV["VISUAL"] || ENV["EDITOR"]
13
+ editor = ENV["FA_EDITOR"] || ENV["VISUAL"] || ENV.fetch("EDITOR", nil)
13
14
  unless editor
14
15
  logger.warn "could not open editor for #{file.inspect}, set one of FA_EDITOR, VISUAL, or EDITOR in your ENV"
15
- raise "No suitable editor found to edit #{file.inspect}, set one of FA_EDITOR, VISUAL, or EDITOR in your ENV" if fatal
16
+ if fatal
17
+ raise "No suitable editor found to edit #{file.inspect}, set one of FA_EDITOR, VISUAL, or EDITOR in your ENV"
18
+ end
16
19
 
17
20
  return
18
21
  end
19
22
 
20
23
  system(*Shellwords.shellwords(editor), file).tap do
21
- next if $?.exitstatus == 0
24
+ next if $CHILD_STATUS.exitstatus.zero?
22
25
 
23
- logger.error "could not run #{editor} #{file}, exit code: #{$?.exitstatus}"
26
+ logger.error "could not run #{editor} #{file}, exit code: #{$CHILD_STATUS.exitstatus}"
24
27
  end
25
28
  end
26
29
  end
@@ -51,16 +51,16 @@ module Furaffinity
51
51
  "journal" => :journals,
52
52
  "unread" => :notes,
53
53
  "troubleticket" => :trouble_tickets,
54
- }
54
+ }.freeze
55
55
 
56
56
  def notifications
57
57
  get("/")
58
58
  .then(&method(:parse_response))
59
59
  .css("a.notification-container")
60
- .map { _1.attr(:title) }
60
+ .map { it.attr(:title) }
61
61
  .uniq
62
62
  .each_with_object({}) do |item, h|
63
- count, type, *_rest = item.split(" ")
63
+ count, type, *_rest = item.split
64
64
  count = count.tr(",", "").strip.to_i
65
65
  h[NOTIFICATIONS_MAP.fetch(type.downcase.strip)] = count
66
66
  end
@@ -73,17 +73,9 @@ module Furaffinity
73
73
  adult: 1,
74
74
  }.freeze
75
75
 
76
- def fake_upload(file, title:, rating:, description:, keywords:, create_folder_name: "", lock_comments: false, scrap: false, type: :submission)
77
- validate_args!(type:, rating:) => { type:, rating: }
78
-
79
- raise "not a file" unless file.is_a?(File)
80
- params = { MAX_FILE_SIZE: "10485760" }
81
- raise ArgumentError.new("file size of #{file.size} is greater than FA limit of #{params[:MAX_FILE_SIZE]}") if file.size > params[:MAX_FILE_SIZE].to_i
82
- "https://www.furaffinity.net/view/54328944/?upload-successful"
83
- end
84
-
85
76
  # @param file [File]
86
- def upload(file, title:, rating:, description:, keywords:, create_folder_name: "", lock_comments: false, scrap: false, type: :submission)
77
+ def upload(file, title:, rating:, description:, keywords:, create_folder_name: "", lock_comments: false,
78
+ scrap: false, type: :submission)
87
79
  validate_args!(type:, rating:) => { type:, rating: }
88
80
 
89
81
  client = http_client
@@ -91,21 +83,24 @@ module Furaffinity
91
83
  # step 1: get the required keys
92
84
  logger.trace "Extracting keys from upload form"
93
85
  response = get("/submit/", client:).then(&method(:parse_response))
86
+ raise_if_system_message!(response)
87
+
94
88
  params = {
95
89
  submission_type: type,
96
90
  submission: file,
97
91
  thumbnail: {
98
92
  content_type: "application/octet-stream",
99
- filename: "",
100
- body: ""
93
+ filename: "",
94
+ body: "",
101
95
  },
102
96
  }
103
97
  params.merge!(
104
98
  %w[MAX_FILE_SIZE key]
105
- .map { [_1.to_sym, response.css("form#myform input[name=#{_1}]").first.attr(:value)] }
106
- .to_h
99
+ .to_h { [it.to_sym, response.css("form#myform input[name=#{it}]").first.attr(:value)] }
107
100
  )
108
- raise ArgumentError.new("file size of #{file.size} is greater than FA limit of #{params[:MAX_FILE_SIZE]}") if file.size > params[:MAX_FILE_SIZE].to_i
101
+ if file.size > params[:MAX_FILE_SIZE].to_i
102
+ raise ArgumentError.new("file size of #{file.size} is greater than FA limit of #{params[:MAX_FILE_SIZE]}")
103
+ end
109
104
 
110
105
  # step 2: upload the submission file
111
106
  logger.debug "Uploading submission..."
@@ -113,7 +108,10 @@ module Furaffinity
113
108
  # for some reason HTTPX performs a GET redirect with the params so we
114
109
  # can't use its plugin here
115
110
  # --> follow the redirect ourselves
116
- raise Error.new("expected a 302 response, got #{upload_response.status}") unless upload_response.status == 302
111
+ unless upload_response.status == 302
112
+ raise_if_system_message!(upload_response.then(&method(:parse_response)))
113
+ raise Error.new("expected a 302 response, got #{upload_response.status} without a system message.")
114
+ end
117
115
 
118
116
  redirect_location = upload_response.headers[:location]
119
117
  unless redirect_location == "/submit/finalize/"
@@ -122,41 +120,39 @@ module Furaffinity
122
120
  response = get(redirect_location, client:).then(&method(:parse_response))
123
121
 
124
122
  params = {
125
- key: response.css("form#myform input[name=key]").first.attr(:value),
123
+ key: response.css("form#myform input[name=key]").first.attr(:value),
126
124
 
127
125
  # category, "1" is "Visual Art -> All"
128
- cat: "1",
126
+ cat: "1",
129
127
  # theme, "1" is "General Things -> All"
130
- atype: "1",
128
+ atype: "1",
131
129
  # species, "1" is "Unspecified / Any"
132
- species: "1",
133
- # gender, "0" is "Any"
134
- gender: "0",
130
+ species: "1",
135
131
 
136
- rating: RATING_MAP.fetch(rating),
137
- title: title,
138
- message: description,
139
- keywords: keywords,
132
+ rating: RATING_MAP.fetch(rating),
133
+ title: title,
134
+ message: description,
135
+ keywords: keywords,
140
136
 
141
137
  create_folder_name:,
142
138
 
143
139
  # finalize button :)
144
- finalize: "Finalize ",
140
+ finalize: "Finalize ",
145
141
  }
146
142
  params[:lock_comments] = "1" if lock_comments
147
143
  params[:scrap] = "1" if scrap
148
144
 
149
145
  logger.debug "Finalising submission..."
150
146
  finalize_response = post("/submit/finalize/", form: params, client:)
151
- if finalize_response.status == 302
152
- redirect_location = finalize_response.headers[:location]
153
- url = File.join(BASE_URL, redirect_location)
154
- logger.info "Uploaded! #{url}"
155
- return url
156
- else
157
- fa_error = parse_response(finalize_response).css(".redirect-message").text
158
- raise Error.new("FA returned: #{fa_error}")
147
+ unless finalize_response.status == 302
148
+ raise_if_system_message!(finalize_response.then(&method(:parse_response)))
149
+ return
159
150
  end
151
+
152
+ redirect_location = finalize_response.headers[:location]
153
+ url = File.join(BASE_URL, redirect_location)
154
+ logger.info "Uploaded! #{url}"
155
+ url
160
156
  end
161
157
 
162
158
  # Returns submission information from your own gallery.
@@ -165,6 +161,7 @@ module Furaffinity
165
161
 
166
162
  logger.trace { "Retrieving submission information for #{id}" }
167
163
  response = get("/controls/submissions/changeinfo/#{id}/", client:).then(&method(:parse_response))
164
+ raise_if_system_message!(response)
168
165
 
169
166
  {}.tap do |h|
170
167
  h[:title] = response.css("form[name=MsgForm] input[name=title]").first.attr(:value)
@@ -173,26 +170,23 @@ module Furaffinity
173
170
  h[field] = response.css("form[name=MsgForm] textarea[name=#{field}]").first.text
174
171
  end
175
172
 
176
- h[:rating] = RATING_MAP.invert.fetch(response.css("form[name=MsgForm] input[name=rating][checked]").first.attr(:value).to_i)
173
+ h[:rating] =
174
+ RATING_MAP.invert.fetch(response.css("form[name=MsgForm] input[name=rating][checked]").first.attr(:value).to_i)
177
175
 
178
176
  %i[lock_comments scrap].each do |field|
179
177
  h[field] = !!response.css("form[name=MsgForm] input[name=#{field}][checked]").first&.attr(:value)
180
178
  end
181
179
 
182
- %i[cat atype species gender].each do |field|
180
+ %i[cat atype species].each do |field|
183
181
  h[field] = response.css("form[name=MsgForm] select[name=#{field}] option[selected]").first.attr(:value)
184
182
  end
185
183
 
186
- h[:folder_ids] = response.css("form[name=MsgForm] input[name=\"folder_ids[]\"][checked]").map { _1.attr(:value) }
184
+ h[:folder_ids] = response.css("form[name=MsgForm] input[name=\"folder_ids[]\"][checked]").map do
185
+ it.attr(:value)
186
+ end
187
187
  end
188
188
  end
189
189
 
190
- def fake_update(id:, title:, rating:, description:, keywords:, lock_comments: false, scrap: false)
191
- validate_args!(rating:) => { rating: }
192
-
193
- "https://www.furaffinity.net/view/54328944/"
194
- end
195
-
196
190
  # NOTE: only tested with `type=submission`
197
191
  def update(id:, title:, rating:, description:, keywords:, lock_comments: false, scrap: false)
198
192
  validate_args!(rating:) => { rating: }
@@ -202,17 +196,19 @@ module Furaffinity
202
196
  # step 1: get the required keys
203
197
  logger.trace { "Extracting keys from submission #{id}" }
204
198
  response = get("/controls/submissions/changeinfo/#{id}/", client:).then(&method(:parse_response))
199
+ raise_if_system_message!(response)
200
+
205
201
  already_set_params = {
206
202
  key: response.css("form[name=MsgForm] input[name=key]").first.attr(:value),
207
- folder_ids: response.css("form[name=MsgForm] input[name=\"folder_ids[]\"][checked]").map { _1.attr(:value) },
203
+ folder_ids: response.css("form[name=MsgForm] input[name=\"folder_ids[]\"][checked]").map { it.attr(:value) },
208
204
  }.tap do |h|
209
- %i[cat atype species gender].each do |field|
205
+ %i[cat atype species].each do |field|
210
206
  h[field] = response.css("form[name=MsgForm] select[name=#{field}] option[selected]").first.attr(:value)
211
207
  end
212
208
  end
213
209
 
214
210
  params = {
215
- update: "yes",
211
+ update: "yes",
216
212
 
217
213
  rating: RATING_MAP.fetch(rating),
218
214
  title: title,
@@ -222,41 +218,58 @@ module Furaffinity
222
218
  **already_set_params,
223
219
 
224
220
  # update button ;-)
225
- submit: "Update",
221
+ submit: "Update",
226
222
  }
227
223
  params[:lock_comments] = "1" if lock_comments
228
224
  params[:scrap] = "1" if scrap
229
225
 
230
226
  logger.debug { "Updating submission #{id}..." }
231
227
  update_response = post("/controls/submissions/changeinfo/#{id}/", form: params, client:)
232
- if update_response.status == 302
233
- redirect_location = update_response.headers[:location]
234
- url = File.join(BASE_URL, redirect_location)
235
- logger.info "Updated! #{url}"
236
- return url
237
- else
238
- fa_error = parse_response(update_response).css(".redirect-message").text
239
- raise Error.new("FA returned: #{fa_error}")
228
+ unless update_response.status == 302
229
+ raise_if_system_message!(update_response.then(&method(:parse_response)))
230
+ return
240
231
  end
232
+
233
+ redirect_location = update_response.headers[:location]
234
+ url = File.join(BASE_URL, redirect_location)
235
+ logger.info "Updated! #{url}"
236
+ url
241
237
  end
242
238
 
243
239
  private
244
240
 
245
- def validate_args!(type: nil, rating:)
241
+ def validate_args!(rating:, type: nil)
246
242
  if type
247
243
  type = type.to_sym
248
- raise ArgumentError.new("#{type.inspect} is not in #{SUBMISSION_TYPES.inspect}") unless SUBMISSION_TYPES.include?(type)
244
+ unless SUBMISSION_TYPES.include?(type)
245
+ raise ArgumentError.new("#{type.inspect} is not in #{SUBMISSION_TYPES.inspect}")
246
+ end
249
247
  end
250
248
  rating = rating.to_sym
251
- raise ArgumentError.new("#{rating.inspect} is not in #{RATING_MAP.keys.inspect}") unless RATING_MAP.include?(rating)
249
+ unless RATING_MAP.include?(rating)
250
+ raise ArgumentError.new("#{rating.inspect} is not in #{RATING_MAP.keys.inspect}")
251
+ end
252
252
 
253
253
  { type:, rating: }
254
254
  end
255
255
 
256
+ # @return [Nokogiri::HTML::Document]
256
257
  def parse_response(httpx_response)
257
258
  logger.measure_trace "Parsing response" do
258
259
  Nokogiri::HTML.parse(httpx_response)
259
260
  end
260
261
  end
262
+
263
+ # @param response [Nokogiri::HTML::Document]
264
+ # @return String
265
+ def system_message(response) = response.css(".redirect-message")&.text
266
+
267
+ # @param response [Nokogiri::HTML::Document]
268
+ def raise_if_system_message!(response)
269
+ fa_error = system_message(response)
270
+ return if fa_error&.empty?
271
+
272
+ raise RemoteError.new(fa_error)
273
+ end
261
274
  end
262
275
  end
@@ -15,7 +15,7 @@ module Furaffinity
15
15
  else
16
16
  @config_hash = {}
17
17
  end
18
- rescue => e
18
+ rescue StandardError => e
19
19
  logger.fatal("Error while loading configuration:", e)
20
20
  raise
21
21
  end
@@ -36,8 +36,8 @@ module Furaffinity
36
36
  end
37
37
  end
38
38
 
39
- def set!(**kwargs)
40
- set(**kwargs)
39
+ def set!(**)
40
+ set(**)
41
41
  save
42
42
  end
43
43
 
@@ -11,7 +11,7 @@ module Furaffinity
11
11
 
12
12
  SUBMISSION_INFO_EXT = ".info.yml"
13
13
 
14
- SUBMISSION_TEMPLATE = <<~YAML
14
+ SUBMISSION_TEMPLATE = <<~YAML.freeze
15
15
  ---
16
16
  # Submission info for %<file_name>s
17
17
 
@@ -100,9 +100,10 @@ module Furaffinity
100
100
 
101
101
  @queue = YAML.safe_load_file(fa_info_path("queue.yml"), permitted_classes: [Symbol])
102
102
  @upload_status = YAML.safe_load_file(fa_info_path("status.yml"), permitted_classes: [Symbol])
103
- @file_info = Dir[File.join(queue_dir, "**/*#{SUBMISSION_INFO_EXT}")].map do |path|
104
- [path.delete_suffix(SUBMISSION_INFO_EXT).sub(/^#{Regexp.escape(queue_dir)}\/?/, ""), YAML.safe_load_file(path, permitted_classes: [Symbol]).transform_keys(&:to_sym)]
105
- end.to_h
103
+ @file_info = Dir[File.join(queue_dir, "**/*#{SUBMISSION_INFO_EXT}")].to_h do |path|
104
+ [path.delete_suffix(SUBMISSION_INFO_EXT).sub(%r{^#{Regexp.escape(queue_dir)}/?}, ""),
105
+ YAML.safe_load_file(path, permitted_classes: [Symbol]).transform_keys(&:to_sym)]
106
+ end
106
107
 
107
108
  logger.trace "Loaded state info", queue:, file_info:
108
109
  end
@@ -177,7 +178,7 @@ module Furaffinity
177
178
  end
178
179
 
179
180
  def clean
180
- uploaded_files.each do |file, _upload_info|
181
+ uploaded_files.each_key do |file|
181
182
  logger.trace { "Deleting #{file} ..." }
182
183
  queue.delete(file)
183
184
  upload_status.delete(file)
@@ -196,7 +197,7 @@ module Furaffinity
196
197
 
197
198
  hook_handler = QueueHook.new(client, file_info)
198
199
 
199
- while file_name = queue.shift
200
+ while (file_name = queue.shift)
200
201
  info = file_info[file_name]
201
202
  unless info
202
203
  logger.warn "no file info found for #{file_name}, ignoring"
@@ -23,7 +23,9 @@ module Furaffinity
23
23
  submission_url(id)
24
24
  else
25
25
  if url_or_submission.is_a?(Hash)
26
- logger.warn { "passed hash does not have an ID, probably not uploaded yet? hash keys: #{url_or_submission.keys.inspect}" }
26
+ logger.warn do
27
+ "passed hash does not have an ID, probably not uploaded yet? hash keys: #{url_or_submission.keys.inspect}"
28
+ end
27
29
  end
28
30
  url_or_submission.to_s
29
31
  end
@@ -36,12 +38,12 @@ module Furaffinity
36
38
 
37
39
  def initialize(client, file_info)
38
40
  @client = client
39
- @file_info = file_info.each_with_object({}) do |(file_name, info), h|
41
+ @file_info = file_info.transform_values do |info|
40
42
  # Hash#except duplicates the hash, which is good here as we don't want
41
43
  # to modify the queue.
42
44
  # exclude after_upload as it's not needed, and create_folder_name and
43
45
  # type is only relevant when initially uploading the submission.
44
- h[file_name] = info.except(:create_folder_name, :after_upload, :type)
46
+ info.except(:create_folder_name, :after_upload, :type)
45
47
  end
46
48
  end
47
49
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Furaffinity
4
- VERSION = "0.2.0"
4
+ VERSION = "26.1.0"
5
5
  end
data/lib/furaffinity.rb CHANGED
@@ -7,4 +7,7 @@ loader.setup
7
7
 
8
8
  module Furaffinity
9
9
  class Error < StandardError; end
10
+
11
+ # Raised when FurAffinity returned a system message where we did not expect one.
12
+ class RemoteError < Error; end
10
13
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: furaffinity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 26.1.0
5
5
  platform: ruby
6
6
  authors:
7
- - Georg Gadinger
8
- autorequire:
7
+ - Jyrki Gadinger
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2023-12-21 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: httpx
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '1.1'
18
+ version: '1.7'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '1.1'
25
+ version: '1.7'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: json
29
28
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +37,20 @@ dependencies:
38
37
  - - ">="
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: logger
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.6'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.6'
41
54
  - !ruby/object:Gem::Dependency
42
55
  name: nokogiri
43
56
  requirement: !ruby/object:Gem::Requirement
@@ -119,13 +132,13 @@ files:
119
132
  - lib/furaffinity/queue.rb
120
133
  - lib/furaffinity/queue_hook.rb
121
134
  - lib/furaffinity/version.rb
122
- homepage: https://github.com/nilsding/furaffinity-cli
135
+ homepage: https://codeberg.org/jyrki/furaffinity-cli
123
136
  licenses:
124
137
  - AGPLv3
125
138
  metadata:
126
- homepage_uri: https://github.com/nilsding/furaffinity-cli
127
- source_code_uri: https://github.com/nilsding/furaffinity-cli
128
- post_install_message:
139
+ homepage_uri: https://codeberg.org/jyrki/furaffinity-cli
140
+ source_code_uri: https://codeberg.org/jyrki/furaffinity-cli
141
+ rubygems_mfa_required: 'true'
129
142
  rdoc_options: []
130
143
  require_paths:
131
144
  - lib
@@ -133,15 +146,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
133
146
  requirements:
134
147
  - - ">="
135
148
  - !ruby/object:Gem::Version
136
- version: 3.2.0
149
+ version: 4.0.0
137
150
  required_rubygems_version: !ruby/object:Gem::Requirement
138
151
  requirements:
139
152
  - - ">="
140
153
  - !ruby/object:Gem::Version
141
154
  version: '0'
142
155
  requirements: []
143
- rubygems_version: 3.4.10
144
- signing_key:
156
+ rubygems_version: 4.0.3
145
157
  specification_version: 4
146
158
  summary: FurAffinity CLI tool
147
159
  test_files: []