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.
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Furaffinity
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "semantic_logger"
4
+ require "zeitwerk"
5
+ loader = Zeitwerk::Loader.for_gem
6
+ loader.setup
7
+
8
+ module Furaffinity
9
+ class Error < StandardError; end
10
+ end
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: []