rubyhexagon 1.6.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/lib/rubyhexagon.rb +34 -18
- data/lib/rubyhexagon/api.rb +96 -0
- data/lib/rubyhexagon/{login.rb → api/artist.rb} +9 -19
- data/lib/rubyhexagon/api/note.rb +50 -0
- data/lib/rubyhexagon/api/pool.rb +51 -0
- data/lib/rubyhexagon/api/post.rb +92 -0
- data/lib/rubyhexagon/api/post/flag.rb +36 -0
- data/lib/rubyhexagon/api/post/tag_item.rb +36 -0
- data/lib/rubyhexagon/api/tag.rb +65 -0
- data/lib/rubyhexagon/api/tag/alias.rb +41 -0
- data/lib/rubyhexagon/api/tag/implication.rb +41 -0
- data/lib/rubyhexagon/api/user.rb +52 -0
- data/lib/rubyhexagon/artist.rb +44 -32
- data/lib/rubyhexagon/error.rb +4 -5
- data/lib/rubyhexagon/note.rb +112 -0
- data/lib/rubyhexagon/pool.rb +22 -50
- data/lib/rubyhexagon/post.rb +142 -161
- data/lib/rubyhexagon/post/flag.rb +78 -0
- data/lib/rubyhexagon/post/image.rb +114 -0
- data/lib/rubyhexagon/post/tag_item.rb +74 -0
- data/lib/rubyhexagon/search/posts.rb +3 -3
- data/lib/rubyhexagon/tag.rb +20 -47
- data/lib/rubyhexagon/tag/alias.rb +87 -0
- data/lib/rubyhexagon/tag/implication.rb +91 -0
- data/lib/rubyhexagon/tag/type.rb +79 -0
- data/lib/rubyhexagon/user.rb +24 -30
- data/lib/rubyhexagon/user/level.rb +92 -0
- metadata +22 -13
- data/lib/rubyhexagon/helper/api.rb +0 -99
- data/lib/rubyhexagon/image.rb +0 -122
- data/lib/rubyhexagon/level.rb +0 -58
- data/lib/rubyhexagon/search/flag_history.rb +0 -62
- data/lib/rubyhexagon/search/pools.rb +0 -62
- data/lib/rubyhexagon/search/tag_history.rb +0 -61
- data/lib/rubyhexagon/search/tags.rb +0 -139
- data/lib/rubyhexagon/tag_change.rb +0 -69
- data/lib/rubyhexagon/type.rb +0 -72
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2014-2018, 2020 Maxine Michalski <maxine@furfind.net>
|
4
|
+
#
|
5
|
+
# This file is part of rubyhexagon.
|
6
|
+
#
|
7
|
+
# rubyhexagon is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# rubyhexagon is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with rubyhexagon. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
module Rubyhexagon
|
20
|
+
class Post
|
21
|
+
# Class to represent post flags
|
22
|
+
#
|
23
|
+
# @author Maxine Michalski
|
24
|
+
# @since 2.0.0
|
25
|
+
class Flag
|
26
|
+
# @return [Integer] id of flag
|
27
|
+
attr_reader :id
|
28
|
+
|
29
|
+
# @return [Time] creation time of flag
|
30
|
+
attr_reader :created_at
|
31
|
+
# @return [621::Post] Post this flag is addressed to
|
32
|
+
attr_reader :post
|
33
|
+
# @return [String] reason for flag
|
34
|
+
attr_reader :reason
|
35
|
+
# @return [E621::User] User who flagged
|
36
|
+
attr_reader :user
|
37
|
+
|
38
|
+
# @author Maxine Michalski
|
39
|
+
#
|
40
|
+
# Initializer for flags
|
41
|
+
#
|
42
|
+
# @param flag [Hash] flag data
|
43
|
+
#
|
44
|
+
# @return the object
|
45
|
+
def initialize(flag)
|
46
|
+
unless flag.is_a?(Hash)
|
47
|
+
raise ArgumentError, "#{flag.class} is not a Hash"
|
48
|
+
end
|
49
|
+
unless flag.key?(:id)
|
50
|
+
raise ArgumentError, 'Not all required keys available!'
|
51
|
+
end
|
52
|
+
flag.each do |k, v|
|
53
|
+
if %i[id reason].include?(k)
|
54
|
+
if k == :id && !(v.is_a?(Integer) && v.positive?)
|
55
|
+
raise InvalidIDError, "ID out of range: #{v}"
|
56
|
+
end
|
57
|
+
instance_variable_set("@#{k}".to_sym, v)
|
58
|
+
elsif k == :created_at
|
59
|
+
@created_at = Time.at(v)
|
60
|
+
elsif k == :post_id
|
61
|
+
@post = E621::Post.new(id: v)
|
62
|
+
elsif k == :user_id
|
63
|
+
@user = E621::User.new(id: v)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @author Maxine Michalski
|
69
|
+
#
|
70
|
+
# Comparison method for flags
|
71
|
+
#
|
72
|
+
# @return [TrueClass, FalseClass]
|
73
|
+
def ==(other)
|
74
|
+
other.is_a?(E621::Post::Flag) && @id == other.id
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2014-2018, 2020 Maxine Michalski <maxine@furfind.net>
|
4
|
+
#
|
5
|
+
# This file is part of rubyhexagon.
|
6
|
+
#
|
7
|
+
# rubyhexagon is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# rubyhexagon is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with rubyhexagon. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
module Rubyhexagon
|
20
|
+
class Post
|
21
|
+
# Class for post file data. This is mostly an abstraction to have data
|
22
|
+
# structures in a more Ruby like nature.
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
# @author Maxine Michalski
|
26
|
+
# @since 1.0.0
|
27
|
+
class Image
|
28
|
+
# @return [URI] url of file
|
29
|
+
attr_reader :url
|
30
|
+
|
31
|
+
# @return [String] extension of file
|
32
|
+
attr_reader :ext
|
33
|
+
|
34
|
+
# @return [Integer] image/video width
|
35
|
+
attr_reader :width
|
36
|
+
|
37
|
+
# @return [Integer] image/video height
|
38
|
+
attr_reader :height
|
39
|
+
|
40
|
+
# @return [Integer] file size in bytes
|
41
|
+
attr_reader :size
|
42
|
+
|
43
|
+
# @author Maxine Michalski
|
44
|
+
#
|
45
|
+
# Initializer for an Image.
|
46
|
+
#
|
47
|
+
# @param image [Hash] image information
|
48
|
+
# @option image [String] :url URI location
|
49
|
+
# @option image [String] :ext extension string
|
50
|
+
# @option image [Integer] :width of image
|
51
|
+
# @option image [Integer] :height of image
|
52
|
+
# @option image [Integer] :size of image
|
53
|
+
#
|
54
|
+
# @return the object
|
55
|
+
def initialize(image)
|
56
|
+
unless image.is_a?(Hash)
|
57
|
+
raise ArgumentError, "#{image.class} is not a Hash"
|
58
|
+
end
|
59
|
+
if image.keys != %i[url ext width height size]
|
60
|
+
mis = %i[url ext width height size] - image.keys
|
61
|
+
raise ArgumentError, "Missing key#{mis.count > 1 ? 's' : ''}: #{mis}"
|
62
|
+
end
|
63
|
+
image.each do |k, v|
|
64
|
+
if %i[ext width height size].include?(k)
|
65
|
+
instance_variable_set("@#{k}".to_sym, v)
|
66
|
+
elsif k == :url
|
67
|
+
@url = URI.parse(v)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# @author Maxine Michalski
|
73
|
+
#
|
74
|
+
# A convenient function to return both width and height.
|
75
|
+
#
|
76
|
+
# @return [Array<Integer>]
|
77
|
+
def resolution
|
78
|
+
[@width, @height]
|
79
|
+
end
|
80
|
+
|
81
|
+
# @author Maxine Michalski
|
82
|
+
#
|
83
|
+
# A convenient function to return the aspect ratio of a given image.
|
84
|
+
#
|
85
|
+
# @return [Float]
|
86
|
+
def aspect_ratio
|
87
|
+
@width / @height.to_f
|
88
|
+
end
|
89
|
+
|
90
|
+
# @author Maxine Michalski
|
91
|
+
#
|
92
|
+
# Comparison method to comapre two Image objects (and sub class objects)
|
93
|
+
#
|
94
|
+
# @return [TrueClass, FalseClass]
|
95
|
+
def ==(other)
|
96
|
+
other.is_a?(Image) && @url == other.url && @size == other.size
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# class for samples, that is just a copy of Image
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
# @author Maxine Michalski
|
104
|
+
# @since 1.0,0
|
105
|
+
class Sample < Image; end
|
106
|
+
|
107
|
+
# class for previews, that is just a copy of Image
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
# @author Maxine Michalski
|
111
|
+
# @since 1.0,0
|
112
|
+
class Preview < Image; end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2014-2018, 2020 Maxine Michalski <maxine@furfind.net>
|
4
|
+
#
|
5
|
+
# This file is part of rubyhexagon.
|
6
|
+
#
|
7
|
+
# rubyhexagon is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# rubyhexagon is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with rubyhexagon. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
module Rubyhexagon
|
20
|
+
class Post
|
21
|
+
# Class to represent post tag_items
|
22
|
+
#
|
23
|
+
# @author Maxine Michalski
|
24
|
+
# @since 2.0.0
|
25
|
+
class TagItem
|
26
|
+
# @return [Integer] id of post information
|
27
|
+
attr_reader :id
|
28
|
+
# @return [Integer] id of post information
|
29
|
+
attr_reader :created_at
|
30
|
+
# @return [Integer] id of post information
|
31
|
+
attr_reader :post
|
32
|
+
# @return [Integer] id of post information
|
33
|
+
attr_reader :tags
|
34
|
+
# @return [Integer] id of post information
|
35
|
+
attr_reader :sources
|
36
|
+
|
37
|
+
# @author Maxine Michalski
|
38
|
+
#
|
39
|
+
# Initializer for tag change items
|
40
|
+
#
|
41
|
+
# @param tag_item [Hash] tag item data
|
42
|
+
#
|
43
|
+
# @return the object
|
44
|
+
def initialize(tag_item)
|
45
|
+
unless tag_item.is_a?(Hash)
|
46
|
+
raise ArgumentError, "#{tag_item.class} is not a Hash"
|
47
|
+
end
|
48
|
+
unless (miss = %i[id created_at post_id tags source] -
|
49
|
+
tag_item.keys).empty?
|
50
|
+
raise ArgumentError, 'Not all required keys available! '\
|
51
|
+
"Missing: #{miss}"
|
52
|
+
end
|
53
|
+
id = tag_item[:id]
|
54
|
+
unless id.is_a?(Integer) && id.positive?
|
55
|
+
raise InvalidIDError, "ID out of range: #{id}"
|
56
|
+
end
|
57
|
+
@id = id
|
58
|
+
@created_at = Time.at(tag_item[:created_at])
|
59
|
+
@post = E621::Post.new(id: tag_item[:post_id])
|
60
|
+
@tags = tag_item[:tags].split(' ').map { |t| E621::Tag.new(name: t) }
|
61
|
+
@sources = tag_item[:source].split($INPUT_RECORD_SEPARATOR)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @author Maxine Michalski
|
65
|
+
#
|
66
|
+
# Comparison method for tag change items
|
67
|
+
#
|
68
|
+
# @return [TrueClass, FalseClass]
|
69
|
+
def ==(other)
|
70
|
+
other.is_a?(E621::Post::TagItem) && @id == other.id
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
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
|
#
|
@@ -60,8 +60,8 @@ module Rubyhexagon
|
|
60
60
|
# This method accepts blocks and only returns one page without blocks
|
61
61
|
#
|
62
62
|
# @return [Array<Post>] an array of posts
|
63
|
-
def self.list(tags, limit = 320
|
64
|
-
parameters = { tags: tags, limit: limit
|
63
|
+
def self.list(tags, limit = 320)
|
64
|
+
parameters = { tags: tags, limit: limit }
|
65
65
|
posts = fetch_posts(parameters)
|
66
66
|
while block_given? && posts != []
|
67
67
|
posts.each { |post| yield post }
|
data/lib/rubyhexagon/tag.rb
CHANGED
@@ -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
|
#
|
@@ -31,73 +31,46 @@ module Rubyhexagon
|
|
31
31
|
# @return [Integer] number of posts that have this tag assigned to them
|
32
32
|
attr_reader :count
|
33
33
|
|
34
|
-
# @return [Type] type of this tag
|
34
|
+
# @return [Tag::Type] type of this tag
|
35
35
|
attr_reader :type
|
36
36
|
|
37
37
|
# @author Maxine Michalski
|
38
38
|
#
|
39
39
|
# Initializer for Tag.
|
40
40
|
#
|
41
|
-
# @raise InvalidIDError
|
41
|
+
# @raise InvalidIDError|ArgumentError
|
42
42
|
#
|
43
|
-
# @param
|
43
|
+
# @param tag [Hash] tag data
|
44
44
|
#
|
45
45
|
# @return the object
|
46
|
-
def initialize(
|
47
|
-
unless
|
48
|
-
|
46
|
+
def initialize(tag)
|
47
|
+
raise ArgumentError, "#{tag.class} is not a Hash" unless tag.is_a?(Hash)
|
48
|
+
if tag[:id].nil? && tag[:name].nil?
|
49
|
+
raise ArgumentError, 'At least :id or :name must be given!'
|
50
|
+
end
|
51
|
+
tag[:type_locked] = false if tag[:type_locked].nil?
|
52
|
+
tag.each do |k, v|
|
53
|
+
if %i[id name count].include?(k)
|
54
|
+
if k == :id && !(v.is_a?(Integer) && v.positive?)
|
55
|
+
raise InvalidIDError, "ID out of range: #{v}"
|
56
|
+
end
|
57
|
+
instance_variable_set("@#{k}".to_sym, v)
|
58
|
+
elsif k == :type
|
59
|
+
@type = E621::Tag::Type.new(id: v, locked: tag[:type_locked])
|
60
|
+
end
|
49
61
|
end
|
50
|
-
@id = id
|
51
|
-
end
|
52
|
-
|
53
|
-
# @author Maxine Michalski
|
54
|
-
#
|
55
|
-
# Bang method to fill tag data.
|
56
|
-
# @notice If the optional parameter is not given, this method makes an API
|
57
|
-
# call.
|
58
|
-
#
|
59
|
-
# @param tag [Hash] optional parameter with API call information
|
60
|
-
#
|
61
|
-
# @return [NilClass]
|
62
|
-
def show!(tag = nil)
|
63
|
-
tag = API.new.fetch('tag', 'show', id: @id) if tag.nil?
|
64
|
-
@name = tag[:name]
|
65
|
-
@count = tag[:count].to_i
|
66
|
-
@type = (Type.new(tag[:type], tag[:type_locked]) unless tag[:type].nil?)
|
67
|
-
nil
|
68
|
-
end
|
69
|
-
|
70
|
-
# @author Maxine Michalski
|
71
|
-
#
|
72
|
-
# Method to return a properly filled Tag object.
|
73
|
-
# @note This makes an additional API call if no argument is given
|
74
|
-
#
|
75
|
-
# @param tag [Hash] optional parameter, if information are ready
|
76
|
-
#
|
77
|
-
# @return [Tag] Tag filled with all information
|
78
|
-
def show(tag = nil)
|
79
|
-
new_tag = Tag.new(@id)
|
80
|
-
new_tag.show!(tag)
|
81
|
-
new_tag
|
82
62
|
end
|
83
63
|
|
84
64
|
# @author Maxine Michalski
|
85
65
|
#
|
86
66
|
# Comparison operator for Tag, to override the default one.
|
87
|
-
# @note This method looks for same id and same name only.
|
88
|
-
# @note If there is only a an ID set with one argument, then only compare
|
89
|
-
# IDs
|
90
67
|
#
|
91
68
|
# @param other [Object] object that should be compared
|
92
69
|
#
|
93
70
|
# @return [TrueClass, FalseClass] test result of comparison
|
94
71
|
def ==(other)
|
95
72
|
return false unless other.is_a?(Tag)
|
96
|
-
|
97
|
-
@id == other.id
|
98
|
-
else
|
99
|
-
@id == other.id && @name == other.name
|
100
|
-
end
|
73
|
+
@id == other.id && @name == other.name && @type == other.type
|
101
74
|
end
|
102
75
|
end
|
103
76
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2014-2018, 2020 Maxine Michalski <maxine@furfind.net>
|
4
|
+
#
|
5
|
+
# This file is part of rubyhexagon.
|
6
|
+
#
|
7
|
+
# rubyhexagon is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# rubyhexagon is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with rubyhexagon. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
module Rubyhexagon
|
20
|
+
class Tag
|
21
|
+
# Class to hold alias information.
|
22
|
+
#
|
23
|
+
# @author Maxine Michalski
|
24
|
+
# @since 2.0.0
|
25
|
+
class Alias
|
26
|
+
# @return [Integer] id of alias
|
27
|
+
attr_reader :id
|
28
|
+
|
29
|
+
# @return [String] name of alias
|
30
|
+
attr_reader :name
|
31
|
+
|
32
|
+
# @return [E621::Tag] tag this alias is aliased to
|
33
|
+
attr_reader :alias_to
|
34
|
+
|
35
|
+
# @author Maxine Michalski
|
36
|
+
#
|
37
|
+
# Initializer for Alias.
|
38
|
+
#
|
39
|
+
# @raise InvalidIDError if alias ID is not valid
|
40
|
+
# @raise ArgumentError hash not valid
|
41
|
+
#
|
42
|
+
# @param ali [Hash] alias data, fetched from e621
|
43
|
+
# @option ali [Integer] :id alias ID
|
44
|
+
# @option ali [String] :name alias for tag
|
45
|
+
# @option ali [Integer] :alias_id ID of tag to alias
|
46
|
+
# @option ali [TrueClass|FalseClass] :pending statud
|
47
|
+
#
|
48
|
+
# @return the object
|
49
|
+
def initialize(ali)
|
50
|
+
raise ArgumentError, "#{ali.class} is not a Hash" unless ali.is_a?(Hash)
|
51
|
+
if ali.keys != %i[id name alias_id pending]
|
52
|
+
mis = %i[id name alias_id pending] - ali.keys
|
53
|
+
raise ArgumentError, "Missing key#{mis.count > 1 ? 's' : ''}: #{mis}"
|
54
|
+
end
|
55
|
+
ali.each do |k, v|
|
56
|
+
if %i[id name pending].include?(k)
|
57
|
+
if k == :id && !(v.is_a?(Integer) && v.positive?)
|
58
|
+
raise InvalidIDError, "Invalid id: #{v}"
|
59
|
+
end
|
60
|
+
instance_variable_set("@#{k}".to_sym, v)
|
61
|
+
elsif k == :alias_id
|
62
|
+
@alias_to = E621::Tag.new(id: v)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# @author Maxine Michalski
|
68
|
+
#
|
69
|
+
# Comparison method for Types, to give a more meaningful comparison.
|
70
|
+
#
|
71
|
+
# @return [TrueClass, FalseClass]
|
72
|
+
def ==(other)
|
73
|
+
other.is_a?(Alias) && @id == other.id
|
74
|
+
end
|
75
|
+
|
76
|
+
# @author Maxine Michalski
|
77
|
+
#
|
78
|
+
# Check if this alias is pending confirmation.
|
79
|
+
#
|
80
|
+
# @return [TrueClass] tag alias is pending
|
81
|
+
# @return [FalseClass] tag alias is not pending
|
82
|
+
def pending?
|
83
|
+
@pending
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|