e621_export_downloader 0.0.3 → 0.0.4
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/README.md +42 -47
- data/lib/e621_export_downloader/api_export_data.rb +12 -0
- data/lib/e621_export_downloader/client/options/builder/parsers.rb +22 -6
- data/lib/e621_export_downloader/client/options/builder.rb +10 -10
- data/lib/e621_export_downloader/client/options.rb +15 -8
- data/lib/e621_export_downloader/client.rb +53 -80
- data/lib/e621_export_downloader/constants.rb +2 -1
- data/lib/e621_export_downloader/export.rb +36 -20
- data/lib/e621_export_downloader/export_helper.rb +25 -47
- data/lib/e621_export_downloader/models/artist.rb +77 -0
- data/lib/e621_export_downloader/models/bulk_update_request.rb +66 -0
- data/lib/e621_export_downloader/models/post_replacement.rb +96 -0
- data/lib/e621_export_downloader/models/post_version.rb +120 -0
- data/lib/e621_export_downloader/types.rb +4 -0
- data/lib/e621_export_downloader/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0319ab366f0396847426a4d510dfe49c7a6a5beb49175001b3e4a56fe2e6aa9
|
|
4
|
+
data.tar.gz: f40878e3cb2253774caaaabb83aa0b0aa014ed648782835946b212f24a32a87f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1dbb3fea2fa77735294a73049b9684632e5fb96d1b03891199556d6fb5501999ec1b5e53b0d6a31bcc3ba60399fbba347c81a669faed08c1f59b031325297d1e
|
|
7
|
+
data.tar.gz: 8585b196a8cdb998a07444e4306e293ebde1f2f4ddb2bb08f26043f7d942cce3d1a041d523dccd15c0d9ca51d85902e3b91456e71e47821103754257e2d786c2
|
data/README.md
CHANGED
|
@@ -23,51 +23,39 @@ client = E621ExportDownloader::Client.new
|
|
|
23
23
|
|
|
24
24
|
# configure options after creation
|
|
25
25
|
client.config do |c|
|
|
26
|
-
c.cache = 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
|
|
26
|
+
c.cache = true # keep export files after reading, defaults to false
|
|
30
27
|
end
|
|
31
28
|
|
|
32
29
|
# or pass an Options struct directly
|
|
33
30
|
client = E621ExportDownloader::Client.new(
|
|
34
|
-
E621ExportDownloader::Client::Options.new(
|
|
35
|
-
cache: true,
|
|
36
|
-
rewind_on_not_found: 2,
|
|
37
|
-
)
|
|
31
|
+
E621ExportDownloader::Client::Options.new(cache: true)
|
|
38
32
|
)
|
|
39
33
|
|
|
40
|
-
# get the
|
|
41
|
-
# types: pools, posts, tag_aliases, tag_implications, tags, wiki_pages
|
|
42
|
-
|
|
43
|
-
# or use the named shorthand:
|
|
44
|
-
helper = client.posts
|
|
34
|
+
# get the export for a type — resolves the latest available export from the e621 API
|
|
35
|
+
# types: artists, bulk_update_requests, pools, posts, post_replacements, post_versions, tag_aliases, tag_implications, tags, wiki_pages
|
|
36
|
+
export = client.get("posts")
|
|
45
37
|
|
|
46
|
-
# get a
|
|
47
|
-
|
|
48
|
-
export = helper.get(Date.today)
|
|
38
|
+
# get a deferred export — the API call is made lazily on first use
|
|
39
|
+
deferred = client.get_deferred("posts")
|
|
49
40
|
|
|
50
|
-
#
|
|
51
|
-
|
|
41
|
+
# get raw metadata for all exports from the e621 API
|
|
42
|
+
# returns an Array of E621ExportDownloader::APIExportData: { file_name, file_size, name, updated_at, url }
|
|
43
|
+
data = client.get_data
|
|
52
44
|
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
# finds an export or exhausts the rewind limit, at which point it raises ResolveError
|
|
45
|
+
# access the metadata for this specific export: { file_name, file_size, name, updated_at, url }
|
|
46
|
+
export.data
|
|
56
47
|
|
|
57
|
-
# check
|
|
48
|
+
# check if the export exists
|
|
58
49
|
exists = export.exists?
|
|
59
|
-
exists = helper.exists?(Date.today)
|
|
60
50
|
|
|
61
51
|
# download the export, returns the file path — not required before reading
|
|
62
52
|
# if you move or remove the file do not reuse the export object
|
|
63
53
|
file = export.download
|
|
64
|
-
file = helper.download(Date.today)
|
|
65
54
|
|
|
66
55
|
# delete the downloaded file, if it exists
|
|
67
56
|
export.delete
|
|
68
|
-
helper.delete(Date.today)
|
|
69
57
|
|
|
70
|
-
# get all of the records as a single array, DO NOT use this for large exports, arrays
|
|
58
|
+
# get all of the records as a single array, DO NOT use this for large exports, arrays with millions of items do not perform well and will likely crash your process!
|
|
71
59
|
# (not to mention that the posts export is more than 5 gigabytes)
|
|
72
60
|
records = export.read_all
|
|
73
61
|
|
|
@@ -98,45 +86,52 @@ client.config do |c|
|
|
|
98
86
|
end
|
|
99
87
|
```
|
|
100
88
|
|
|
89
|
+
Available parser keys: `artists`, `bulk_update_requests`, `pools`, `posts`, `post_replacements`, `post_versions`, `tag_aliases`, `tag_implications`, `tags`, `wiki_pages`
|
|
90
|
+
|
|
101
91
|
## Models
|
|
102
92
|
|
|
103
93
|
Each export type is parsed into a corresponding model class:
|
|
104
94
|
|
|
105
|
-
| Export type
|
|
106
|
-
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
95
|
+
| Export type | Model class |
|
|
96
|
+
|------------------------|-------------------------------------------------------|
|
|
97
|
+
| `artists` | `E621ExportDownloader::Models::Artist` |
|
|
98
|
+
| `bulk_update_requests` | `E621ExportDownloader::Models::BulkUpdateRequest` |
|
|
99
|
+
| `pools` | `E621ExportDownloader::Models::Pool` |
|
|
100
|
+
| `posts` | `E621ExportDownloader::Models::Post` |
|
|
101
|
+
| `post_replacements` | `E621ExportDownloader::Models::PostReplacement` |
|
|
102
|
+
| `post_versions` | `E621ExportDownloader::Models::PostVersion` |
|
|
103
|
+
| `tag_aliases` | `E621ExportDownloader::Models::TagAlias` |
|
|
104
|
+
| `tag_implications` | `E621ExportDownloader::Models::TagImplication` |
|
|
105
|
+
| `tags` | `E621ExportDownloader::Models::Tag` |
|
|
106
|
+
| `wiki_pages` | `E621ExportDownloader::Models::WikiPage` |
|
|
113
107
|
|
|
114
108
|
## CLI
|
|
115
109
|
|
|
116
110
|
```bash
|
|
117
|
-
#
|
|
111
|
+
# get export metadata from the e621 API as a JSON array
|
|
112
|
+
# outputs { file_name, file_size, name, updated_at, url }[] with no trailing newline
|
|
113
|
+
e621-export-downloader data
|
|
114
|
+
|
|
115
|
+
# check if an export exists
|
|
118
116
|
# outputs "true" or "false" with no trailing newline
|
|
119
|
-
e621-export-downloader exists posts
|
|
117
|
+
e621-export-downloader exists posts
|
|
120
118
|
|
|
121
|
-
# download an export
|
|
119
|
+
# download an export
|
|
122
120
|
# outputs the path to the downloaded file with no trailing newline
|
|
123
|
-
e621-export-downloader download posts
|
|
121
|
+
e621-export-downloader download posts
|
|
124
122
|
|
|
125
|
-
# read an export as individual JSON lines
|
|
123
|
+
# read an export as individual JSON lines
|
|
126
124
|
# outputs each record as a JSON object on its own line
|
|
127
|
-
e621-export-downloader read posts
|
|
125
|
+
e621-export-downloader read posts
|
|
128
126
|
|
|
129
|
-
# read an export as a JSON array
|
|
127
|
+
# read an export as a JSON array
|
|
130
128
|
# streams the CSV internally, so it is safe to use with large exports
|
|
131
|
-
e621-export-downloader read-all posts
|
|
129
|
+
e621-export-downloader read-all posts
|
|
132
130
|
|
|
133
131
|
e621-export-downloader --help
|
|
134
132
|
e621-export-downloader --version
|
|
135
|
-
e621-export-downloader --cache
|
|
136
|
-
e621-export-downloader --no-cache
|
|
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)
|
|
133
|
+
e621-export-downloader --cache # enable caching
|
|
134
|
+
e621-export-downloader --no-cache # disable caching (default)
|
|
140
135
|
```
|
|
141
136
|
|
|
142
137
|
## ActiveJob Integration
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
module E621ExportDownloader
|
|
5
|
+
class APIExportData < T::Struct
|
|
6
|
+
const(:file_name, String)
|
|
7
|
+
const(:file_size, Integer)
|
|
8
|
+
const(:name, Types)
|
|
9
|
+
const(:updated_at, String)
|
|
10
|
+
const(:url, String)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -8,12 +8,24 @@ module E621ExportDownloader
|
|
|
8
8
|
class Parsers
|
|
9
9
|
extend(T::Sig)
|
|
10
10
|
|
|
11
|
+
sig { returns(Parser) }
|
|
12
|
+
attr_accessor(:artists)
|
|
13
|
+
|
|
14
|
+
sig { returns(Parser) }
|
|
15
|
+
attr_accessor(:bulk_update_requests)
|
|
16
|
+
|
|
11
17
|
sig { returns(Parser) }
|
|
12
18
|
attr_accessor(:pools)
|
|
13
19
|
|
|
14
20
|
sig { returns(Parser) }
|
|
15
21
|
attr_accessor(:posts)
|
|
16
22
|
|
|
23
|
+
sig { returns(Parser) }
|
|
24
|
+
attr_accessor(:post_replacements)
|
|
25
|
+
|
|
26
|
+
sig { returns(Parser) }
|
|
27
|
+
attr_accessor(:post_versions)
|
|
28
|
+
|
|
17
29
|
sig { returns(Parser) }
|
|
18
30
|
attr_accessor(:tag_aliases)
|
|
19
31
|
|
|
@@ -28,12 +40,16 @@ module E621ExportDownloader
|
|
|
28
40
|
|
|
29
41
|
sig { params(defaults: Options::Parsers).void }
|
|
30
42
|
def initialize(defaults = Options::Parsers.defaults)
|
|
31
|
-
@
|
|
32
|
-
@
|
|
33
|
-
@
|
|
34
|
-
@
|
|
35
|
-
@
|
|
36
|
-
@
|
|
43
|
+
@artists = T.let(T.must(defaults.artists), Parser)
|
|
44
|
+
@bulk_update_requests = T.let(T.must(defaults.bulk_update_requests), Parser)
|
|
45
|
+
@pools = T.let(T.must(defaults.pools), Parser)
|
|
46
|
+
@posts = T.let(T.must(defaults.posts), Parser)
|
|
47
|
+
@post_replacements = T.let(T.must(defaults.post_replacements), Parser)
|
|
48
|
+
@post_versions = T.let(T.must(defaults.post_versions), Parser)
|
|
49
|
+
@tag_aliases = T.let(T.must(defaults.tag_aliases), Parser)
|
|
50
|
+
@tag_implications = T.let(T.must(defaults.tag_implications), Parser)
|
|
51
|
+
@tags = T.let(T.must(defaults.tags), Parser)
|
|
52
|
+
@wiki_pages = T.let(T.must(defaults.wiki_pages), Parser)
|
|
37
53
|
end
|
|
38
54
|
end
|
|
39
55
|
end
|
|
@@ -10,16 +10,12 @@ module E621ExportDownloader
|
|
|
10
10
|
sig { returns(T::Boolean) }
|
|
11
11
|
attr_accessor(:cache)
|
|
12
12
|
|
|
13
|
-
sig { returns(T.any(T::Boolean, Integer)) }
|
|
14
|
-
attr_accessor(:rewind_on_not_found)
|
|
15
|
-
|
|
16
13
|
sig { params(parsers: Options::Parsers).returns(Options::Parsers) }
|
|
17
14
|
attr_writer(:parsers)
|
|
18
15
|
|
|
19
16
|
sig { params(defaults: Options).void }
|
|
20
17
|
def initialize(defaults = Options.new)
|
|
21
18
|
@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
19
|
@parsers = T.let(defaults.parsers, Options::Parsers)
|
|
24
20
|
end
|
|
25
21
|
|
|
@@ -30,12 +26,16 @@ module E621ExportDownloader
|
|
|
30
26
|
block.call(parsers)
|
|
31
27
|
|
|
32
28
|
@parsers = Options::Parsers.new(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
artists: parsers.artists,
|
|
30
|
+
bulk_update_requests: parsers.bulk_update_requests,
|
|
31
|
+
pools: parsers.pools,
|
|
32
|
+
posts: parsers.posts,
|
|
33
|
+
post_replacements: parsers.post_replacements,
|
|
34
|
+
post_versions: parsers.post_versions,
|
|
35
|
+
tag_aliases: parsers.tag_aliases,
|
|
36
|
+
tag_implications: parsers.tag_implications,
|
|
37
|
+
tags: parsers.tags,
|
|
38
|
+
wiki_pages: parsers.wiki_pages,
|
|
39
39
|
)
|
|
40
40
|
end
|
|
41
41
|
end
|
|
@@ -9,8 +9,12 @@ module E621ExportDownloader
|
|
|
9
9
|
class Parsers < T::Struct
|
|
10
10
|
extend(T::Sig)
|
|
11
11
|
|
|
12
|
+
const(:artists, T.nilable(Parser), default: nil)
|
|
13
|
+
const(:bulk_update_requests, T.nilable(Parser), default: nil)
|
|
12
14
|
const(:pools, T.nilable(Parser), default: nil)
|
|
13
15
|
const(:posts, T.nilable(Parser), default: nil)
|
|
16
|
+
const(:post_replacements, T.nilable(Parser), default: nil)
|
|
17
|
+
const(:post_versions, T.nilable(Parser), default: nil)
|
|
14
18
|
const(:tag_aliases, T.nilable(Parser), default: nil)
|
|
15
19
|
const(:tag_implications, T.nilable(Parser), default: nil)
|
|
16
20
|
const(:tags, T.nilable(Parser), default: nil)
|
|
@@ -19,18 +23,21 @@ module E621ExportDownloader
|
|
|
19
23
|
sig { returns(Parsers) }
|
|
20
24
|
def self.defaults
|
|
21
25
|
Parsers.new(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
artists: ->(record) { Models::Artist.new(record) },
|
|
27
|
+
bulk_update_requests: ->(record) { Models::BulkUpdateRequest.new(record) },
|
|
28
|
+
pools: ->(record) { Models::Pool.new(record) },
|
|
29
|
+
posts: ->(record) { Models::Post.new(record) },
|
|
30
|
+
post_replacements: ->(record) { Models::PostReplacement.new(record) },
|
|
31
|
+
post_versions: ->(record) { Models::PostVersion.new(record) },
|
|
32
|
+
tag_aliases: ->(record) { Models::TagAlias.new(record) },
|
|
33
|
+
tag_implications: ->(record) { Models::TagImplication.new(record) },
|
|
34
|
+
tags: ->(record) { Models::Tag.new(record) },
|
|
35
|
+
wiki_pages: ->(record) { Models::WikiPage.new(record) },
|
|
28
36
|
)
|
|
29
37
|
end
|
|
30
38
|
end
|
|
31
39
|
|
|
32
|
-
const(:cache, T::Boolean, default:
|
|
33
|
-
const(:rewind_on_not_found, T.any(T::Boolean, Integer), default: false)
|
|
40
|
+
const(:cache, T::Boolean, default: false)
|
|
34
41
|
const(:parsers, Parsers, default: Parsers.defaults)
|
|
35
42
|
end
|
|
36
43
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# typed: strict
|
|
3
3
|
|
|
4
4
|
require("faraday")
|
|
5
|
+
require("json")
|
|
5
6
|
|
|
6
7
|
module E621ExportDownloader
|
|
7
8
|
class Client
|
|
@@ -15,6 +16,7 @@ module E621ExportDownloader
|
|
|
15
16
|
sig { params(options: T.nilable(Options)).void }
|
|
16
17
|
def initialize(options = nil)
|
|
17
18
|
@options = T.let(options || Options.new, Options)
|
|
19
|
+
@export_cache = T.let(nil, T.nilable(T::Array[APIExportData]))
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
sig { params(block: T.proc.params(arg0: Options::Builder).void).void }
|
|
@@ -24,7 +26,7 @@ module E621ExportDownloader
|
|
|
24
26
|
|
|
25
27
|
sig { returns(Faraday::Connection) }
|
|
26
28
|
def connection
|
|
27
|
-
Faraday.new(
|
|
29
|
+
Faraday.new(headers: { "User-Agent" => Constants::USER_AGENT })
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
sig { params(msg: String, header: T::Array[String]).void }
|
|
@@ -32,89 +34,60 @@ module E621ExportDownloader
|
|
|
32
34
|
Logger.debug("[e621_export_downloader#{":#{header.join(':')}" unless header.empty?}] #{msg}")
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
sig { params(type: T.any(Types, String)).returns(
|
|
37
|
+
sig { params(type: T.any(Types, String)).returns(Export[T.untyped]) }
|
|
36
38
|
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
|
-
|
|
39
|
+
type = string_to_type(type) if type.is_a?(String)
|
|
56
40
|
T.assert_type!(type, Types)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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)
|
|
41
|
+
data = get_data.find { |d| d.name == type }
|
|
42
|
+
raise(ResolveError, "Export data for \"#{type.serialize}\" not found") if data.nil?
|
|
43
|
+
debug("creating export for #{type.serialize}")
|
|
44
|
+
Export.new(data: data, client: self, type: type, parser: options.parsers.public_send(type.serialize))
|
|
103
45
|
end
|
|
104
46
|
|
|
105
|
-
sig { params(
|
|
106
|
-
def
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
47
|
+
sig { params(type: T.any(Types, String)).returns(ExportHelper[T.untyped]) }
|
|
48
|
+
def get_deferred(type)
|
|
49
|
+
type = string_to_type(type) if type.is_a?(String)
|
|
50
|
+
T.assert_type!(type, Types)
|
|
51
|
+
debug("creating deferred export for #{type.serialize}")
|
|
52
|
+
ExportHelper.new(type: type, client: self)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
sig { returns(T::Array[APIExportData]) }
|
|
56
|
+
def get_data
|
|
57
|
+
return @export_cache unless @export_cache.nil?
|
|
58
|
+
debug("fetching export data from api")
|
|
59
|
+
res = connection.get(Constants::API_URL)
|
|
60
|
+
raise(ResolveError, "Failed to fetch exports: #{res.status} #{res.reason_phrase}") unless res.success?
|
|
61
|
+
data = JSON.parse(T.unsafe(res).body)
|
|
62
|
+
result = T.let(data.map do |d|
|
|
63
|
+
APIExportData.new(
|
|
64
|
+
file_name: d["file_name"],
|
|
65
|
+
file_size: d["file_size"].to_i,
|
|
66
|
+
name: Types.deserialize(d["name"]),
|
|
67
|
+
updated_at: d["updated_at"],
|
|
68
|
+
url: d["url"],
|
|
69
|
+
)
|
|
70
|
+
end, T::Array[APIExportData])
|
|
71
|
+
@export_cache = result
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
sig { params(type: String).returns(Types) }
|
|
77
|
+
def string_to_type(type)
|
|
78
|
+
case type
|
|
79
|
+
when "artists" then Types::Artists
|
|
80
|
+
when "bulk_update_requests" then Types::BulkUpdateRequests
|
|
81
|
+
when "pools" then Types::Pools
|
|
82
|
+
when "posts" then Types::Posts
|
|
83
|
+
when "post_replacements" then Types::PostReplacements
|
|
84
|
+
when "post_versions" then Types::PostVersions
|
|
85
|
+
when "tag_aliases" then Types::TagAliases
|
|
86
|
+
when "tag_implications" then Types::TagImplications
|
|
87
|
+
when "tags" then Types::Tags
|
|
88
|
+
when "wiki_pages" then Types::WikiPages
|
|
89
|
+
else raise(ArgumentError, "invalid type: #{type}")
|
|
90
|
+
end
|
|
118
91
|
end
|
|
119
92
|
end
|
|
120
93
|
end
|
|
@@ -9,8 +9,9 @@ module E621ExportDownloader
|
|
|
9
9
|
module Constants
|
|
10
10
|
extend(T::Sig)
|
|
11
11
|
|
|
12
|
+
API_URL = "https://e621.net/db_exports.json"
|
|
12
13
|
BASE_URL = "https://e621.net/db_export/"
|
|
13
|
-
EXPORT_NAMES = %w[pools posts tag_aliases tag_implications tags wiki_pages].freeze
|
|
14
|
+
EXPORT_NAMES = %w[artists bulk_update_requests pools posts post_replacements post_versions tag_aliases tag_implications tags wiki_pages].freeze
|
|
14
15
|
TEMP_DIR = T.let(File.join(Dir.tmpdir, "e621-export-downloader"), String)
|
|
15
16
|
USER_AGENT = T.let("E621ExportDownloader.rb/#{VERSION} (#{WEBSITE})", String)
|
|
16
17
|
end
|
|
@@ -10,26 +10,31 @@ module E621ExportDownloader
|
|
|
10
10
|
|
|
11
11
|
Model = type_member
|
|
12
12
|
|
|
13
|
-
sig { returns(
|
|
14
|
-
attr_accessor(:
|
|
13
|
+
sig { returns(APIExportData) }
|
|
14
|
+
attr_accessor(:data)
|
|
15
15
|
|
|
16
|
-
sig { returns(
|
|
17
|
-
attr_accessor(:
|
|
16
|
+
sig { returns(Client) }
|
|
17
|
+
attr_accessor(:client)
|
|
18
|
+
|
|
19
|
+
sig { returns(Types) }
|
|
20
|
+
attr_accessor(:type)
|
|
18
21
|
|
|
19
22
|
sig { returns(T.nilable(T::Boolean)) }
|
|
20
|
-
# If nil, no check has been performed yet
|
|
21
23
|
attr_accessor(:downloaded)
|
|
22
24
|
|
|
23
|
-
sig { params(
|
|
24
|
-
def initialize(
|
|
25
|
-
@
|
|
26
|
-
@
|
|
25
|
+
sig { params(data: APIExportData, client: Client, type: Types, parser: Client::Options::Parser).void }
|
|
26
|
+
def initialize(data:, client:, type:, parser:)
|
|
27
|
+
@data = T.let(data, APIExportData)
|
|
28
|
+
@client = T.let(client, Client)
|
|
29
|
+
@type = T.let(type, Types)
|
|
30
|
+
@parser = T.let(parser, Client::Options::Parser)
|
|
27
31
|
@downloaded = T.let(nil, T.nilable(T::Boolean))
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
sig { returns(T::Boolean) }
|
|
31
35
|
def delete
|
|
32
36
|
return false unless check_downloaded
|
|
37
|
+
client.debug("deleting cached export", header: ["export:#{type.serialize}"])
|
|
33
38
|
FileUtils.rm(file_path)
|
|
34
39
|
@downloaded = false
|
|
35
40
|
true
|
|
@@ -37,19 +42,19 @@ module E621ExportDownloader
|
|
|
37
42
|
|
|
38
43
|
sig { returns(String) }
|
|
39
44
|
def download
|
|
40
|
-
raise(ResolveError, "Export #{
|
|
45
|
+
raise(ResolveError, "Export #{type.serialize} does not exist") unless exists?
|
|
41
46
|
if check_downloaded
|
|
42
|
-
|
|
47
|
+
client.debug("using cached export", header: ["export:#{type.serialize}"])
|
|
43
48
|
return file_path
|
|
44
49
|
end
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
client.debug("downloading export", header: ["export:#{type.serialize}"])
|
|
47
52
|
|
|
48
53
|
FileUtils.mkdir_p(Constants::TEMP_DIR)
|
|
49
54
|
File.open(file_path, "wb") do |file|
|
|
50
55
|
inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
|
|
51
56
|
|
|
52
|
-
res =
|
|
57
|
+
res = client.connection.get(data.url) do |req|
|
|
53
58
|
req.options.on_data = proc do |chunk, _total|
|
|
54
59
|
decompressed = inflater.inflate(chunk)
|
|
55
60
|
file.write(decompressed) if decompressed && !decompressed.empty?
|
|
@@ -61,7 +66,7 @@ module E621ExportDownloader
|
|
|
61
66
|
file.close
|
|
62
67
|
|
|
63
68
|
unless res.success?
|
|
64
|
-
raise(ResolveError, "Failed to download export #{
|
|
69
|
+
raise(ResolveError, "Failed to download export #{type.serialize}: #{res.status} #{res.reason_phrase}")
|
|
65
70
|
end
|
|
66
71
|
rescue # rubocop:disable Style/RescueStandardError
|
|
67
72
|
FileUtils.rm_f(file_path)
|
|
@@ -69,30 +74,34 @@ module E621ExportDownloader
|
|
|
69
74
|
end
|
|
70
75
|
|
|
71
76
|
@downloaded = true
|
|
77
|
+
client.debug("download complete: #{file_path}", header: ["export:#{type.serialize}"])
|
|
72
78
|
file_path
|
|
73
79
|
end
|
|
74
80
|
|
|
75
81
|
sig { returns(T::Boolean) }
|
|
76
82
|
def exists?
|
|
77
|
-
|
|
83
|
+
result = client.connection.head(data.url).success?
|
|
84
|
+
client.debug("checked export existence: #{result}", header: ["export:#{type.serialize}"])
|
|
85
|
+
result
|
|
78
86
|
end
|
|
79
87
|
|
|
80
88
|
sig { params(block: T.proc.params(arg0: Model, arg1: Integer).void).void }
|
|
81
89
|
def read(&block)
|
|
82
90
|
download unless check_downloaded
|
|
83
|
-
|
|
91
|
+
client.debug("reading export", header: ["export:#{type.serialize}"])
|
|
84
92
|
total = line_count
|
|
85
93
|
CSV.foreach(file_path, headers: true) do |row|
|
|
86
|
-
args = [
|
|
94
|
+
args = [@parser.call(T.cast(row, CSV::Row).to_hash), total]
|
|
87
95
|
args = args.slice(0, block.arity) if block.arity != -1
|
|
88
96
|
block.call(*T.unsafe(args))
|
|
89
97
|
end
|
|
98
|
+
client.debug("finished reading export", header: ["export:#{type.serialize}"])
|
|
99
|
+
delete unless client.options.cache
|
|
90
100
|
end
|
|
91
101
|
|
|
92
102
|
sig { returns(T::Array[Model]) }
|
|
93
103
|
def read_all
|
|
94
|
-
|
|
95
|
-
helper.client.debug("reading all records for #{helper.type.serialize}", header: %W[export #{helper.format_date(date)}])
|
|
104
|
+
client.debug("reading all records", header: ["export:#{type.serialize}"])
|
|
96
105
|
results = []
|
|
97
106
|
read do |record|
|
|
98
107
|
results << record
|
|
@@ -102,6 +111,11 @@ module E621ExportDownloader
|
|
|
102
111
|
|
|
103
112
|
private
|
|
104
113
|
|
|
114
|
+
sig { returns(String) }
|
|
115
|
+
def format_date
|
|
116
|
+
DateTime.parse(data.updated_at).strftime("%Y-%m-%d")
|
|
117
|
+
end
|
|
118
|
+
|
|
105
119
|
sig { returns(Integer) }
|
|
106
120
|
def line_count
|
|
107
121
|
total = 0
|
|
@@ -113,6 +127,8 @@ module E621ExportDownloader
|
|
|
113
127
|
def check_downloaded
|
|
114
128
|
return @downloaded unless @downloaded.nil?
|
|
115
129
|
@downloaded = File.exist?(file_path)
|
|
130
|
+
client.debug("checked downloaded state: #{@downloaded}", header: ["export:#{type.serialize}"])
|
|
131
|
+
@downloaded
|
|
116
132
|
end
|
|
117
133
|
|
|
118
134
|
sig { returns(String) }
|
|
@@ -122,7 +138,7 @@ module E621ExportDownloader
|
|
|
122
138
|
|
|
123
139
|
sig { returns(String) }
|
|
124
140
|
def file_name
|
|
125
|
-
"#{
|
|
141
|
+
"#{type.serialize}-#{format_date}.csv"
|
|
126
142
|
end
|
|
127
143
|
end
|
|
128
144
|
end
|
|
@@ -11,73 +11,51 @@ module E621ExportDownloader
|
|
|
11
11
|
sig { returns(Types) }
|
|
12
12
|
attr_reader(:type)
|
|
13
13
|
|
|
14
|
-
sig { returns(Client::Options::Parser) }
|
|
15
|
-
attr_reader(:parser)
|
|
16
|
-
|
|
17
14
|
sig { returns(Client) }
|
|
18
15
|
attr_reader(:client)
|
|
19
16
|
|
|
20
|
-
sig { params(type: Types,
|
|
21
|
-
def initialize(type:,
|
|
17
|
+
sig { params(type: Types, client: Client).void }
|
|
18
|
+
def initialize(type:, client:)
|
|
22
19
|
@type = type
|
|
23
|
-
@parser = parser
|
|
24
20
|
@client = client
|
|
25
|
-
@
|
|
21
|
+
@export = T.let(nil, T.nilable(Export[Model]))
|
|
26
22
|
end
|
|
27
23
|
|
|
28
|
-
sig {
|
|
29
|
-
def
|
|
30
|
-
|
|
24
|
+
sig { returns(T::Boolean) }
|
|
25
|
+
def delete
|
|
26
|
+
_get.delete
|
|
31
27
|
end
|
|
32
28
|
|
|
33
|
-
sig {
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
_get(date).delete
|
|
29
|
+
sig { returns(String) }
|
|
30
|
+
def download
|
|
31
|
+
_get.download
|
|
37
32
|
end
|
|
38
33
|
|
|
39
|
-
sig {
|
|
40
|
-
def
|
|
41
|
-
|
|
42
|
-
_get(date).download
|
|
34
|
+
sig { returns(T::Boolean) }
|
|
35
|
+
def exists?
|
|
36
|
+
_get.exists?
|
|
43
37
|
end
|
|
44
38
|
|
|
45
|
-
sig {
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
_get(date).exists?
|
|
39
|
+
sig { returns(Export[Model]) }
|
|
40
|
+
def get
|
|
41
|
+
_get
|
|
49
42
|
end
|
|
50
43
|
|
|
51
|
-
sig { params(
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
Export.new(date: date, helper: self)
|
|
44
|
+
sig { params(block: T.proc.params(arg0: Model, arg1: Integer).void).void }
|
|
45
|
+
def read(&block)
|
|
46
|
+
_get.read(&block)
|
|
55
47
|
end
|
|
56
48
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def max_rewind_count
|
|
61
|
-
return 0 if client.options.rewind_on_not_found == false
|
|
62
|
-
return 2 if client.options.rewind_on_not_found == true
|
|
63
|
-
T.cast(client.options.rewind_on_not_found, Integer)
|
|
49
|
+
sig { returns(T::Array[Model]) }
|
|
50
|
+
def read_all
|
|
51
|
+
_get.read_all
|
|
64
52
|
end
|
|
65
53
|
|
|
66
|
-
|
|
67
|
-
def _get(date, original_date = date)
|
|
68
|
-
export = get(date)
|
|
69
|
-
if export.exists?
|
|
70
|
-
client.debug("resolved export for #{type.serialize}", header: %w[helper])
|
|
71
|
-
return export
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
if @rewind_count < max_rewind_count
|
|
75
|
-
@rewind_count += 1
|
|
76
|
-
client.debug("rewinding export lookup for #{type.serialize} from #{format_date(original_date)} (attempt #{@rewind_count}/#{max_rewind_count})", header: %w[helper])
|
|
77
|
-
return _get(date.to_date + 1, original_date)
|
|
78
|
-
end
|
|
54
|
+
private
|
|
79
55
|
|
|
80
|
-
|
|
56
|
+
sig { returns(Export[Model]) }
|
|
57
|
+
def _get
|
|
58
|
+
@export ||= client.get(type)
|
|
81
59
|
end
|
|
82
60
|
end
|
|
83
61
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
module E621ExportDownloader
|
|
5
|
+
module Models
|
|
6
|
+
class Artist
|
|
7
|
+
extend(T::Sig)
|
|
8
|
+
|
|
9
|
+
sig { returns(DateTime) }
|
|
10
|
+
attr_reader(:created_at)
|
|
11
|
+
|
|
12
|
+
sig { returns(Integer) }
|
|
13
|
+
attr_reader(:creator_id)
|
|
14
|
+
|
|
15
|
+
sig { returns(T.nilable(String)) }
|
|
16
|
+
attr_reader(:group_name)
|
|
17
|
+
|
|
18
|
+
sig { returns(Integer) }
|
|
19
|
+
attr_reader(:id)
|
|
20
|
+
|
|
21
|
+
sig { returns(T::Boolean) }
|
|
22
|
+
attr_reader(:is_active)
|
|
23
|
+
|
|
24
|
+
sig { returns(T::Boolean) }
|
|
25
|
+
attr_reader(:is_locked)
|
|
26
|
+
|
|
27
|
+
sig { returns(T.nilable(Integer)) }
|
|
28
|
+
attr_reader(:linked_user_id)
|
|
29
|
+
|
|
30
|
+
sig { returns(String) }
|
|
31
|
+
attr_reader(:name)
|
|
32
|
+
|
|
33
|
+
sig { returns(T::Array[String]) }
|
|
34
|
+
attr_reader(:other_names)
|
|
35
|
+
|
|
36
|
+
sig { returns(DateTime) }
|
|
37
|
+
attr_reader(:updated_at)
|
|
38
|
+
|
|
39
|
+
sig { returns(T::Array[String]) }
|
|
40
|
+
attr_reader(:urls)
|
|
41
|
+
|
|
42
|
+
sig { params(record: T::Hash[String, String]).void }
|
|
43
|
+
def initialize(record)
|
|
44
|
+
@record = T.let(record, T::Hash[String, String])
|
|
45
|
+
@created_at = T.let(DateTime.parse(record["created_at"]), DateTime)
|
|
46
|
+
@creator_id = T.let(record["creator_id"].to_i, Integer)
|
|
47
|
+
@group_name = T.let(T.must(record["group_name"]).empty? ? nil : record["group_name"], T.nilable(String))
|
|
48
|
+
@id = T.let(record["id"].to_i, Integer)
|
|
49
|
+
@is_active = T.let(record["is_active"] == "t", T::Boolean)
|
|
50
|
+
@is_locked = T.let(record["is_locked"] == "t", T::Boolean)
|
|
51
|
+
@linked_user_id = T.let(T.must(record["linked_user_id"]).empty? ? nil : record["linked_user_id"].to_i, T.nilable(Integer))
|
|
52
|
+
@name = T.let(T.must(record["name"]), String)
|
|
53
|
+
inner = T.must(T.must(record["other_names"])[1..-2])
|
|
54
|
+
@other_names = T.let(inner.empty? ? [] : inner.split(","), T::Array[String])
|
|
55
|
+
@updated_at = T.let(DateTime.parse(record["updated_at"]), DateTime)
|
|
56
|
+
@urls = T.let(T.must(record["urls"]).split("\n"), T::Array[String])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
sig { params(_args: T.untyped).returns(String) }
|
|
60
|
+
def to_json(*_args)
|
|
61
|
+
{
|
|
62
|
+
created_at: @created_at,
|
|
63
|
+
creator_id: @creator_id,
|
|
64
|
+
group_name: @group_name,
|
|
65
|
+
id: @id,
|
|
66
|
+
is_active: @is_active,
|
|
67
|
+
is_locked: @is_locked,
|
|
68
|
+
linked_user_id: @linked_user_id,
|
|
69
|
+
name: @name,
|
|
70
|
+
other_names: @other_names,
|
|
71
|
+
updated_at: @updated_at,
|
|
72
|
+
urls: @urls,
|
|
73
|
+
}.to_json
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
module E621ExportDownloader
|
|
5
|
+
module Models
|
|
6
|
+
class BulkUpdateRequest
|
|
7
|
+
extend(T::Sig)
|
|
8
|
+
|
|
9
|
+
sig { returns(T.nilable(Integer)) }
|
|
10
|
+
attr_reader(:approver_id)
|
|
11
|
+
|
|
12
|
+
sig { returns(DateTime) }
|
|
13
|
+
attr_reader(:created_at)
|
|
14
|
+
|
|
15
|
+
sig { returns(T.nilable(Integer)) }
|
|
16
|
+
attr_reader(:forum_topic_id)
|
|
17
|
+
|
|
18
|
+
sig { returns(Integer) }
|
|
19
|
+
attr_reader(:id)
|
|
20
|
+
|
|
21
|
+
sig { returns(String) }
|
|
22
|
+
attr_reader(:script)
|
|
23
|
+
|
|
24
|
+
sig { returns(String) }
|
|
25
|
+
attr_reader(:status)
|
|
26
|
+
|
|
27
|
+
sig { returns(T.nilable(String)) }
|
|
28
|
+
attr_reader(:title)
|
|
29
|
+
|
|
30
|
+
sig { returns(DateTime) }
|
|
31
|
+
attr_reader(:updated_at)
|
|
32
|
+
|
|
33
|
+
sig { returns(Integer) }
|
|
34
|
+
attr_reader(:user_id)
|
|
35
|
+
|
|
36
|
+
sig { params(record: T::Hash[String, String]).void }
|
|
37
|
+
def initialize(record)
|
|
38
|
+
@record = T.let(record, T::Hash[String, String])
|
|
39
|
+
@approver_id = T.let(T.must(record["approver_id"]).empty? ? nil : record["approver_id"].to_i, T.nilable(Integer))
|
|
40
|
+
@created_at = T.let(DateTime.parse(record["created_at"]), DateTime)
|
|
41
|
+
@forum_topic_id = T.let(T.must(record["forum_topic_id"]).empty? ? nil : record["forum_topic_id"].to_i, T.nilable(Integer))
|
|
42
|
+
@id = T.let(record["id"].to_i, Integer)
|
|
43
|
+
@script = T.let(T.must(record["script"]), String)
|
|
44
|
+
@status = T.let(T.must(record["status"]), String)
|
|
45
|
+
@title = T.let(T.must(record["title"]).empty? ? nil : record["title"], T.nilable(String))
|
|
46
|
+
@updated_at = T.let(DateTime.parse(record["updated_at"]), DateTime)
|
|
47
|
+
@user_id = T.let(record["user_id"].to_i, Integer)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
sig { params(_args: T.untyped).returns(String) }
|
|
51
|
+
def to_json(*_args)
|
|
52
|
+
{
|
|
53
|
+
approver_id: @approver_id,
|
|
54
|
+
created_at: @created_at,
|
|
55
|
+
forum_topic_id: @forum_topic_id,
|
|
56
|
+
id: @id,
|
|
57
|
+
script: @script,
|
|
58
|
+
status: @status,
|
|
59
|
+
title: @title,
|
|
60
|
+
updated_at: @updated_at,
|
|
61
|
+
user_id: @user_id,
|
|
62
|
+
}.to_json
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
module E621ExportDownloader
|
|
5
|
+
module Models
|
|
6
|
+
class PostReplacement
|
|
7
|
+
extend(T::Sig)
|
|
8
|
+
|
|
9
|
+
sig { returns(T.nilable(Integer)) }
|
|
10
|
+
attr_reader(:approver_id)
|
|
11
|
+
|
|
12
|
+
sig { returns(DateTime) }
|
|
13
|
+
attr_reader(:created_at)
|
|
14
|
+
|
|
15
|
+
sig { returns(Integer) }
|
|
16
|
+
attr_reader(:creator_id)
|
|
17
|
+
|
|
18
|
+
sig { returns(String) }
|
|
19
|
+
attr_reader(:file_ext)
|
|
20
|
+
|
|
21
|
+
sig { returns(String) }
|
|
22
|
+
attr_reader(:file_name)
|
|
23
|
+
|
|
24
|
+
sig { returns(Integer) }
|
|
25
|
+
attr_reader(:file_size)
|
|
26
|
+
|
|
27
|
+
sig { returns(Integer) }
|
|
28
|
+
attr_reader(:id)
|
|
29
|
+
|
|
30
|
+
sig { returns(Integer) }
|
|
31
|
+
attr_reader(:image_height)
|
|
32
|
+
|
|
33
|
+
sig { returns(Integer) }
|
|
34
|
+
attr_reader(:image_width)
|
|
35
|
+
|
|
36
|
+
sig { returns(String) }
|
|
37
|
+
attr_reader(:md5)
|
|
38
|
+
|
|
39
|
+
sig { returns(Integer) }
|
|
40
|
+
attr_reader(:post_id)
|
|
41
|
+
|
|
42
|
+
sig { returns(String) }
|
|
43
|
+
attr_reader(:reason)
|
|
44
|
+
|
|
45
|
+
sig { returns(T.nilable(String)) }
|
|
46
|
+
attr_reader(:source)
|
|
47
|
+
|
|
48
|
+
sig { returns(String) }
|
|
49
|
+
attr_reader(:status)
|
|
50
|
+
|
|
51
|
+
sig { returns(DateTime) }
|
|
52
|
+
attr_reader(:updated_at)
|
|
53
|
+
|
|
54
|
+
sig { params(record: T::Hash[String, String]).void }
|
|
55
|
+
def initialize(record)
|
|
56
|
+
@record = T.let(record, T::Hash[String, String])
|
|
57
|
+
@approver_id = T.let(T.must(record["approver_id"]).empty? ? nil : record["approver_id"].to_i, T.nilable(Integer))
|
|
58
|
+
@created_at = T.let(DateTime.parse(record["created_at"]), DateTime)
|
|
59
|
+
@creator_id = T.let(record["creator_id"].to_i, Integer)
|
|
60
|
+
@file_ext = T.let(T.must(record["file_ext"]), String)
|
|
61
|
+
@file_name = T.let(T.must(record["file_name"]), String)
|
|
62
|
+
@file_size = T.let(record["file_size"].to_i, Integer)
|
|
63
|
+
@id = T.let(record["id"].to_i, Integer)
|
|
64
|
+
@image_height = T.let(record["image_height"].to_i, Integer)
|
|
65
|
+
@image_width = T.let(record["image_width"].to_i, Integer)
|
|
66
|
+
@md5 = T.let(T.must(record["md5"]), String)
|
|
67
|
+
@post_id = T.let(record["post_id"].to_i, Integer)
|
|
68
|
+
@reason = T.let(T.must(record["reason"]), String)
|
|
69
|
+
@source = T.let(T.must(record["source"]).empty? ? nil : record["source"], T.nilable(String))
|
|
70
|
+
@status = T.let(T.must(record["status"]), String)
|
|
71
|
+
@updated_at = T.let(DateTime.parse(record["updated_at"]), DateTime)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
sig { params(_args: T.untyped).returns(String) }
|
|
75
|
+
def to_json(*_args)
|
|
76
|
+
{
|
|
77
|
+
approver_id: @approver_id,
|
|
78
|
+
created_at: @created_at,
|
|
79
|
+
creator_id: @creator_id,
|
|
80
|
+
file_ext: @file_ext,
|
|
81
|
+
file_name: @file_name,
|
|
82
|
+
file_size: @file_size,
|
|
83
|
+
id: @id,
|
|
84
|
+
image_height: @image_height,
|
|
85
|
+
image_width: @image_width,
|
|
86
|
+
md5: @md5,
|
|
87
|
+
post_id: @post_id,
|
|
88
|
+
reason: @reason,
|
|
89
|
+
source: @source,
|
|
90
|
+
status: @status,
|
|
91
|
+
updated_at: @updated_at,
|
|
92
|
+
}.to_json
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
module E621ExportDownloader
|
|
5
|
+
module Models
|
|
6
|
+
class PostVersion
|
|
7
|
+
extend(T::Sig)
|
|
8
|
+
|
|
9
|
+
sig { returns(T::Array[String]) }
|
|
10
|
+
attr_reader(:added_locked_tags)
|
|
11
|
+
|
|
12
|
+
sig { returns(T::Array[String]) }
|
|
13
|
+
attr_reader(:added_tags)
|
|
14
|
+
|
|
15
|
+
sig { returns(T.nilable(String)) }
|
|
16
|
+
attr_reader(:description)
|
|
17
|
+
|
|
18
|
+
sig { returns(T::Boolean) }
|
|
19
|
+
attr_reader(:description_changed)
|
|
20
|
+
|
|
21
|
+
sig { returns(Integer) }
|
|
22
|
+
attr_reader(:id)
|
|
23
|
+
|
|
24
|
+
sig { returns(T.nilable(String)) }
|
|
25
|
+
attr_reader(:locked_tags)
|
|
26
|
+
|
|
27
|
+
sig { returns(T::Boolean) }
|
|
28
|
+
attr_reader(:parent_changed)
|
|
29
|
+
|
|
30
|
+
sig { returns(T.nilable(Integer)) }
|
|
31
|
+
attr_reader(:parent_id)
|
|
32
|
+
|
|
33
|
+
sig { returns(T.nilable(String)) }
|
|
34
|
+
attr_reader(:rating)
|
|
35
|
+
|
|
36
|
+
sig { returns(T::Boolean) }
|
|
37
|
+
attr_reader(:rating_changed)
|
|
38
|
+
|
|
39
|
+
sig { returns(T.nilable(String)) }
|
|
40
|
+
attr_reader(:reason)
|
|
41
|
+
|
|
42
|
+
sig { returns(T::Array[String]) }
|
|
43
|
+
attr_reader(:removed_locked_tags)
|
|
44
|
+
|
|
45
|
+
sig { returns(T::Array[String]) }
|
|
46
|
+
attr_reader(:removed_tags)
|
|
47
|
+
|
|
48
|
+
sig { returns(T.nilable(String)) }
|
|
49
|
+
attr_reader(:source)
|
|
50
|
+
|
|
51
|
+
sig { returns(T::Boolean) }
|
|
52
|
+
attr_reader(:source_changed)
|
|
53
|
+
|
|
54
|
+
sig { returns(T.nilable(String)) }
|
|
55
|
+
attr_reader(:tags)
|
|
56
|
+
|
|
57
|
+
sig { returns(DateTime) }
|
|
58
|
+
attr_reader(:updated_at)
|
|
59
|
+
|
|
60
|
+
sig { returns(Integer) }
|
|
61
|
+
attr_reader(:updater_id)
|
|
62
|
+
|
|
63
|
+
sig { returns(Integer) }
|
|
64
|
+
attr_reader(:version)
|
|
65
|
+
|
|
66
|
+
sig { params(record: T::Hash[String, String]).void }
|
|
67
|
+
def initialize(record)
|
|
68
|
+
@record = T.let(record, T::Hash[String, String])
|
|
69
|
+
inner = T.must(T.must(record["added_locked_tags"])[1..-2])
|
|
70
|
+
@added_locked_tags = T.let(inner.empty? ? [] : inner.split(","), T::Array[String])
|
|
71
|
+
inner = T.must(T.must(record["added_tags"])[1..-2])
|
|
72
|
+
@added_tags = T.let(inner.empty? ? [] : inner.split(","), T::Array[String])
|
|
73
|
+
@description = T.let(T.must(record["description"]).empty? ? nil : record["description"], T.nilable(String))
|
|
74
|
+
@description_changed = T.let(record["description_changed"] == "t", T::Boolean)
|
|
75
|
+
@id = T.let(record["id"].to_i, Integer)
|
|
76
|
+
@locked_tags = T.let(T.must(record["locked_tags"]).empty? ? nil : record["locked_tags"], T.nilable(String))
|
|
77
|
+
@parent_changed = T.let(record["parent_changed"] == "t", T::Boolean)
|
|
78
|
+
@parent_id = T.let(T.must(record["parent_id"]).empty? ? nil : record["parent_id"].to_i, T.nilable(Integer))
|
|
79
|
+
@rating = T.let(T.must(record["rating"]).empty? ? nil : record["rating"], T.nilable(String))
|
|
80
|
+
@rating_changed = T.let(record["rating_changed"] == "t", T::Boolean)
|
|
81
|
+
@reason = T.let(T.must(record["reason"]).empty? ? nil : record["reason"], T.nilable(String))
|
|
82
|
+
inner = T.must(T.must(record["removed_locked_tags"])[1..-2])
|
|
83
|
+
@removed_locked_tags = T.let(inner.empty? ? [] : inner.split(","), T::Array[String])
|
|
84
|
+
inner = T.must(T.must(record["removed_tags"])[1..-2])
|
|
85
|
+
@removed_tags = T.let(inner.empty? ? [] : inner.split(","), T::Array[String])
|
|
86
|
+
@source = T.let(T.must(record["source"]).empty? ? nil : record["source"], T.nilable(String))
|
|
87
|
+
@source_changed = T.let(record["source_changed"] == "t", T::Boolean)
|
|
88
|
+
@tags = T.let(T.must(record["tags"]).empty? ? nil : record["tags"], T.nilable(String))
|
|
89
|
+
@updated_at = T.let(DateTime.parse(record["updated_at"]), DateTime)
|
|
90
|
+
@updater_id = T.let(record["updater_id"].to_i, Integer)
|
|
91
|
+
@version = T.let(record["version"].to_i, Integer)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
sig { params(_args: T.untyped).returns(String) }
|
|
95
|
+
def to_json(*_args)
|
|
96
|
+
{
|
|
97
|
+
added_locked_tags: @added_locked_tags,
|
|
98
|
+
added_tags: @added_tags,
|
|
99
|
+
description: @description,
|
|
100
|
+
description_changed: @description_changed,
|
|
101
|
+
id: @id,
|
|
102
|
+
locked_tags: @locked_tags,
|
|
103
|
+
parent_changed: @parent_changed,
|
|
104
|
+
parent_id: @parent_id,
|
|
105
|
+
rating: @rating,
|
|
106
|
+
rating_changed: @rating_changed,
|
|
107
|
+
reason: @reason,
|
|
108
|
+
removed_locked_tags: @removed_locked_tags,
|
|
109
|
+
removed_tags: @removed_tags,
|
|
110
|
+
source: @source,
|
|
111
|
+
source_changed: @source_changed,
|
|
112
|
+
tags: @tags,
|
|
113
|
+
updated_at: @updated_at,
|
|
114
|
+
updater_id: @updater_id,
|
|
115
|
+
version: @version,
|
|
116
|
+
}.to_json
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
module E621ExportDownloader
|
|
4
4
|
class Types < T::Enum
|
|
5
5
|
enums do
|
|
6
|
+
Artists = new("artists")
|
|
7
|
+
BulkUpdateRequests = new("bulk_update_requests")
|
|
6
8
|
Pools = new("pools")
|
|
7
9
|
Posts = new("posts")
|
|
10
|
+
PostReplacements = new("post_replacements")
|
|
11
|
+
PostVersions = new("post_versions")
|
|
8
12
|
TagAliases = new("tag_aliases")
|
|
9
13
|
TagImplications = new("tag_implications")
|
|
10
14
|
Tags = new("tags")
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: e621_export_downloader
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Donovan_DMC
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-06-12 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: csv
|
|
@@ -80,6 +80,7 @@ files:
|
|
|
80
80
|
- Rakefile
|
|
81
81
|
- exe/e621-export-downloader
|
|
82
82
|
- lib/e621_export_downloader.rb
|
|
83
|
+
- lib/e621_export_downloader/api_export_data.rb
|
|
83
84
|
- lib/e621_export_downloader/client.rb
|
|
84
85
|
- lib/e621_export_downloader/client/options.rb
|
|
85
86
|
- lib/e621_export_downloader/client/options/builder.rb
|
|
@@ -87,8 +88,12 @@ files:
|
|
|
87
88
|
- lib/e621_export_downloader/constants.rb
|
|
88
89
|
- lib/e621_export_downloader/export.rb
|
|
89
90
|
- lib/e621_export_downloader/export_helper.rb
|
|
91
|
+
- lib/e621_export_downloader/models/artist.rb
|
|
92
|
+
- lib/e621_export_downloader/models/bulk_update_request.rb
|
|
90
93
|
- lib/e621_export_downloader/models/pool.rb
|
|
91
94
|
- lib/e621_export_downloader/models/post.rb
|
|
95
|
+
- lib/e621_export_downloader/models/post_replacement.rb
|
|
96
|
+
- lib/e621_export_downloader/models/post_version.rb
|
|
92
97
|
- lib/e621_export_downloader/models/tag.rb
|
|
93
98
|
- lib/e621_export_downloader/models/tag_alias.rb
|
|
94
99
|
- lib/e621_export_downloader/models/tag_implication.rb
|