rubyhexagon 1.6.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +5 -5
  2. data/lib/rubyhexagon.rb +34 -18
  3. data/lib/rubyhexagon/api.rb +96 -0
  4. data/lib/rubyhexagon/{login.rb → api/artist.rb} +9 -19
  5. data/lib/rubyhexagon/api/note.rb +50 -0
  6. data/lib/rubyhexagon/api/pool.rb +51 -0
  7. data/lib/rubyhexagon/api/post.rb +92 -0
  8. data/lib/rubyhexagon/api/post/flag.rb +36 -0
  9. data/lib/rubyhexagon/api/post/tag_item.rb +36 -0
  10. data/lib/rubyhexagon/api/tag.rb +65 -0
  11. data/lib/rubyhexagon/api/tag/alias.rb +41 -0
  12. data/lib/rubyhexagon/api/tag/implication.rb +41 -0
  13. data/lib/rubyhexagon/api/user.rb +52 -0
  14. data/lib/rubyhexagon/artist.rb +44 -32
  15. data/lib/rubyhexagon/error.rb +4 -5
  16. data/lib/rubyhexagon/note.rb +112 -0
  17. data/lib/rubyhexagon/pool.rb +22 -50
  18. data/lib/rubyhexagon/post.rb +142 -161
  19. data/lib/rubyhexagon/post/flag.rb +78 -0
  20. data/lib/rubyhexagon/post/image.rb +114 -0
  21. data/lib/rubyhexagon/post/tag_item.rb +74 -0
  22. data/lib/rubyhexagon/search/posts.rb +3 -3
  23. data/lib/rubyhexagon/tag.rb +20 -47
  24. data/lib/rubyhexagon/tag/alias.rb +87 -0
  25. data/lib/rubyhexagon/tag/implication.rb +91 -0
  26. data/lib/rubyhexagon/tag/type.rb +79 -0
  27. data/lib/rubyhexagon/user.rb +24 -30
  28. data/lib/rubyhexagon/user/level.rb +92 -0
  29. metadata +22 -13
  30. data/lib/rubyhexagon/helper/api.rb +0 -99
  31. data/lib/rubyhexagon/image.rb +0 -122
  32. data/lib/rubyhexagon/level.rb +0 -58
  33. data/lib/rubyhexagon/search/flag_history.rb +0 -62
  34. data/lib/rubyhexagon/search/pools.rb +0 -62
  35. data/lib/rubyhexagon/search/tag_history.rb +0 -61
  36. data/lib/rubyhexagon/search/tags.rb +0 -139
  37. data/lib/rubyhexagon/tag_change.rb +0 -69
  38. data/lib/rubyhexagon/type.rb +0 -72
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2014-2018 Maxine Michalski <maxine@furfind.net>
3
+ # Copyright 2014-2018, 2020 Maxine Michalski <maxine@furfind.net>
4
4
  #
5
5
  # This file is part of rubyhexagon.
6
6
  #
@@ -38,8 +38,11 @@ module Rubyhexagon
38
38
  # @return [String] returns a description of the current pool
39
39
  attr_reader :description
40
40
 
41
- # @return [User] returns the user, who has created this pool
42
- attr_reader :user
41
+ # @return [Integer] returns number of posts inside pool
42
+ attr_reader :post_count
43
+
44
+ # @return [E621::User] returns the user, who has created this pool
45
+ attr_reader :creator
43
46
 
44
47
  # @return [Array<Post>] returns an array of posts, that belong to this pool
45
48
  attr_reader :posts
@@ -48,52 +51,26 @@ module Rubyhexagon
48
51
  #
49
52
  # Initializer for Pool objects
50
53
  #
51
- # @param id [Integer] ID of this pool
54
+ # @param pool [Hash] Pool information
52
55
  #
53
56
  # @return the object
54
- def initialize(id)
55
- unless id.is_a?(Integer) && id.positive?
56
- raise InvalidIDError, "ID out of range: #{id}"
57
- end
58
- @id = id
59
- end
60
-
61
- # @author Maxine Michalski
62
- #
63
- # @param pool [Hash] pool data, if present
64
- #
65
- # Fetch data from e621 to current post id
66
- def show!(pool = nil)
67
- @posts = []
68
- if pool.nil?
69
- page = 1
70
- loop do
71
- pool = API.new.fetch('pool', 'show', id: @id, page: page)
72
- page += 1
73
- @posts += pool[:posts].map do |post|
74
- E621::Post.new(post[:id]).show(post)
57
+ def initialize(pool)
58
+ raise ArgumentError, "#{pool.class} is not a Hash" unless pool.is_a?(Hash)
59
+ raise ArgumentError, 'Hash must include :id' if pool[:id].nil?
60
+ pool.each do |k, v|
61
+ if %i[id name post_count is_active is_locked description].include?(k)
62
+ if k == :id && !(v.is_a?(Integer) && v.positive?)
63
+ raise InvalidIDError, "ID out of range: #{v}"
75
64
  end
