e621_export_downloader 0.0.1
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/.irbrc +14 -0
- data/.ruby-version +1 -0
- data/LICENSE +21 -0
- data/README.md +148 -0
- data/Rakefile +8 -0
- data/exe/e621-export-downloader +112 -0
- data/lib/e621_export_downloader/client/options/builder/parsers.rb +42 -0
- data/lib/e621_export_downloader/client/options/builder.rb +44 -0
- data/lib/e621_export_downloader/client/options.rb +37 -0
- data/lib/e621_export_downloader/client.rb +120 -0
- data/lib/e621_export_downloader/constants.rb +17 -0
- data/lib/e621_export_downloader/export.rb +128 -0
- data/lib/e621_export_downloader/export_helper.rb +83 -0
- data/lib/e621_export_downloader/models/pool.rb +69 -0
- data/lib/e621_export_downloader/models/post.rb +166 -0
- data/lib/e621_export_downloader/models/tag.rb +41 -0
- data/lib/e621_export_downloader/models/tag_alias.rb +46 -0
- data/lib/e621_export_downloader/models/tag_implication.rb +46 -0
- data/lib/e621_export_downloader/models/wiki_page.rb +61 -0
- data/lib/e621_export_downloader/types.rb +14 -0
- data/lib/e621_export_downloader/version.rb +10 -0
- data/lib/e621_export_downloader.rb +12 -0
- data/sorbet/config +5 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/faraday.rbi +17 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/ast@2.4.3.rbi +550 -0
- data/sorbet/rbi/gems/benchmark@0.5.0.rbi +621 -0
- data/sorbet/rbi/gems/csv@3.3.5.rbi +4462 -0
- data/sorbet/rbi/gems/date@3.5.1.rbi +391 -0
- data/sorbet/rbi/gems/erb@6.0.4.rbi +1538 -0
- data/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
- data/sorbet/rbi/gems/faraday-net_http@3.4.2.rbi +9 -0
- data/sorbet/rbi/gems/faraday@2.14.1.rbi +9 -0
- data/sorbet/rbi/gems/io-console@0.8.2.rbi +9 -0
- data/sorbet/rbi/gems/json@2.19.5.rbi +2240 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.5.rbi +9 -0
- data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +189 -0
- data/sorbet/rbi/gems/logger@1.7.0.rbi +896 -0
- data/sorbet/rbi/gems/net-http@0.9.1.rbi +4029 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +147 -0
- data/sorbet/rbi/gems/parallel@2.1.0.rbi +321 -0
- data/sorbet/rbi/gems/parser@3.3.11.1.rbi +5229 -0
- data/sorbet/rbi/gems/pp@0.6.3.rbi +377 -0
- data/sorbet/rbi/gems/prettyprint@0.2.0.rbi +455 -0
- data/sorbet/rbi/gems/prism@1.9.0.rbi +42224 -0
- data/sorbet/rbi/gems/psych@5.3.1.rbi +2374 -0
- data/sorbet/rbi/gems/racc@1.8.1.rbi +165 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +362 -0
- data/sorbet/rbi/gems/rake@13.4.2.rbi +3130 -0
- data/sorbet/rbi/gems/rbi@0.3.11.rbi +5505 -0
- data/sorbet/rbi/gems/rbs@4.0.2.rbi +6908 -0
- data/sorbet/rbi/gems/rdoc@7.2.0.rbi +9 -0
- data/sorbet/rbi/gems/regexp_parser@2.12.0.rbi +3398 -0
- data/sorbet/rbi/gems/reline@0.6.3.rbi +2446 -0
- data/sorbet/rbi/gems/require-hooks@0.4.0.rbi +152 -0
- data/sorbet/rbi/gems/rexml@3.4.4.rbi +4905 -0
- data/sorbet/rbi/gems/rubocop-ast@1.49.1.rbi +7062 -0
- data/sorbet/rbi/gems/rubocop-rake@0.7.1.rbi +314 -0
- data/sorbet/rbi/gems/rubocop@1.86.1.rbi +62227 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +988 -0
- data/sorbet/rbi/gems/rubydex@0.2.0.rbi +663 -0
- data/sorbet/rbi/gems/spoom@1.7.13.rbi +6151 -0
- data/sorbet/rbi/gems/stringio@3.2.0.rbi +9 -0
- data/sorbet/rbi/gems/tapioca@0.19.1.rbi +3555 -0
- data/sorbet/rbi/gems/thor@1.5.0.rbi +3870 -0
- data/sorbet/rbi/gems/tsort@0.2.0.rbi +389 -0
- data/sorbet/rbi/gems/unicode-display_width@3.2.0.rbi +130 -0
- data/sorbet/rbi/gems/unicode-emoji@4.2.0.rbi +332 -0
- data/sorbet/rbi/gems/uri@1.1.1.rbi +2400 -0
- data/sorbet/rbi/gems/zeitwerk@2.7.5.rbi +1090 -0
- data/sorbet/rbi/shims/faraday.rbi +42 -0
- data/sorbet/rbi/todo.rbi +7 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +4 -0
- metadata +177 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 07a77d7c815326f0a51b7026c42f89c187fc526cc7caf20af35839e628e5243e
|
|
4
|
+
data.tar.gz: 23e2cd461ce89e9be5ff79d4a398e84762560e19a6edc9afffb323954ec37ba9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 25ac18b83938e29298b3221b162594c6112a679b11d510ac70c46aff148f5fb08ede1b26466ccc942aa74a898a3680d7eb2087fb4374f49387b133ecf3974dc2
|
|
7
|
+
data.tar.gz: 316c7856999ce0ef76a24ba7dcaa7870969a0c1c41a8efe9a4ba30d63447bccc55d172a6c797359b916da2f76d89d2433f0047810798f23a859726e3ed21052b
|
data/.irbrc
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: true
|
|
3
|
+
|
|
4
|
+
require_relative("lib/e621_export_downloader")
|
|
5
|
+
require("sorbet-runtime")
|
|
6
|
+
|
|
7
|
+
extend(T::Sig) # rubocop:disable Style/MixinUsage
|
|
8
|
+
|
|
9
|
+
E621ExportDownloader::Client::Logger.level = Logger::DEBUG
|
|
10
|
+
|
|
11
|
+
sig { returns(E621ExportDownloader::Client) }
|
|
12
|
+
def client
|
|
13
|
+
E621ExportDownloader::Client.new
|
|
14
|
+
end
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.4.1
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Donovan_DMC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# E621 Export Downloader
|
|
2
|
+
|
|
3
|
+
A utility for downloading and parsing e621's exports.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
gem install e621_export_downloader
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or add it to your Gemfile:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bundle add e621_export_downloader
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
require("e621_export_downloader")
|
|
21
|
+
|
|
22
|
+
client = E621ExportDownloader::Client.new
|
|
23
|
+
|
|
24
|
+
# configure options after creation
|
|
25
|
+
client.config do |c|
|
|
26
|
+
c.cache = true # keep export files after reading, defaults to true
|
|
27
|
+
c.rewind_on_not_found = 2 # decrease date by one day if no export is found for that date,
|
|
28
|
+
# provide an integer to limit how many days can be rewound,
|
|
29
|
+
# `true` is equivalent to `2`, defaults to false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# or pass an Options struct directly
|
|
33
|
+
client = E621ExportDownloader::Client.new(
|
|
34
|
+
E621ExportDownloader::Client::Options.new(
|
|
35
|
+
cache: true,
|
|
36
|
+
rewind_on_not_found: 2,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# get the helper for interacting with a type of export
|
|
41
|
+
# types: pools, posts, tag_aliases, tag_implications, tags, wiki_pages
|
|
42
|
+
helper = client.get("posts")
|
|
43
|
+
# or use the named shorthand:
|
|
44
|
+
helper = client.posts
|
|
45
|
+
|
|
46
|
+
# get a wrapper for a specific date (time components are ignored)
|
|
47
|
+
# using this directly will not trigger rewinding regardless of rewind_on_not_found
|
|
48
|
+
export = helper.get(Date.today)
|
|
49
|
+
|
|
50
|
+
# convenience shorthand on the client — also skips rewinding
|
|
51
|
+
export = client.get_posts(Date.today)
|
|
52
|
+
|
|
53
|
+
# all of the following methods can also be called on the helper with a date argument;
|
|
54
|
+
# if rewind_on_not_found is enabled the helper decrements the date by one day until it
|
|
55
|
+
# finds an export or exhausts the rewind limit, at which point it raises ResolveError
|
|
56
|
+
|
|
57
|
+
# check whether the export exists for the date
|
|
58
|
+
exists = export.exists?
|
|
59
|
+
exists = helper.exists?(Date.today)
|
|
60
|
+
|
|
61
|
+
# download the export, returns the file path — not required before reading
|
|
62
|
+
# if you move or remove the file do not reuse the export object
|
|
63
|
+
file = export.download
|
|
64
|
+
file = helper.download(Date.today)
|
|
65
|
+
|
|
66
|
+
# delete the downloaded file, if it exists
|
|
67
|
+
export.delete
|
|
68
|
+
helper.delete(Date.today)
|
|
69
|
+
|
|
70
|
+
# get all of the records as a single array, DO NOT use this for large exports, arrays will millions of items do not perform well and will likely crash your process!
|
|
71
|
+
# (not to mention that the posts export is more than 5 gigabytes)
|
|
72
|
+
records = export.read_all
|
|
73
|
+
|
|
74
|
+
# read streams the CSV and yields each parsed record together with the total row count
|
|
75
|
+
# this is the recommended approach for large exports (the posts export exceeds 5 GB)
|
|
76
|
+
export.read do |record, total|
|
|
77
|
+
# record is an E621ExportDownloader::Models::Post instance
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Replace or extend a parser
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
require("e621_export_downloader")
|
|
85
|
+
|
|
86
|
+
client = E621ExportDownloader::Client.new
|
|
87
|
+
|
|
88
|
+
client.config do |c|
|
|
89
|
+
c.parsers do |p|
|
|
90
|
+
# replace a parser with a custom proc that receives the raw CSV row hash
|
|
91
|
+
# return nil to skip a record
|
|
92
|
+
p.posts = proc do |record|
|
|
93
|
+
post = E621ExportDownloader::Models::Post.new(record)
|
|
94
|
+
# attach extra data or wrap in your own class
|
|
95
|
+
post
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Models
|
|
102
|
+
|
|
103
|
+
Each export type is parsed into a corresponding model class:
|
|
104
|
+
|
|
105
|
+
| Export type | Model class |
|
|
106
|
+
|-------------------|---------------------------------------------------|
|
|
107
|
+
| `pools` | `E621ExportDownloader::Models::Pool` |
|
|
108
|
+
| `posts` | `E621ExportDownloader::Models::Post` |
|
|
109
|
+
| `tag_aliases` | `E621ExportDownloader::Models::TagAlias` |
|
|
110
|
+
| `tag_implications`| `E621ExportDownloader::Models::TagImplication` |
|
|
111
|
+
| `tags` | `E621ExportDownloader::Models::Tag` |
|
|
112
|
+
| `wiki_pages` | `E621ExportDownloader::Models::WikiPage` |
|
|
113
|
+
|
|
114
|
+
## CLI
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# check if an export exists for a given date; date is optional and defaults to today
|
|
118
|
+
# outputs "true" or "false" with no trailing newline
|
|
119
|
+
e621-export-downloader exists posts 2024-01-01
|
|
120
|
+
|
|
121
|
+
# download an export for a given date; date is optional and defaults to today
|
|
122
|
+
# outputs the path to the downloaded file with no trailing newline
|
|
123
|
+
e621-export-downloader download posts 2024-01-01
|
|
124
|
+
|
|
125
|
+
# read an export as individual JSON lines; date is optional and defaults to today
|
|
126
|
+
# outputs each record as a JSON object on its own line
|
|
127
|
+
e621-export-downloader read posts 2024-01-01
|
|
128
|
+
|
|
129
|
+
# read an export as a JSON array; date is optional and defaults to today
|
|
130
|
+
# streams the CSV internally, so it is safe to use with large exports
|
|
131
|
+
e621-export-downloader read-all posts 2024-01-01
|
|
132
|
+
|
|
133
|
+
e621-export-downloader --help
|
|
134
|
+
e621-export-downloader --version
|
|
135
|
+
e621-export-downloader --cache # enable caching (default)
|
|
136
|
+
e621-export-downloader --no-cache # disable caching
|
|
137
|
+
e621-export-downloader --rewind-on-not-found # enable rewinding (up to 2 days)
|
|
138
|
+
e621-export-downloader --rewind-on-not-found 5 # enable rewinding (up to 5 days)
|
|
139
|
+
e621-export-downloader --no-rewind-on-not-found # disable rewinding (default)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Contributing
|
|
143
|
+
|
|
144
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/DonovanDMC/E621ExportDownloader.rb.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require("optparse")
|
|
5
|
+
require("date")
|
|
6
|
+
require("e621_export_downloader")
|
|
7
|
+
|
|
8
|
+
options = {
|
|
9
|
+
cache: true,
|
|
10
|
+
rewind_on_not_found: false,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
parser = OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
|
|
14
|
+
opts.program_name = "e621-export-downloader"
|
|
15
|
+
opts.banner = <<~BANNER
|
|
16
|
+
Usage: e621-export-downloader [options] <command> [args]
|
|
17
|
+
|
|
18
|
+
Commands:
|
|
19
|
+
exists <name> [date] Check if an export exists for a given date
|
|
20
|
+
download <name> [date] Download an export for a given date
|
|
21
|
+
read <name> [date] Read an export as individual JSON lines
|
|
22
|
+
read-all <name> [date] Read an export as a JSON array
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
BANNER
|
|
26
|
+
|
|
27
|
+
opts.on("--[no-]cache", "Cache downloaded exports (default: enabled)") { |v| options[:cache] = v }
|
|
28
|
+
|
|
29
|
+
opts.on("--rewind-on-not-found [DAYS]", Integer,
|
|
30
|
+
"Rewind date if export not found; optionally limit how many days (true = 2)") do |v|
|
|
31
|
+
options[:rewind_on_not_found] = v || true
|
|
32
|
+
end
|
|
33
|
+
opts.on("--no-rewind-on-not-found", "Disable rewinding of export dates (default)") do
|
|
34
|
+
options[:rewind_on_not_found] = false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
opts.on("-v", "--version", "Print the version") do
|
|
38
|
+
puts(E621ExportDownloader::Constants::VERSION)
|
|
39
|
+
exit
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
opts.on_tail("-h", "--help", "Print this help") do
|
|
43
|
+
puts(opts)
|
|
44
|
+
exit
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
parser.order!(ARGV)
|
|
49
|
+
|
|
50
|
+
command = ARGV.shift
|
|
51
|
+
if command.nil?
|
|
52
|
+
puts(parser)
|
|
53
|
+
exit(1)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
name = ARGV.shift
|
|
57
|
+
if name.nil?
|
|
58
|
+
warn("Error: export name is required")
|
|
59
|
+
exit(1)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
date_str = ARGV.shift
|
|
63
|
+
date = date_str ? Date.parse(date_str) : Date.today
|
|
64
|
+
|
|
65
|
+
client = E621ExportDownloader::Client.new(
|
|
66
|
+
E621ExportDownloader::Client::Options.new(
|
|
67
|
+
cache: options[:cache],
|
|
68
|
+
rewind_on_not_found: options[:rewind_on_not_found],
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Downloads via the helper (respects rewind_on_not_found) and returns an Export
|
|
73
|
+
# handle that is already marked as downloaded so read won't re-fetch.
|
|
74
|
+
def download_and_open(helper, date)
|
|
75
|
+
path = helper.download(date)
|
|
76
|
+
resolved_date = Date.parse(File.basename(path, ".csv")[-10..])
|
|
77
|
+
E621ExportDownloader::Export.new(date: resolved_date, helper: helper)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
helper = client.get(name)
|
|
81
|
+
|
|
82
|
+
case command
|
|
83
|
+
when "exists"
|
|
84
|
+
print(helper.exists?(date))
|
|
85
|
+
|
|
86
|
+
when "download"
|
|
87
|
+
print(helper.download(date))
|
|
88
|
+
|
|
89
|
+
when "read"
|
|
90
|
+
export = download_and_open(helper, date)
|
|
91
|
+
first = true
|
|
92
|
+
export.read do |record, _total|
|
|
93
|
+
$stdout.write("\n") unless first
|
|
94
|
+
first = false
|
|
95
|
+
$stdout.write(record.to_json)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
when "read-all"
|
|
99
|
+
export = download_and_open(helper, date)
|
|
100
|
+
$stdout.write("[")
|
|
101
|
+
first = true
|
|
102
|
+
export.read do |record, _total|
|
|
103
|
+
$stdout.write(",") unless first
|
|
104
|
+
first = false
|
|
105
|
+
$stdout.write(record.to_json)
|
|
106
|
+
end
|
|
107
|
+
$stdout.write("]")
|
|
108
|
+
|
|
109
|
+
else
|
|
110
|
+
warn("Error: unknown command '#{command}'. Run with --help for usage.")
|
|
111
|
+
exit(1)
|
|
112
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
module E621ExportDownloader
|
|
5
|
+
class Client
|
|
6
|
+
class Options
|
|
7
|
+
class Builder
|
|
8
|
+
class Parsers
|
|
9
|
+
extend(T::Sig)
|
|
10
|
+
|
|
11
|
+
sig { returns(Parser) }
|
|
12
|
+
attr_accessor(:pools)
|
|
13
|
+
|
|
14
|
+
sig { returns(Parser) }
|
|
15
|
+
attr_accessor(:posts)
|
|
16
|
+
|
|
17
|
+
sig { returns(Parser) }
|
|
18
|
+
attr_accessor(:tag_aliases)
|
|
19
|
+
|
|
20
|
+
sig { returns(Parser) }
|
|
21
|
+
attr_accessor(:tag_implications)
|
|
22
|
+
|
|
23
|
+
sig { returns(Parser) }
|
|
24
|
+
attr_accessor(:tags)
|
|
25
|
+
|
|
26
|
+
sig { returns(Parser) }
|
|
27
|
+
attr_accessor(:wiki_pages)
|
|
28
|
+
|
|
29
|
+
sig { params(defaults: Options::Parsers).void }
|
|
30
|
+
def initialize(defaults = Options::Parsers.defaults)
|
|
31
|
+
@pools = T.let(T.must(defaults.pools), Parser)
|
|
32
|
+
@posts = T.let(T.must(defaults.posts), Parser)
|
|
33
|
+
@tag_aliases = T.let(T.must(defaults.tag_aliases), Parser)
|
|
34
|
+
@tag_implications = T.let(T.must(defaults.tag_implications), Parser)
|
|
35
|
+
@tags = T.let(T.must(defaults.tags), Parser)
|
|
36
|
+
@wiki_pages = T.let(T.must(defaults.wiki_pages), Parser)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
module E621ExportDownloader
|
|
5
|
+
class Client
|
|
6
|
+
class Options
|
|
7
|
+
class Builder
|
|
8
|
+
extend(T::Sig)
|
|
9
|
+
|
|
10
|
+
sig { returns(T::Boolean) }
|
|
11
|
+
attr_accessor(:cache)
|
|
12
|
+
|
|
13
|
+
sig { returns(T.any(T::Boolean, Integer)) }
|
|
14
|
+
attr_accessor(:rewind_on_not_found)
|
|
15
|
+
|
|
16
|
+
sig { params(parsers: Options::Parsers).returns(Options::Parsers) }
|
|
17
|
+
attr_writer(:parsers)
|
|
18
|
+
|
|
19
|
+
sig { params(defaults: Options).void }
|
|
20
|
+
def initialize(defaults = Options.new)
|
|
21
|
+
@cache = T.let(defaults.cache, T::Boolean)
|
|
22
|
+
@rewind_on_not_found = T.let(defaults.rewind_on_not_found, T.any(T::Boolean, Integer))
|
|
23
|
+
@parsers = T.let(defaults.parsers, Options::Parsers)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
sig { params(block: T.nilable(T.proc.params(arg0: Builder::Parsers).void)).returns(Options::Parsers) }
|
|
27
|
+
def parsers(&block)
|
|
28
|
+
return @parsers if block.nil?
|
|
29
|
+
parsers = Builder::Parsers.new(@parsers)
|
|
30
|
+
block.call(parsers)
|
|
31
|
+
|
|
32
|
+
@parsers = Options::Parsers.new(
|
|
33
|
+
pools: parsers.pools,
|
|
34
|
+
posts: parsers.posts,
|
|
35
|
+
tag_aliases: parsers.tag_aliases,
|
|
36
|
+
tag_implications: parsers.tag_implications,
|
|
37
|
+
tags: parsers.tags,
|
|
38
|
+
wiki_pages: parsers.wiki_pages,
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
module E621ExportDownloader
|
|
5
|
+
class Client
|
|
6
|
+
class Options < T::Struct
|
|
7
|
+
Parser = T.type_alias { T.proc.params(arg0: T::Hash[String, String]).returns(T.untyped) }
|
|
8
|
+
|
|
9
|
+
class Parsers < T::Struct
|
|
10
|
+
extend(T::Sig)
|
|
11
|
+
|
|
12
|
+
const(:pools, T.nilable(Parser), default: nil)
|
|
13
|
+
const(:posts, T.nilable(Parser), default: nil)
|
|
14
|
+
const(:tag_aliases, T.nilable(Parser), default: nil)
|
|
15
|
+
const(:tag_implications, T.nilable(Parser), default: nil)
|
|
16
|
+
const(:tags, T.nilable(Parser), default: nil)
|
|
17
|
+
const(:wiki_pages, T.nilable(Parser), default: nil)
|
|
18
|
+
|
|
19
|
+
sig { returns(Parsers) }
|
|
20
|
+
def self.defaults
|
|
21
|
+
Parsers.new(
|
|
22
|
+
pools: ->(record) { Models::Pool.new(record) },
|
|
23
|
+
posts: ->(record) { Models::Post.new(record) },
|
|
24
|
+
tag_aliases: ->(record) { Models::TagAlias.new(record) },
|
|
25
|
+
tag_implications: ->(record) { Models::TagImplication.new(record) },
|
|
26
|
+
tags: ->(record) { Models::Tag.new(record) },
|
|
27
|
+
wiki_pages: ->(record) { Models::WikiPage.new(record) },
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
const(:cache, T::Boolean, default: true)
|
|
33
|
+
const(:rewind_on_not_found, T.any(T::Boolean, Integer), default: false)
|
|
34
|
+
const(:parsers, Parsers, default: Parsers.defaults)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
require("faraday")
|
|
5
|
+
|
|
6
|
+
module E621ExportDownloader
|
|
7
|
+
class Client
|
|
8
|
+
extend(T::Sig)
|
|
9
|
+
|
|
10
|
+
Logger = ::Logger.new($stdout)
|
|
11
|
+
|
|
12
|
+
sig { returns(Options) }
|
|
13
|
+
attr_reader(:options)
|
|
14
|
+
|
|
15
|
+
sig { params(options: T.nilable(Options)).void }
|
|
16
|
+
def initialize(options = nil)
|
|
17
|
+
@options = T.let(options || Options.new, Options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
sig { params(block: T.proc.params(arg0: Options::Builder).void).void }
|
|
21
|
+
def config(&block)
|
|
22
|
+
block.call(Options::Builder.new(@options))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
sig { returns(Faraday::Connection) }
|
|
26
|
+
def connection
|
|
27
|
+
Faraday.new(url: Constants::BASE_URL, headers: { "User-Agent" => Constants::USER_AGENT })
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sig { params(msg: String, header: T::Array[String]).void }
|
|
31
|
+
def debug(msg, header: [])
|
|
32
|
+
Logger.debug("[e621_export_downloader#{":#{header.join(':')}" unless header.empty?}] #{msg}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
sig { params(type: T.any(Types, String)).returns(ExportHelper[T.untyped]) }
|
|
36
|
+
def get(type)
|
|
37
|
+
if type.is_a?(String)
|
|
38
|
+
case type
|
|
39
|
+
when "pools"
|
|
40
|
+
type = Types::Pools
|
|
41
|
+
when "posts"
|
|
42
|
+
type = Types::Posts
|
|
43
|
+
when "tag_aliases"
|
|
44
|
+
type = Types::TagAliases
|
|
45
|
+
when "tag_implications"
|
|
46
|
+
type = Types::TagImplications
|
|
47
|
+
when "tags"
|
|
48
|
+
type = Types::Tags
|
|
49
|
+
when "wiki_pages"
|
|
50
|
+
type = Types::WikiPages
|
|
51
|
+
else
|
|
52
|
+
raise(ArgumentError, "invalid type: #{type}")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
T.assert_type!(type, Types)
|
|
57
|
+
ExportHelper.new(client: self, type: type, parser: options.parsers.public_send(type.serialize))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
sig { returns(ExportHelper[Models::Pool]) }
|
|
61
|
+
def pools
|
|
62
|
+
get(Types::Pools)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
sig { params(date: T.any(Date, DateTime)).returns(Export[Models::Pool]) }
|
|
66
|
+
def get_pools(date = Date.today)
|
|
67
|
+
pools.get(date)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
sig { returns(ExportHelper[Models::Post]) }
|
|
71
|
+
def posts
|
|
72
|
+
get(Types::Posts)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
sig { params(date: T.any(Date, DateTime)).returns(Export[Models::Post]) }
|
|
76
|
+
def get_posts(date = Date.today)
|
|
77
|
+
posts.get(date)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
sig { returns(ExportHelper[Models::TagAlias]) }
|
|
81
|
+
def tag_aliases
|
|
82
|
+
get(Types::TagAliases)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
sig { params(date: T.any(Date, DateTime)).returns(Export[Models::TagAlias]) }
|
|
86
|
+
def get_tag_aliases(date = Date.today)
|
|
87
|
+
tag_aliases.get(date)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
sig { returns(ExportHelper[Models::TagImplication]) }
|
|
91
|
+
def tag_implications
|
|
92
|
+
get(Types::TagImplications)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
sig { params(date: T.any(Date, DateTime)).returns(Export[Models::TagImplication]) }
|
|
96
|
+
def get_tag_implications(date = Date.today)
|
|
97
|
+
tag_implications.get(date)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
sig { returns(ExportHelper[Models::Tag]) }
|
|
101
|
+
def tags
|
|
102
|
+
get(Types::Tags)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
sig { params(date: T.any(Date, DateTime)).returns(Export[Models::Tag]) }
|
|
106
|
+
def get_tags(date = Date.today)
|
|
107
|
+
tags.get(date)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
sig { returns(ExportHelper[Models::WikiPage]) }
|
|
111
|
+
def wiki_pages
|
|
112
|
+
get(Types::WikiPages)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
sig { params(date: T.any(Date, DateTime)).returns(Export[Models::WikiPage]) }
|
|
116
|
+
def get_wiki_pages(date = Date.today)
|
|
117
|
+
wiki_pages.get(date)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
require("tmpdir")
|
|
5
|
+
require("sorbet-runtime")
|
|
6
|
+
require_relative("version")
|
|
7
|
+
|
|
8
|
+
module E621ExportDownloader
|
|
9
|
+
module Constants
|
|
10
|
+
extend(T::Sig)
|
|
11
|
+
|
|
12
|
+
BASE_URL = "https://e621.net/db_export/"
|
|
13
|
+
EXPORT_NAMES = %w[pools posts tag_aliases tag_implications tags wiki_pages].freeze
|
|
14
|
+
TEMP_DIR = T.let(File.join(Dir.tmpdir, "e621-export-downloader"), String)
|
|
15
|
+
USER_AGENT = T.let("E621ExportDownloader.rb/#{VERSION} (#{WEBSITE})", String)
|
|
16
|
+
end
|
|
17
|
+
end
|