furaffinity 0.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 +7 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE +662 -0
- data/README.md +84 -0
- data/Rakefile +5 -0
- data/exe/fa +11 -0
- data/lib/furaffinity/cli.rb +126 -0
- data/lib/furaffinity/cli_base.rb +17 -0
- data/lib/furaffinity/cli_queue.rb +140 -0
- data/lib/furaffinity/client.rb +175 -0
- data/lib/furaffinity/config.rb +53 -0
- data/lib/furaffinity/queue.rb +228 -0
- data/lib/furaffinity/version.rb +5 -0
- data/lib/furaffinity.rb +10 -0
- metadata +145 -0
@@ -0,0 +1,228 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "shellwords"
|
5
|
+
require "yaml"
|
6
|
+
|
7
|
+
module Furaffinity
|
8
|
+
class Queue
|
9
|
+
include SemanticLogger::Loggable
|
10
|
+
|
11
|
+
FA_QUEUE_DIR = ".fa"
|
12
|
+
|
13
|
+
SUBMISSION_INFO_EXT = ".info.yml"
|
14
|
+
|
15
|
+
SUBMISSION_TEMPLATE = <<~YAML
|
16
|
+
---
|
17
|
+
# Submission info for %<file_name>s
|
18
|
+
|
19
|
+
# Required field
|
20
|
+
title: ""
|
21
|
+
|
22
|
+
# Required field
|
23
|
+
description: |-
|
24
|
+
Your description goes here
|
25
|
+
|
26
|
+
# Optional field, keywords separated by spaces
|
27
|
+
keywords: ""
|
28
|
+
|
29
|
+
# Required field, one of: [#{Furaffinity::Client::RATING_MAP.keys.join(", ")}]
|
30
|
+
rating: general
|
31
|
+
|
32
|
+
# Required field, one of: [#{Furaffinity::Client::SUBMISSION_TYPES.join(", ")}]
|
33
|
+
type: submission
|
34
|
+
|
35
|
+
scrap: false
|
36
|
+
lock_comments: false
|
37
|
+
|
38
|
+
# Folder name to place this submission under, leave blank if it should not be in any.
|
39
|
+
folder_name: ""
|
40
|
+
YAML
|
41
|
+
|
42
|
+
attr_reader :client, :queue_dir, :queue, :upload_status, :file_info
|
43
|
+
|
44
|
+
# @param client [Furaffinity::Client]
|
45
|
+
# @param queue_dir [String]
|
46
|
+
def initialize(client, queue_dir)
|
47
|
+
@client = client
|
48
|
+
@queue_dir = queue_dir
|
49
|
+
@queue = []
|
50
|
+
@upload_status = {}
|
51
|
+
@file_info = {}
|
52
|
+
end
|
53
|
+
|
54
|
+
def fa_info_dir = File.join(queue_dir, FA_QUEUE_DIR)
|
55
|
+
|
56
|
+
def fa_info_path(*path) = File.join(fa_info_dir, *path)
|
57
|
+
|
58
|
+
def submission_template_path = fa_info_path("templates", "submission.yml")
|
59
|
+
|
60
|
+
# loads state info from queue dir
|
61
|
+
def reload
|
62
|
+
logger.trace { "Loading state info" }
|
63
|
+
|
64
|
+
@queue = YAML.safe_load_file(fa_info_path("queue.yml"), permitted_classes: [Symbol])
|
65
|
+
@upload_status = YAML.safe_load_file(fa_info_path("status.yml"), permitted_classes: [Symbol])
|
66
|
+
@file_info = Dir[File.join(queue_dir, "**/*#{SUBMISSION_INFO_EXT}")].map do |path|
|
67
|
+
[path.delete_suffix(SUBMISSION_INFO_EXT).sub(/^#{Regexp.escape(queue_dir)}\/?/, ""), YAML.safe_load_file(path, permitted_classes: [Symbol]).transform_keys(&:to_sym)]
|
68
|
+
end.to_h
|
69
|
+
|
70
|
+
logger.trace "Loaded state info", queue:, file_info:
|
71
|
+
end
|
72
|
+
|
73
|
+
def init
|
74
|
+
if Dir.exist?(queue_dir)
|
75
|
+
logger.trace { "Checking if directory #{queue_dir.inspect} is empty" }
|
76
|
+
raise Error.new("#{queue_dir.inspect} is not empty") unless Dir.empty?(queue_dir)
|
77
|
+
end
|
78
|
+
|
79
|
+
logger.trace { "Creating directory #{fa_info_dir.inspect}" }
|
80
|
+
FileUtils.mkdir_p(fa_info_dir)
|
81
|
+
|
82
|
+
%w[templates].each do |dir|
|
83
|
+
logger.trace { "Creating directory #{fa_info_path(dir).inspect}" }
|
84
|
+
FileUtils.mkdir_p(fa_info_path(dir))
|
85
|
+
end
|
86
|
+
|
87
|
+
logger.trace { "Creating empty state files" }
|
88
|
+
save
|
89
|
+
|
90
|
+
logger.trace { "Creating submission template" }
|
91
|
+
File.open(submission_template_path, "w") do |f|
|
92
|
+
f.puts(SUBMISSION_TEMPLATE)
|
93
|
+
end
|
94
|
+
|
95
|
+
logger.debug "Created new queue dir in #{queue_dir.inspect}"
|
96
|
+
queue_dir
|
97
|
+
end
|
98
|
+
|
99
|
+
def add(*files)
|
100
|
+
files.select do |file|
|
101
|
+
unless File.exist?(file)
|
102
|
+
logger.warn "File #{file.inspect} does not exist"
|
103
|
+
next false
|
104
|
+
end
|
105
|
+
|
106
|
+
if queue.include?(file)
|
107
|
+
logger.warn "File #{file.inspect} is already in the queue"
|
108
|
+
next false
|
109
|
+
end
|
110
|
+
|
111
|
+
submission_info_path = create_submission_info(file)
|
112
|
+
open_editor submission_info_path
|
113
|
+
|
114
|
+
queue << file
|
115
|
+
upload_status[file] = {
|
116
|
+
uploaded: false,
|
117
|
+
url: nil,
|
118
|
+
}
|
119
|
+
|
120
|
+
save
|
121
|
+
|
122
|
+
true
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def remove(*files)
|
127
|
+
files.select do |file|
|
128
|
+
unless queue.include?(file)
|
129
|
+
logger.warn "File #{file.inspect} is not in the queue"
|
130
|
+
next false
|
131
|
+
end
|
132
|
+
|
133
|
+
queue.delete(file)
|
134
|
+
upload_status.delete(file)
|
135
|
+
|
136
|
+
save
|
137
|
+
|
138
|
+
true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def clean
|
143
|
+
uploaded_files.each do |file, _upload_info|
|
144
|
+
logger.trace { "Deleting #{file} ..." }
|
145
|
+
queue.delete(file)
|
146
|
+
upload_status.delete(file)
|
147
|
+
File.unlink(file)
|
148
|
+
File.unlink(file + SUBMISSION_INFO_EXT)
|
149
|
+
save
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def reorder
|
154
|
+
open_editor fa_info_path("queue.yml")
|
155
|
+
end
|
156
|
+
|
157
|
+
def upload(wait_time = 60)
|
158
|
+
raise ArgumentError.new("wait_time must be at least 30") if wait_time < 30
|
159
|
+
|
160
|
+
while file_name = queue.shift
|
161
|
+
info = file_info[file_name]
|
162
|
+
unless info
|
163
|
+
logger.warn "no file info found for #{file_name}, ignoring"
|
164
|
+
next
|
165
|
+
end
|
166
|
+
|
167
|
+
logger.info "Uploading #{info[:title].inspect} (#{file_name.inspect})"
|
168
|
+
url = client.upload(
|
169
|
+
File.new(file_name),
|
170
|
+
**file_info[file_name]
|
171
|
+
)
|
172
|
+
|
173
|
+
upload_status[file_name][:uploaded] = true
|
174
|
+
upload_status[file_name][:url] = url
|
175
|
+
|
176
|
+
save
|
177
|
+
|
178
|
+
unless queue.empty?
|
179
|
+
logger.info "Waiting #{wait_time} seconds until the next upload"
|
180
|
+
sleep wait_time
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def uploaded_files
|
186
|
+
upload_status.select { _2[:uploaded] }
|
187
|
+
end
|
188
|
+
|
189
|
+
def save
|
190
|
+
{ queue:, status: upload_status }.each do |type, content|
|
191
|
+
path = fa_info_path("#{type}.yml")
|
192
|
+
logger.trace { "Writing #{path}" }
|
193
|
+
yaml_content = content.to_yaml
|
194
|
+
File.open(path, "w") do |f|
|
195
|
+
f.puts(yaml_content)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def create_submission_info(file)
|
201
|
+
template = File.read(submission_template_path)
|
202
|
+
|
203
|
+
submission_info_path = "#{file}#{SUBMISSION_INFO_EXT}"
|
204
|
+
return submission_info_path if File.exist?(submission_info_path)
|
205
|
+
|
206
|
+
rendered_template = format(template, file_name: file.inspect)
|
207
|
+
File.open(submission_info_path, "w") do |f|
|
208
|
+
f.puts rendered_template
|
209
|
+
end
|
210
|
+
|
211
|
+
submission_info_path
|
212
|
+
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
|
+
end
|
228
|
+
end
|
data/lib/furaffinity.rb
ADDED
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: furaffinity
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Georg Gadinger
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-11-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: httpx
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nokogiri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.15'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.15'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: semantic_logger
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.14'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.14'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: thor
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.3'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: zeitwerk
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.6'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.6'
|
97
|
+
description: A command line tool to interface with FurAffinity
|
98
|
+
email:
|
99
|
+
- nilsding@nilsding.org
|
100
|
+
executables:
|
101
|
+
- fa
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".rubocop.yml"
|
106
|
+
- CHANGELOG.md
|
107
|
+
- CODE_OF_CONDUCT.md
|
108
|
+
- LICENSE
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- exe/fa
|
112
|
+
- lib/furaffinity.rb
|
113
|
+
- lib/furaffinity/cli.rb
|
114
|
+
- lib/furaffinity/cli_base.rb
|
115
|
+
- lib/furaffinity/cli_queue.rb
|
116
|
+
- lib/furaffinity/client.rb
|
117
|
+
- lib/furaffinity/config.rb
|
118
|
+
- lib/furaffinity/queue.rb
|
119
|
+
- lib/furaffinity/version.rb
|
120
|
+
homepage: https://github.com/nilsding/furaffinity-cli
|
121
|
+
licenses:
|
122
|
+
- AGPLv3
|
123
|
+
metadata:
|
124
|
+
homepage_uri: https://github.com/nilsding/furaffinity-cli
|
125
|
+
source_code_uri: https://github.com/nilsding/furaffinity-cli
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 3.2.0
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubygems_version: 3.4.10
|
142
|
+
signing_key:
|
143
|
+
specification_version: 4
|
144
|
+
summary: FurAffinity CLI tool
|
145
|
+
test_files: []
|