76
- break if pool[:posts] == []
65
+ instance_variable_set("@#{k}".to_sym, v)
66
+ elsif %i[updated_at created_at].include?(k)
67
+ instance_variable_set("@#{k}".to_sym, Time.at(v[:s]))
68
+ elsif k == :user_id
69
+ @creator = E621::User.new(id: v)
70
+ elsif k == :posts
71
+ @posts = v.map { |post| E621::Post.new(post) }
77
72
  end
78
73
  end
79
- @name = pool[:name]
80
- @created_at = Time.at(pool[:created_at][:s].to_i)
81
- @updated_at = Time.at(pool[:updated_at][:s].to_i)
82
- @description = pool[:description]
83
- @user = User.new(id: pool[:user_id])
84
- @is_locked = pool[:is_locked]
85
- @is_active = pool[:is_active]
86
- end
87
-
88
- # @author Maxine Michalski
89
- #
90
- # @param pool [Hash] pool data, if present
91
- #
92
- # Fetch data from e621 to current pool id
93
- def show(pool = nil)
94
- tmp = Pool.new(@id)
95
- tmp.show!(pool)
96
- tmp
97
74
  end
98
75
 
99
76
  # @author Maxine Michalski
@@ -122,12 +99,7 @@ module Rubyhexagon
122
99
  #
123
100
  # @return [TrueClass,FalseClass]
124
101
  def ==(other)
125
- if @name.nil?
126
- other.is_a?(Pool) && @id == other.id
127
- else
128
- other.is_a?(Pool) && @name == other.name && @id == other.id &&
129
- @updated_at == other.updated_at
130
- end
102
+ other.is_a?(Pool) && @id == other.id && @updated_at == other.updated_at
131
103
  end
132
104
  end
133
105
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2014-2018 Maxine Michalski <maxine@furfind.net>
3
+ # Copyright 2014-2018, 2020 Maxine Michalski <maxine@furfind.net>
4
4
  #
5
5
  # This file is part of rubyhexagon.
6
6
  #
@@ -26,7 +26,7 @@ module Rubyhexagon
26
26
  # @return [Integer] id of post information
27
27
  attr_reader :id
28
28
 
29
- # @return [User] user object of who uploaded this post
29
+ # @return [E621::User] user object of who uploaded this post
30
30
  attr_reader :author
31
31
 
32
32
  # @return [Time] creation time of post
@@ -40,15 +40,15 @@ module Rubyhexagon
40
40
  # form
41
41
  attr_reader :sources
42
42
 
43
- # @return [Array<Artist>] array of artists, that work on the image in this
44
- # post
43
+ # @return [Array<E621::Artist>] array of artists, that work on the image
44
+ # in this post
45
45
  attr_reader :artists
46
46
 
47
47
  # @return [String] description that's associated with this post
48
48
  attr_reader :description
49
49
 
50
50
  # @return [Integer] number of people, who favorited this post
51
- attr_reader :favorites
51
+ attr_reader :fav_count
52
52
 
53
53
  # @return [Integer] vote score this post holds
54
54
  attr_reader :score
@@ -57,252 +57,233 @@ module Rubyhexagon
57
57
  # :questionable, :explicit
58
58
  attr_reader :rating
59
59
 
60
- # @return [Post] parent of this post
60
+ # @return [E621:post] parent of this post
61
61
  # @return [NilClass]
62
62
  attr_reader :parent
63
63
 
64
- # @return [Array<Post>] Array of child posts
64
+ # @return [Array<E621::Post>] Array of child posts
65
65
  attr_reader :children
66
66
 
67
67
  # @return [String] MD5 checksum associated with this post
68
68
  attr_reader :md5
69
69
 
70
- # @return [Rubyhexagon::Image] file content of this post
70
+ # @return [E621::Image] file content of this post
71
71
  attr_reader :image
72
72
 
73
- # @return [Rubyhexagon::Sample] sample data in Rubyhexagon::Sample format
73
+ # @return [E621::Sample] sample data in E621::Sample format
74
74
  attr_reader :sample
