furaffinity 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +4 -0
- data/lib/furaffinity/cli.rb +58 -2
- data/lib/furaffinity/cli_utils.rb +27 -0
- data/lib/furaffinity/client.rb +99 -12
- data/lib/furaffinity/queue.rb +51 -19
- data/lib/furaffinity/queue_hook.rb +65 -0
- data/lib/furaffinity/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 486881425319cc8b49b196f58f5e4784462d39baa62aa27e985c91a0606c0f21
|
4
|
+
data.tar.gz: 81eef14d9b5b32e2874ae3d1e83e6811086cdc902b47b3e861f0e70d0a1f6810
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d4ea145d6886a771017f00189153022947575f5184d8328ec0252748b29d17802188ad307dc9abee02806ff7af2d4bc5552cfa94f5d9e6119ca87369d7c59d6
|
7
|
+
data.tar.gz: 8e91c5403692b846cf7078735af81a67f4e462d8e04e99d51d4268fa13d7778e70ecb22c2ef2690690bfc1f25c8186d27903d179ab48c29e652a60f88855caa8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.2.0] - 2023-12-21
|
4
|
+
|
5
|
+
## Added
|
6
|
+
- `fa edit` command to edit submissions
|
7
|
+
- `after_upload` hook to queues
|
8
|
+
|
9
|
+
## Changed
|
10
|
+
- `folder_name` is now called `create_folder_name` as it should
|
11
|
+
|
3
12
|
## [0.1.0] - 2023-11-04
|
4
13
|
|
5
14
|
- Initial release
|
data/README.md
CHANGED
@@ -30,6 +30,10 @@ fa notifications
|
|
30
30
|
|
31
31
|
# upload a new submission
|
32
32
|
fa upload my_image.png --title "test post please ignore" --description "This is an image as you can see" --rating general --scrap
|
33
|
+
|
34
|
+
# interactively update a submission, this needs your preferred editor in ENV
|
35
|
+
export EDITOR=vi
|
36
|
+
fa edit 54328944
|
33
37
|
```
|
34
38
|
|
35
39
|
There is also a way to upload submissions in bulk: `fa queue`
|
data/lib/furaffinity/cli.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "json"
|
4
|
+
require "tempfile"
|
4
5
|
require "thor"
|
6
|
+
require "yaml"
|
5
7
|
|
6
8
|
module Furaffinity
|
7
9
|
class Cli < Thor
|
@@ -63,9 +65,9 @@ module Furaffinity
|
|
63
65
|
type: :string,
|
64
66
|
desc: "Keywords, separated by spaces.",
|
65
67
|
default: ""
|
66
|
-
option :
|
68
|
+
option :create_folder_name,
|
67
69
|
type: :string,
|
68
|
-
desc: "
|
70
|
+
desc: "Create a new folder and place this submission into it.",
|
69
71
|
default: ""
|
70
72
|
def upload(file_path)
|
71
73
|
set_log_level(options)
|
@@ -83,6 +85,55 @@ module Furaffinity
|
|
83
85
|
say "Submission uploaded! #{url}", :green
|
84
86
|
end
|
85
87
|
|
88
|
+
EDIT_TEMPLATE = <<~YAML
|
89
|
+
---
|
90
|
+
# Submission info for %<id>s
|
91
|
+
|
92
|
+
# Required field:
|
93
|
+
title: %<title>s
|
94
|
+
|
95
|
+
# Required field
|
96
|
+
description: |-
|
97
|
+
%<description>s
|
98
|
+
|
99
|
+
# Optional field, keywords separated by spaces:
|
100
|
+
keywords: %<keywords>s
|
101
|
+
|
102
|
+
# Required field, one of: [#{Furaffinity::Client::RATING_MAP.keys.join(", ")}]
|
103
|
+
rating: %<rating>s
|
104
|
+
|
105
|
+
scrap: %<scrap>s
|
106
|
+
lock_comments: %<lock_comments>s
|
107
|
+
|
108
|
+
# vim: syntax=yaml
|
109
|
+
YAML
|
110
|
+
|
111
|
+
desc "edit ID", "Edit submission info"
|
112
|
+
def edit_interactive(id)
|
113
|
+
set_log_level(options)
|
114
|
+
config = config_for(options)
|
115
|
+
client = config.new_client
|
116
|
+
|
117
|
+
submission_info = client.submission_info(id)
|
118
|
+
# write template
|
119
|
+
file = Tempfile.new("facli_edit_#{id}")
|
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
|
+
)
|
126
|
+
file.close
|
127
|
+
CliUtils.open_editor file.path, fatal: true
|
128
|
+
params = YAML.safe_load_file(file.path, permitted_classes: [Symbol]).transform_keys(&:to_sym)
|
129
|
+
|
130
|
+
url = client.update(id:, **params)
|
131
|
+
say "Submission updated! #{url}", :green
|
132
|
+
ensure
|
133
|
+
file.close
|
134
|
+
file.unlink
|
135
|
+
end
|
136
|
+
|
86
137
|
desc "queue SUBCOMMAND ...ARGS", "Manage an upload queue"
|
87
138
|
long_desc <<~LONG_DESC, wrap: false
|
88
139
|
`#{basename} queue` manages an upload queue.
|
@@ -122,5 +173,10 @@ module Furaffinity
|
|
122
173
|
#{basename} queue clean
|
123
174
|
LONG_DESC
|
124
175
|
subcommand "queue", CliQueue
|
176
|
+
|
177
|
+
map %w[--version -v] => :__print_version
|
178
|
+
|
179
|
+
desc "--version, -v", "Print the version"
|
180
|
+
def __print_version = puts "furaffinity-cli/#{VERSION}"
|
125
181
|
end
|
126
182
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shellwords"
|
4
|
+
|
5
|
+
module Furaffinity
|
6
|
+
module CliUtils
|
7
|
+
module_function
|
8
|
+
|
9
|
+
include SemanticLogger::Loggable
|
10
|
+
|
11
|
+
def open_editor(file, fatal: false)
|
12
|
+
editor = ENV["FA_EDITOR"] || ENV["VISUAL"] || ENV["EDITOR"]
|
13
|
+
unless editor
|
14
|
+
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
|
+
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
system(*Shellwords.shellwords(editor), file).tap do
|
21
|
+
next if $?.exitstatus == 0
|
22
|
+
|
23
|
+
logger.error "could not run #{editor} #{file}, exit code: #{$?.exitstatus}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/furaffinity/client.rb
CHANGED
@@ -73,23 +73,18 @@ module Furaffinity
|
|
73
73
|
adult: 1,
|
74
74
|
}.freeze
|
75
75
|
|
76
|
-
def fake_upload(file, title:, rating:, description:, keywords:,
|
77
|
-
type
|
78
|
-
|
79
|
-
rating = rating.to_sym
|
80
|
-
raise ArgumentError.new("#{rating.inspect} is not in #{RATING_MAP.keys.inspect}") unless RATING_MAP.include?(rating)
|
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
|
+
|
81
79
|
raise "not a file" unless file.is_a?(File)
|
82
80
|
params = { MAX_FILE_SIZE: "10485760" }
|
83
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
|
84
|
-
"https
|
82
|
+
"https://www.furaffinity.net/view/54328944/?upload-successful"
|
85
83
|
end
|
86
84
|
|
87
85
|
# @param file [File]
|
88
|
-
def upload(file, title:, rating:, description:, keywords:,
|
89
|
-
type
|
90
|
-
raise ArgumentError.new("#{type.inspect} is not in #{SUBMISSION_TYPES.inspect}") unless SUBMISSION_TYPES.include?(type)
|
91
|
-
rating = rating.to_sym
|
92
|
-
raise ArgumentError.new("#{rating.inspect} is not in #{RATING_MAP.keys.inspect}") unless RATING_MAP.include?(rating)
|
86
|
+
def upload(file, title:, rating:, description:, keywords:, create_folder_name: "", lock_comments: false, scrap: false, type: :submission)
|
87
|
+
validate_args!(type:, rating:) => { type:, rating: }
|
93
88
|
|
94
89
|
client = http_client
|
95
90
|
|
@@ -143,7 +138,7 @@ module Furaffinity
|
|
143
138
|
message: description,
|
144
139
|
keywords: keywords,
|
145
140
|
|
146
|
-
create_folder_name
|
141
|
+
create_folder_name:,
|
147
142
|
|
148
143
|
# finalize button :)
|
149
144
|
finalize: "Finalize ",
|
@@ -164,8 +159,100 @@ module Furaffinity
|
|
164
159
|
end
|
165
160
|
end
|
166
161
|
|
162
|
+
# Returns submission information from your own gallery.
|
163
|
+
def submission_info(id)
|
164
|
+
client = http_client
|
165
|
+
|
166
|
+
logger.trace { "Retrieving submission information for #{id}" }
|
167
|
+
response = get("/controls/submissions/changeinfo/#{id}/", client:).then(&method(:parse_response))
|
168
|
+
|
169
|
+
{}.tap do |h|
|
170
|
+
h[:title] = response.css("form[name=MsgForm] input[name=title]").first.attr(:value)
|
171
|
+
|
172
|
+
%i[message keywords].each do |field|
|
173
|
+
h[field] = response.css("form[name=MsgForm] textarea[name=#{field}]").first.text
|
174
|
+
end
|
175
|
+
|
176
|
+
h[:rating] = RATING_MAP.invert.fetch(response.css("form[name=MsgForm] input[name=rating][checked]").first.attr(:value).to_i)
|
177
|
+
|
178
|
+
%i[lock_comments scrap].each do |field|
|
179
|
+
h[field] = !!response.css("form[name=MsgForm] input[name=#{field}][checked]").first&.attr(:value)
|
180
|
+
end
|
181
|
+
|
182
|
+
%i[cat atype species gender].each do |field|
|
183
|
+
h[field] = response.css("form[name=MsgForm] select[name=#{field}] option[selected]").first.attr(:value)
|
184
|
+
end
|
185
|
+
|
186
|
+
h[:folder_ids] = response.css("form[name=MsgForm] input[name=\"folder_ids[]\"][checked]").map { _1.attr(:value) }
|
187
|
+
end
|
188
|
+
end
|
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
|
+
# NOTE: only tested with `type=submission`
|
197
|
+
def update(id:, title:, rating:, description:, keywords:, lock_comments: false, scrap: false)
|
198
|
+
validate_args!(rating:) => { rating: }
|
199
|
+
|
200
|
+
client = http_client
|
201
|
+
|
202
|
+
# step 1: get the required keys
|
203
|
+
logger.trace { "Extracting keys from submission #{id}" }
|
204
|
+
response = get("/controls/submissions/changeinfo/#{id}/", client:).then(&method(:parse_response))
|
205
|
+
already_set_params = {
|
206
|
+
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) },
|
208
|
+
}.tap do |h|
|
209
|
+
%i[cat atype species gender].each do |field|
|
210
|
+
h[field] = response.css("form[name=MsgForm] select[name=#{field}] option[selected]").first.attr(:value)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
params = {
|
215
|
+
update: "yes",
|
216
|
+
|
217
|
+
rating: RATING_MAP.fetch(rating),
|
218
|
+
title: title,
|
219
|
+
message: description,
|
220
|
+
keywords: keywords,
|
221
|
+
|
222
|
+
**already_set_params,
|
223
|
+
|
224
|
+
# update button ;-)
|
225
|
+
submit: "Update",
|
226
|
+
}
|
227
|
+
params[:lock_comments] = "1" if lock_comments
|
228
|
+
params[:scrap] = "1" if scrap
|
229
|
+
|
230
|
+
logger.debug { "Updating submission #{id}..." }
|
231
|
+
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}")
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
167
243
|
private
|
168
244
|
|
245
|
+
def validate_args!(type: nil, rating:)
|
246
|
+
if type
|
247
|
+
type = type.to_sym
|
248
|
+
raise ArgumentError.new("#{type.inspect} is not in #{SUBMISSION_TYPES.inspect}") unless SUBMISSION_TYPES.include?(type)
|
249
|
+
end
|
250
|
+
rating = rating.to_sym
|
251
|
+
raise ArgumentError.new("#{rating.inspect} is not in #{RATING_MAP.keys.inspect}") unless RATING_MAP.include?(rating)
|
252
|
+
|
253
|
+
{ type:, rating: }
|
254
|
+
end
|
255
|
+
|
169
256
|
def parse_response(httpx_response)
|
170
257
|
logger.measure_trace "Parsing response" do
|
171
258
|
Nokogiri::HTML.parse(httpx_response)
|
data/lib/furaffinity/queue.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "fileutils"
|
4
|
-
require "shellwords"
|
5
4
|
require "yaml"
|
6
5
|
|
7
6
|
module Furaffinity
|
@@ -35,8 +34,46 @@ module Furaffinity
|
|
35
34
|
scrap: false
|
36
35
|
lock_comments: false
|
37
36
|
|
38
|
-
#
|
39
|
-
|
37
|
+
# Create a new folder to place this submission under, leave blank if none should be created.
|
38
|
+
create_folder_name: ""
|
39
|
+
|
40
|
+
# Run this Ruby code after uploading
|
41
|
+
after_upload: |-
|
42
|
+
# Quick reference
|
43
|
+
#
|
44
|
+
# Available objects:
|
45
|
+
# - `client` (Furaffinity::Client)
|
46
|
+
# The client object used to interact with FurAffinity.
|
47
|
+
# - `submission_info` (Hash)
|
48
|
+
# The current submission information of this YAML file. Keys are symbols.
|
49
|
+
# To get the description use e.g. `submission_info[:description]`.
|
50
|
+
# This also contains the ID of the uploaded submission, e.g.
|
51
|
+
# `submission_info[:id]`.
|
52
|
+
# - `file_info` (Hash)
|
53
|
+
# A hash of all YAML files. Format is
|
54
|
+
# `{ "filename.png" => { submission_info } }`.
|
55
|
+
# Like `submission_info` it also contains the submission's ID as the `:id`
|
56
|
+
# field if it's been uploaded.
|
57
|
+
#
|
58
|
+
# Helper functions:
|
59
|
+
# - `submission_url(submission_id)`
|
60
|
+
# Generates a submission URL, e.g.
|
61
|
+
# "https://www.furaffinity.net/view/54328944/"
|
62
|
+
# - `link_to(url_or_submission, text)`
|
63
|
+
# Generates a link. If the first parameter is a submission info hash it will
|
64
|
+
# generate the URL using `submission_url(submission_info[:id])`.
|
65
|
+
|
66
|
+
# Remove this `return` if you want to run Ruby code
|
67
|
+
return
|
68
|
+
|
69
|
+
# Append a link to this submission to a previously uploaded one
|
70
|
+
previous_submission = file_info.fetch("previous_file.png")
|
71
|
+
previous_submission[:description] += ("\n\n" + link_to(submission_info, "Alt version 2"))
|
72
|
+
client.update(**previous_submission)
|
73
|
+
|
74
|
+
# Append a link to the previous submission to the current one
|
75
|
+
submission_info[:description] += ("\n\n" + link_to(previous_submission, "Alt version 1"))
|
76
|
+
client.update(**submission_info)
|
40
77
|
YAML
|
41
78
|
|
42
79
|
attr_reader :client, :queue_dir, :queue, :upload_status, :file_info
|
@@ -109,7 +146,7 @@ module Furaffinity
|
|
109
146
|
end
|
110
147
|
|
111
148
|
submission_info_path = create_submission_info(file)
|
112
|
-
open_editor submission_info_path
|
149
|
+
CliUtils.open_editor submission_info_path
|
113
150
|
|
114
151
|
queue << file
|
115
152
|
upload_status[file] = {
|
@@ -151,12 +188,14 @@ module Furaffinity
|
|
151
188
|
end
|
152
189
|
|
153
190
|
def reorder
|
154
|
-
open_editor fa_info_path("queue.yml")
|
191
|
+
CliUtils.open_editor fa_info_path("queue.yml")
|
155
192
|
end
|
156
193
|
|
157
194
|
def upload(wait_time = 60)
|
158
195
|
raise ArgumentError.new("wait_time must be at least 30") if wait_time < 30
|
159
196
|
|
197
|
+
hook_handler = QueueHook.new(client, file_info)
|
198
|
+
|
160
199
|
while file_name = queue.shift
|
161
200
|
info = file_info[file_name]
|
162
201
|
unless info
|
@@ -164,6 +203,8 @@ module Furaffinity
|
|
164
203
|
next
|
165
204
|
end
|
166
205
|
|
206
|
+
code = file_info[file_name].delete(:after_upload)
|
207
|
+
|
167
208
|
logger.info "Uploading #{info[:title].inspect} (#{file_name.inspect})"
|
168
209
|
url = client.upload(
|
169
210
|
File.new(file_name),
|
@@ -175,6 +216,11 @@ module Furaffinity
|
|
175
216
|
|
176
217
|
save
|
177
218
|
|
219
|
+
if code
|
220
|
+
hook_handler.update_ids(upload_status)
|
221
|
+
hook_handler.run_hook(file_name, code)
|
222
|
+
end
|
223
|
+
|
178
224
|
unless queue.empty?
|
179
225
|
logger.info "Waiting #{wait_time} seconds until the next upload"
|
180
226
|
sleep wait_time
|
@@ -210,19 +256,5 @@ module Furaffinity
|
|
210
256
|
|
211
257
|
submission_info_path
|
212
258
|
end
|
213
|
-
|
214
|
-
def open_editor(file)
|
215
|
-
editor = ENV["FA_EDITOR"] || ENV["VISUAL"] || ENV["EDITOR"]
|
216
|
-
unless editor
|
217
|
-
logger.warn "could not open editor for #{file.inspect}, set one of FA_EDITOR, VISUAL, or EDITOR in your ENV"
|
218
|
-
return
|
219
|
-
end
|
220
|
-
|
221
|
-
system(*Shellwords.shellwords(editor), file).tap do
|
222
|
-
next if $?.exitstatus == 0
|
223
|
-
|
224
|
-
logger.error "could not run #{editor} #{file}, exit code: #{$?.exitstatus}"
|
225
|
-
end
|
226
|
-
end
|
227
259
|
end
|
228
260
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Furaffinity
|
4
|
+
class QueueHook
|
5
|
+
include SemanticLogger::Loggable
|
6
|
+
|
7
|
+
class HookRunner
|
8
|
+
include SemanticLogger::Loggable
|
9
|
+
|
10
|
+
attr_reader :client, :file_info, :submission_info
|
11
|
+
|
12
|
+
def initialize(client, file_info, file_name)
|
13
|
+
@client = client
|
14
|
+
@file_info = file_info
|
15
|
+
@submission_info = file_info.fetch(file_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def submission_url(submission_id) = "https://www.furaffinity.net/view/#{submission_id}/"
|
19
|
+
|
20
|
+
def link_to(url_or_submission, text)
|
21
|
+
url = case url_or_submission
|
22
|
+
in { id: }
|
23
|
+
submission_url(id)
|
24
|
+
else
|
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}" }
|
27
|
+
end
|
28
|
+
url_or_submission.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
"[url=#{url}]#{text}[/url]"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :client, :file_info
|
36
|
+
|
37
|
+
def initialize(client, file_info)
|
38
|
+
@client = client
|
39
|
+
@file_info = file_info.each_with_object({}) do |(file_name, info), h|
|
40
|
+
# Hash#except duplicates the hash, which is good here as we don't want
|
41
|
+
# to modify the queue.
|
42
|
+
# exclude after_upload as it's not needed, and create_folder_name and
|
43
|
+
# type is only relevant when initially uploading the submission.
|
44
|
+
h[file_name] = info.except(:create_folder_name, :after_upload, :type)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def update_ids(upload_status)
|
49
|
+
logger.trace { "Updating file info ids" }
|
50
|
+
upload_status.each do |file_name, status|
|
51
|
+
next unless status[:uploaded]
|
52
|
+
|
53
|
+
@file_info[file_name][:id] = status[:url].match(%{/view/(?<id>[^/]+)})[:id]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def run_hook(file_name, code)
|
58
|
+
logger.debug { "Running hook" }
|
59
|
+
logger.trace { "Hook code:\n#{code}" }
|
60
|
+
HookRunner
|
61
|
+
.new(client, file_info, file_name)
|
62
|
+
.instance_eval(code, File.join(Dir.pwd, "#{file_name}.info.yml"), 0)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/furaffinity/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: furaffinity
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Georg Gadinger
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httpx
|
@@ -113,9 +113,11 @@ files:
|
|
113
113
|
- lib/furaffinity/cli.rb
|
114
114
|
- lib/furaffinity/cli_base.rb
|
115
115
|
- lib/furaffinity/cli_queue.rb
|
116
|
+
- lib/furaffinity/cli_utils.rb
|
116
117
|
- lib/furaffinity/client.rb
|
117
118
|
- lib/furaffinity/config.rb
|
118
119
|
- lib/furaffinity/queue.rb
|
120
|
+
- lib/furaffinity/queue_hook.rb
|
119
121
|
- lib/furaffinity/version.rb
|
120
122
|
homepage: https://github.com/nilsding/furaffinity-cli
|
121
123
|
licenses:
|