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 +4 -4
- data/.rubocop.yml +15 -1
- data/CHANGELOG.md +13 -2
- data/README.md +2 -2
- data/Rakefile +3 -0
- data/exe/fa +1 -0
- data/lib/furaffinity/cli.rb +36 -37
- data/lib/furaffinity/cli_queue.rb +10 -11
- data/lib/furaffinity/cli_utils.rb +7 -4
- data/lib/furaffinity/client.rb +75 -62
- data/lib/furaffinity/config.rb +3 -3
- data/lib/furaffinity/queue.rb +7 -6
- data/lib/furaffinity/queue_hook.rb +5 -3
- data/lib/furaffinity/version.rb +1 -1
- data/lib/furaffinity.rb +3 -0
- metadata +25 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 73eb5f5ac1b82da18816b27a511134753ee22ea2387f932e929540c639ed4f79
|
|
4
|
+
data.tar.gz: 578e0d9a28ebb0a991107dff1966d25ca05f2abdf47e0fe119922f0ad4dd3e91
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c7beca8cd88a0bb0da1c2d4af70434c97a1d199f99a3c9aacd82b0539cc4ff224430783ffa8eb6aa768226c943a76629cfd3b2c4eb073277915591d068d93a32
|
|
7
|
+
data.tar.gz: 16f425069ee8c619328dcc1ce3a433df9a3bfd3684e97d3f140215565f5807f69f928c72d3db8dd8d901f04933e4e28edaf0cb557ef60027c3731519c1dd5589
|
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
-
TargetRubyVersion:
|
|
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
|
-
|
|
16
|
+
### Added
|
|
6
17
|
- `fa edit` command to edit submissions
|
|
7
18
|
- `after_upload` hook to queues
|
|
8
19
|
|
|
9
|
-
|
|
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
|
|
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://
|
|
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
data/exe/fa
CHANGED
data/lib/furaffinity/cli.rb
CHANGED
|
@@ -10,14 +10,14 @@ module Furaffinity
|
|
|
10
10
|
include CliBase
|
|
11
11
|
|
|
12
12
|
class_option :log_level,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
type: :string,
|
|
14
|
+
desc: "Log level to use",
|
|
15
|
+
default: "info"
|
|
16
16
|
|
|
17
17
|
class_option :config,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
type: :string,
|
|
42
|
+
desc: "Submission type. One of: #{Furaffinity::Client::SUBMISSION_TYPES.join(", ")}",
|
|
43
|
+
default: "submission"
|
|
44
44
|
option :title,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
type: :string,
|
|
46
|
+
desc: "Submission title.",
|
|
47
|
+
required: true
|
|
48
48
|
option :description,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
type: :string,
|
|
50
|
+
desc: "Submission description.",
|
|
51
|
+
required: true
|
|
52
52
|
option :rating,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
type: :string,
|
|
54
|
+
desc: "Submission rating. One of: #{Furaffinity::Client::RATING_MAP.keys.join(", ")}",
|
|
55
|
+
required: true
|
|
56
56
|
option :lock_comments,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
type: :boolean,
|
|
58
|
+
desc: "Disable comments on this submission.",
|
|
59
|
+
default: false
|
|
60
60
|
option :scrap,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
type: :boolean,
|
|
62
|
+
desc: "Place this upload to your scraps.",
|
|
63
|
+
default: false
|
|
64
64
|
option :keywords,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
type: :string,
|
|
66
|
+
desc: "Keywords, separated by spaces.",
|
|
67
|
+
default: ""
|
|
68
68
|
option :create_folder_name,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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,
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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 $
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
24
|
+
next if $CHILD_STATUS.exitstatus.zero?
|
|
22
25
|
|
|
23
|
-
logger.error "could not run #{editor} #{file}, exit code: #{
|
|
26
|
+
logger.error "could not run #{editor} #{file}, exit code: #{$CHILD_STATUS.exitstatus}"
|
|
24
27
|
end
|
|
25
28
|
end
|
|
26
29
|
end
|
data/lib/furaffinity/client.rb
CHANGED
|
@@ -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 {
|
|
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,
|
|
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
|
-
.
|
|
106
|
-
.to_h
|
|
99
|
+
.to_h { [it.to_sym, response.css("form#myform input[name=#{it}]").first.attr(:value)] }
|
|
107
100
|
)
|
|
108
|
-
|
|
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
|
-
|
|
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:
|
|
123
|
+
key: response.css("form#myform input[name=key]").first.attr(:value),
|
|
126
124
|
|
|
127
125
|
# category, "1" is "Visual Art -> All"
|
|
128
|
-
cat:
|
|
126
|
+
cat: "1",
|
|
129
127
|
# theme, "1" is "General Things -> All"
|
|
130
|
-
atype:
|
|
128
|
+
atype: "1",
|
|
131
129
|
# species, "1" is "Unspecified / Any"
|
|
132
|
-
species:
|
|
133
|
-
# gender, "0" is "Any"
|
|
134
|
-
gender: "0",
|
|
130
|
+
species: "1",
|
|
135
131
|
|
|
136
|
-
rating:
|
|
137
|
-
title:
|
|
138
|
-
message:
|
|
139
|
-
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:
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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] =
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
241
|
+
def validate_args!(rating:, type: nil)
|
|
246
242
|
if type
|
|
247
243
|
type = type.to_sym
|
|
248
|
-
|
|
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
|
-
|
|
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
|
data/lib/furaffinity/config.rb
CHANGED
|
@@ -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!(**
|
|
40
|
-
set(**
|
|
39
|
+
def set!(**)
|
|
40
|
+
set(**)
|
|
41
41
|
save
|
|
42
42
|
end
|
|
43
43
|
|
data/lib/furaffinity/queue.rb
CHANGED
|
@@ -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}")].
|
|
104
|
-
[path.delete_suffix(SUBMISSION_INFO_EXT).sub(
|
|
105
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
46
|
+
info.except(:create_folder_name, :after_upload, :type)
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
|
data/lib/furaffinity/version.rb
CHANGED
data/lib/furaffinity.rb
CHANGED
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:
|
|
4
|
+
version: 26.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
8
|
-
autorequire:
|
|
7
|
+
- Jyrki Gadinger
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
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.
|
|
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.
|
|
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://
|
|
135
|
+
homepage: https://codeberg.org/jyrki/furaffinity-cli
|
|
123
136
|
licenses:
|
|
124
137
|
- AGPLv3
|
|
125
138
|
metadata:
|
|
126
|
-
homepage_uri: https://
|
|
127
|
-
source_code_uri: https://
|
|
128
|
-
|
|
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:
|
|
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:
|
|
144
|
-
signing_key:
|
|
156
|
+
rubygems_version: 4.0.3
|
|
145
157
|
specification_version: 4
|
|
146
158
|
summary: FurAffinity CLI tool
|
|
147
159
|
test_files: []
|