75
75
 
76
- # @return [Rubyhexagon::Preview] preview data in Rubyhexagon::Preview format
76
+ # @return [E621::Preview] preview data in E621::Preview format
77
77
  attr_reader :preview
78
78
 
79
- # @return [Array<String>] tag names only for convenience
80
- attr_reader :tag_names
79
+ # @return [String|NilClass] reason for deletion, if deleted
80
+ attr_reader :delreason
81
+
82
+ # @return [Array<E621::Tag>] tags of this post
83
+ attr_reader :tags
81
84
 
82
85
  # @author Maxine Michalski
83
86
  #
84
87
  # Initializer for Post.
85
88
  #
86
- # @param id [Integer] post id on e621
89
+ # @param post [Hash] post data
87
90
  #
88
91
  # @return the object
89
- def initialize(id)
90
- id = id.to_i
92
+ def initialize(post)
93
+ raise ArgumentError, "#{post.class} is not a Hash" unless post.is_a?(Hash)
94
+ post[:id] = post[:id].to_i
95
+ id = post[:id]
91
96
  raise InvalidIDError, "ID out of range: #{id}" unless id.positive?
92
- @id = id
97
+ post.each do |k, v|
98
+ if %i[rating created_at status sources parent_id children
99
+ description artist tags].include?(k)
100
+ __send__("setup_#{k}".to_sym, v)
101
+ elsif %i[id description score fav_count md5 has_comments has_children
102
+ delreason has_notes].include?(k)
103
+ instance_variable_set("@#{k}".to_sym, v)
104
+ end
105
+ end
106
+ setup_author(post)
107
+ setup_files(post)
93
108
  end
94
109
 
95
110
  # @author Maxine Michalski
96
111
  #
97
- # @param post [Hash] post data, if present
112
+ # Test status with an optional parameter. This is more meant to be used by
113
+ # alias methods
98
114
  #
99
- # Fetch data from e621 to current post id
100
- def show!(post = nil)
101
- post = API.new.fetch('post', 'show', id: @id) if post.nil?
102
- @created_at = Time.at(post[:created_at][:s].to_i)
103
- @status = post[:status].to_sym
104
- setup_author(post)
105
- setup_relatives(post)
106
- setup_file_data(post)
107
- setup_additional_data(post)
115
+ # @param test_var [Symbol] variable under test
116
+ #
117
+ # @return [TrueClass]
118
+ # @return [FalseClass]
119
+ def test(test_var = nil)
120
+ test_var ||= __callee__.to_s.sub(/\?/, '').to_sym
121
+ test_ar = %i[safe questionable explicit]
122
+ test_ar.include?(test_var) ? @rating == test_var : @status == test_var
108
123
  end
124
+ alias active? test
125
+ alias flagged? test
126
+ alias pending? test
127
+ alias deleted? test
128
+ alias safe? test
129
+ alias questionable? test
130
+ alias explicit? test
109
131
 
110
132
  # @author Maxine Michalski
111
133
  #
112
- # @param post [Hash] post data, if present
134
+ # Show if post has comments or not
113
135
  #
114
- # Fetch data from e621 to current post id
115
- def show(post = nil)
116
- tmp = Post.new(@id)
117
- tmp.show!(post)
118
- tmp
136
+ # @return [TrueClass|FalseClass]
137
+ def comments?
138
+ return false if @has_comments.nil?
139
+ @has_comments
119
140
  end
120
141
 
121
142
  # @author Maxine Michalski
122
143
  #
123
- # Send a vote to e621
144
+ # Show if post has a parent
124
145
  #
125
- # @param score [Integer] direction of vote, can be positive or negative
126
- def vote!(score)
127
- unless score.is_a?(Integer)
128
- raise ArgumentError, 'Voting requires a positive/negative number'
129
- end
130
- raise ArgumentError, "Vote can't be zero" if score.zero?
131
- score = score.positive? ? 1 : -1
132
- res = API.new.fetch('post', 'vote', { id: @id, score: score }, 'POST')
133
- if res[:reason] == 'access denied' && !res[:success]
134
- raise Rubyhexagon::AccessDenied, res[:message]
135
- end
136
- nil
146
+ # @return [TrueClass|FalseClass]
147
+ def parent?
148
+ !@parent.nil?
137
149
  end
138
150
 
139
151
  # @author Maxine Michalski
140
152
  #
141
- # Upvotes this post
142
- def voteup!
143
- vote!(1)
144
- end
145
-
146
- # @author Maxine Michalski
153
+ # Show if post has children
147
154
  #
148
- # Downvotes this post
149
- def votedown!
150
- vote!(-1)
155
+ # @return [TrueClass|FalseClass]
156
+ def children?
157
+ return false if @has_children.nil?
158
+ @has_children
151
159
  end
152
160
 
153
161
  # @author Maxine Michalski
154
162
  #
155
- # Favorite this post
156
- def favorite!
157
- res = API.new.fetch('favorite', 'create', { id: @id }, 'POST')
158
- if res[:reason] == 'access denied' && !res[:success]
159
- raise Rubyhexagon::AccessDenied, res[:message]
160
- elsif !res[:success] &&
161
- res[:reason] == 'You\'ve already favorited this post'
162
- raise Rubyhexagon::AlreadyFavorited
163
- end
164
- nil
163
+ # Comparison method for posts
164
+ #
165
+ # @return [TrueClass, FalseClass]
166
+ def ==(other)
167
+ other.is_a?(Post) && @id == other.id
165
168
  end
166
169
 
167
170
  # @author Maxine Michalski
168
171
  #
169
- # Unfavorite this post
170
- def unfavorite!
171
- res = API.new.fetch('favorite', 'destroy', { id: @id }, 'POST')
172
- if res[:reason] == 'access denied' && !res[:success]
173
- raise Rubyhexagon::AccessDenied, res[:message]
174
- end
175
- nil
172
+ # Has post notes?
173
+ #
174
+ # @return [TrueClass, FalseClass]
175
+ def notes?
176
+ @has_notes
176
177
  end
177
178
 
179
+ private
180
+
178
181
  # @author Maxine Michalski
179
182
  #
180
- # Retrieve an Array of this post's tags
183
+ # Set author information
181
184
  #
182
- # @return [Array<Tag>] array of tags
183
- def tags
184
- API.new.fetch('post', 'tags', id: @id).map do |tag|
185
- Tag.new(tag[:id]).show(tag)
186
- end
185
+ # @param post [Hash] post data
186
+ def setup_author(post)
187
+ return if post[:creator_id].nil?
188
+ @author = E621::User.new(id: post[:creator_id], name: post[:author])
187
189
  end
188
190
 
189
191
  # @author Maxine Michalski
190
192
  #
191
- # Test status with an optional parameter. This is more meant to be used by
192
- # alias methods
193
+ # Set rating for post
193
194
  #
194
- # @param test_var [Symbol] variable under test
195
- #
196
- # @return [TrueClass]
197
- # @return [FalseClass]
198
- def test(test_var = nil)
199
- test_var ||= __callee__.to_s.sub(/\?/, '').to_sym
200
- test_ar = %i[safe questionable explicit]
201
- test_ar.include?(test_var) ? @rating == test_var : @status == test_var
195
+ # @param rating [String] rating of this post
196
+ def setup_rating(rating)
197
+ @rating = case rating
198
+ when 's' then :safe
199
+ when 'q' then :questionable
200
+ when 'e' then :explicit
201
+ end
202
202
  end
203
- alias active? test
204
- alias flagged? test
205
- alias pending? test
206
- alias deleted? test
207
- alias safe? test
208
- alias questionable? test
209
- alias explicit? test
210
203
 
211
204
  # @author Maxine Michalski
212
205
  #
213
- # Comparison method for posts
206
+ # Set creation time
214
207
  #
215
- # @return [TrueClass, FalseClass]
216
- def ==(other)
217
- other.is_a?(Post) && @id == other.id
208
+ # @param time [Time] Hash that includes creation time in UNIX Epoch on an
209
+ # `:s` key.
210
+ def setup_created_at(time)
211
+ @created_at = Time.at(time[:s]) unless time.nil?
218
212
  end
219
213
 
220
- private
221
-
222
214
  # @author Maxine Michalski
223
215
  #
224
- # Helper function, to setup post file data
225
- def setup_file_data(post)
226
- @tag_names = post[:tags].split(/\s+/)
227
- @md5 = post[:md5]
228
- @image = Image.new(url: post[:file_url], ext: post[:file_ext],
229
- width: post[:width], height: post[:height],
230
- size: post[:file_size])
231
- setup_sample(post)
232
- setup_preview(post)
216
+ # Set status of post
217
+ #
218
+ # @param status [String] Status of post
219
+ def setup_status(status)
220
+ @status = status.to_sym unless status.nil?
233
221
  end
234
222
 
235
223
  # @author Maxine Michalski
236
224
  #
237
- # Helper function, to setup post sample data
238
- def setup_sample(post)
239
- @sample = Sample.new(url: post[:sample_url], ext: post[:file_ext],
240
- width: post[:sample_width],
241
- height: post[:sample_height])
225
+ # Set sources of post
226
+ #
227
+ # @param sources [Array<String>] Sources of post
228
+ def setup_sources(sources)
229
+ @sources = sources.nil? ? [] : sources
242
230
  end
243
231
 
244
232
  # @author Maxine Michalski
245
233
  #
246
- # Helper function, to setup post preview data
247
- def setup_preview(post)
248
- @preview = Preview.new(url: post[:preview_url], ext: 'jpg',
249
- width: post[:preview_width],
250
- height: post[:preview_height])
234
+ # Set parent post
235
+ #
236
+ # @param parent [Integer] Parent post ID
237
+ #
238
+ # @notice A parent post is only set with an ID and further information must
239
+ # be fetched manually.
240
+ def setup_parent_id(parent)
241
+ @parent = parent.nil? ? nil : E621::Post.new(id: parent)
251
242
  end
252
243
 
253
244
  # @author Maxine Michalski
254
245
  #
255
- # Helper function to add additional post data
256
- def setup_additional_data(post)
257
- @sources = post[:source].nil? ? [] : post[:sources]
258
- @artists = post[:artist].map { |a| Artist.new(a) }
259
- @description = post[:description]
260
- @favorites = post[:fav_count].to_i
261
- @score = post[:score].to_i
262
- @rating = case post[:rating]
263
- when 's' then :safe
264
- when 'q' then :questionable
265
- when 'e' then :explicit
266
- end
246
+ # Set children posts
247
+ #
248
+ # @param children [Array<Integer>] Children post IDs
249
+ #
250
+ # @notice All children are only set with an ID and further information must
251
+ # be fetched manually.
252
+ def setup_children(children)
253
+ return [] if children.nil?
254
+ @children = children.split(',').map { |c| E621::Post.new(id: c) }
267
255
  end
268
256
 
269
257
  # @author Maxine Michalski
270
258
  #
271
- # Helper function to related posts (children, parent)
272
- def setup_relatives(post)
273
- @children = post[:children].split(',').map { |i| Post.new(i) }
274
- @parent = post[:parent_id].nil? ? nil : Post.new(post[:parent_id])
259
+ # Set post description
260
+ #
261
+ # @param description [String] Post description
262
+ #
263
+ # @notice Empty descriptions are set to nil
264
+ def setup_description(description)
265
+ @description = !description.nil? && description.empty? ? nil : description
275
266
  end
276
267
 
277
- # @author Maxine Michalski
278
- #
279
- # Helper function to set author of a post
280
- def setup_author(post)
281
- @author = User.new(id: post[:creator_id], name: post[:author])
268
+ def setup_artist(artists)
269
+ @artists = artists.map { |a| E621::Artist.new(name: CGI.unescape(a)) }
282
270
  end
283
- end
284
271
 
285
- # This class holds post information, fetched from e621, and gives access in a
286
- # more Ruby like manner.
287
- # This is only for deleted posts.
288
- #
289
- # @author Maxine Michalski
290
- # @since 1.0.0
291
- class DeletedPost < Post
292
- # @return [String] deletion reason, when available
293
- attr_reader :reason
272
+ def setup_tags(tags)
273
+ @tags = tags.split(' ').map { |t| E621::Tag.new(name: t) }
274
+ end
294
275
 
295
- # @author Maxine Michalski
296
- #
297
- # Initializer for DeletedPost.
298
- #
299
- # @param id [Integer] post id on e621
300
- # @param delreason [String] reason of deletion
301
- #
302
- # @return the object
303
- def initialize(id, delreason)
304
- super(id)
305
- @reason = delreason
276
+ def setup_files(post)
277
+ return if post[:file_url].nil?
278
+ @image = Image.new(url: post[:file_url], ext: post[:file_ext],
279
+ width: post[:width], height: post[:height],
280
+ size: post[:file_size])
281
+ @sample = Sample.new(url: post[:sample_url], ext: post[:file_ext],
282
+ width: post[:sample_width],
283
+ height: post[:sample_height], size: 0)
284
+ @preview = Preview.new(url: post[:preview_url], ext: 'jpg',
285
+ width: post[:preview_width],
286
+ height: post[:preview_height], size: 0)
306
287
  end
307
288
  end
308
289
  end