redd 0.7.10 → 0.8.0.pre.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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -30
  3. data/.rspec +1 -1
  4. data/.rubocop.yml +16 -3
  5. data/.travis.yml +13 -7
  6. data/Gemfile +3 -1
  7. data/LICENSE.txt +21 -0
  8. data/README.md +40 -126
  9. data/Rakefile +10 -3
  10. data/TODO.md +11 -0
  11. data/bin/console +84 -0
  12. data/bin/setup +8 -0
  13. data/lib/redd.rb +84 -46
  14. data/lib/redd/api_client.rb +109 -0
  15. data/lib/redd/auth_strategies/auth_strategy.rb +60 -0
  16. data/lib/redd/auth_strategies/installed.rb +22 -0
  17. data/lib/redd/auth_strategies/script.rb +23 -0
  18. data/lib/redd/auth_strategies/userless.rb +17 -0
  19. data/lib/redd/auth_strategies/web.rb +29 -0
  20. data/lib/redd/client.rb +88 -0
  21. data/lib/redd/error.rb +19 -142
  22. data/lib/redd/models/access.rb +20 -0
  23. data/lib/redd/models/basic_model.rb +124 -0
  24. data/lib/redd/models/comment.rb +51 -0
  25. data/lib/redd/models/front_page.rb +71 -0
  26. data/lib/redd/models/inboxable.rb +23 -0
  27. data/lib/redd/models/lazy_model.rb +63 -0
  28. data/lib/redd/models/listing.rb +26 -0
  29. data/lib/redd/models/messageable.rb +20 -0
  30. data/lib/redd/models/moderatable.rb +41 -0
  31. data/lib/redd/models/more_comments.rb +10 -0
  32. data/lib/redd/models/multireddit.rb +32 -0
  33. data/lib/redd/models/postable.rb +70 -0
  34. data/lib/redd/models/private_message.rb +29 -0
  35. data/lib/redd/models/replyable.rb +16 -0
  36. data/lib/redd/models/session.rb +86 -0
  37. data/lib/redd/models/submission.rb +40 -0
  38. data/lib/redd/models/subreddit.rb +201 -0
  39. data/lib/redd/models/user.rb +72 -0
  40. data/lib/redd/models/wiki_page.rb +24 -0
  41. data/lib/redd/utilities/error_handler.rb +35 -0
  42. data/lib/redd/utilities/rate_limiter.rb +21 -0
  43. data/lib/redd/utilities/stream.rb +63 -0
  44. data/lib/redd/utilities/unmarshaller.rb +39 -0
  45. data/lib/redd/version.rb +4 -3
  46. data/logo.png +0 -0
  47. data/redd.gemspec +26 -22
  48. metadata +73 -99
  49. data/LICENSE.md +0 -22
  50. data/RedditKit.LICENSE.md +0 -9
  51. data/lib/redd/access.rb +0 -76
  52. data/lib/redd/clients/base.rb +0 -188
  53. data/lib/redd/clients/base/account.rb +0 -20
  54. data/lib/redd/clients/base/identity.rb +0 -22
  55. data/lib/redd/clients/base/none.rb +0 -27
  56. data/lib/redd/clients/base/privatemessages.rb +0 -33
  57. data/lib/redd/clients/base/read.rb +0 -113
  58. data/lib/redd/clients/base/stream.rb +0 -81
  59. data/lib/redd/clients/base/submit.rb +0 -19
  60. data/lib/redd/clients/base/utilities.rb +0 -104
  61. data/lib/redd/clients/base/wikiread.rb +0 -33
  62. data/lib/redd/clients/installed.rb +0 -57
  63. data/lib/redd/clients/script.rb +0 -41
  64. data/lib/redd/clients/userless.rb +0 -32
  65. data/lib/redd/clients/web.rb +0 -58
  66. data/lib/redd/objects/base.rb +0 -39
  67. data/lib/redd/objects/comment.rb +0 -22
  68. data/lib/redd/objects/labeled_multi.rb +0 -13
  69. data/lib/redd/objects/listing.rb +0 -29
  70. data/lib/redd/objects/more_comments.rb +0 -11
  71. data/lib/redd/objects/private_message.rb +0 -28
  72. data/lib/redd/objects/submission.rb +0 -139
  73. data/lib/redd/objects/subreddit.rb +0 -330
  74. data/lib/redd/objects/thing.rb +0 -26
  75. data/lib/redd/objects/thing/editable.rb +0 -22
  76. data/lib/redd/objects/thing/hideable.rb +0 -18
  77. data/lib/redd/objects/thing/inboxable.rb +0 -25
  78. data/lib/redd/objects/thing/messageable.rb +0 -34
  79. data/lib/redd/objects/thing/moderatable.rb +0 -43
  80. data/lib/redd/objects/thing/refreshable.rb +0 -14
  81. data/lib/redd/objects/thing/saveable.rb +0 -21
  82. data/lib/redd/objects/thing/votable.rb +0 -33
  83. data/lib/redd/objects/user.rb +0 -52
  84. data/lib/redd/objects/wiki_page.rb +0 -15
  85. data/lib/redd/rate_limit.rb +0 -88
  86. data/lib/redd/response/parse_json.rb +0 -18
  87. data/lib/redd/response/raise_error.rb +0 -16
  88. data/spec/redd/objects/base_spec.rb +0 -1
  89. data/spec/redd/response/raise_error_spec.rb +0 -11
  90. data/spec/redd_spec.rb +0 -5
  91. data/spec/spec_helper.rb +0 -71
@@ -1,22 +0,0 @@
1
- require_relative "thing"
2
-
3
- module Redd
4
- module Objects
5
- # A comment that can be made on a link.
6
- class Comment < Thing
7
- include Thing::Editable
8
- include Thing::Inboxable
9
- include Thing::Moderatable
10
- include Thing::Refreshable
11
- include Thing::Saveable
12
- include Thing::Votable
13
-
14
- alias_property :reports_count, :num_reports
15
-
16
- # @return [Listing] The comment's replies.
17
- def replies
18
- @replies ||= (client.object_from_body(self[:replies]) || [])
19
- end
20
- end
21
- end
22
- end
@@ -1,13 +0,0 @@
1
- module Redd
2
- module Objects
3
- # A comment that can be made on a link.
4
- class LabeledMulti < Base
5
- # @see Objects::Base
6
- def initialize(client, attributes = {})
7
- attr_dup = attributes.dup
8
- attr_dup[:subreddits].map! { |sub| sub[:name] }
9
- super(client, attr_dup)
10
- end
11
- end
12
- end
13
- end
@@ -1,29 +0,0 @@
1
- module Redd
2
- module Objects
3
- # A collection of reddit things.
4
- # @see https://www.reddit.com/dev/api#listings
5
- class Listing < Array
6
- # @!attribute [r] before
7
- # @return [String] The id of the object before the listing.
8
- attr_reader :before
9
-
10
- # @!attribute [r] after
11
- # @return [String] The id of the object after the listing.
12
- attr_reader :after
13
-
14
- # @param [Clients::Base] client The client to expand the comments with.
15
- # @param [{:before => String, :after => String,
16
- # :children => Array<Hash>}] attributes The data to initialize the
17
- # class with.
18
- # @todo Only call Clients::Base#object_from_body when item is being
19
- # accessed.
20
- def initialize(client, attributes)
21
- @before = attributes[:before]
22
- @after = attributes[:after]
23
- attributes[:children].each do |child|
24
- self << (client.object_from_body(child) || child)
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,11 +0,0 @@
1
- module Redd
2
- module Objects
3
- # The model for a morecomments object
4
- class MoreComments < Array
5
- def initialize(_, attributes)
6
- #Return an empty arrar if there are no children
7
- super(attributes[:children]) if attributes[:children]
8
- end
9
- end
10
- end
11
- end
@@ -1,28 +0,0 @@
1
- require_relative "thing"
2
-
3
- module Redd
4
- module Objects
5
- # The model for private messages
6
- class PrivateMessage < Thing
7
- include Thing::Inboxable
8
-
9
- alias_property :from, :author
10
- alias_property :to, :dest
11
-
12
- # Block the sender of the message from sending any more.
13
- def block_sender!
14
- post("/api/block", id: fullname)
15
- end
16
-
17
- # Mark the message as read.
18
- def mark_as_read
19
- post("/api/read_message", id: fullname)
20
- end
21
-
22
- # Mark the message as unread and add orangered to account.
23
- def mark_as_unread
24
- post("/api/unread_message", id: fullname)
25
- end
26
- end
27
- end
28
- end
@@ -1,139 +0,0 @@
1
- require_relative "thing"
2
-
3
- module Redd
4
- module Objects
5
- # A submission made in a subreddit.
6
- class Submission < Thing
7
- include Thing::Editable
8
- include Thing::Hideable
9
- include Thing::Moderatable
10
- include Thing::Refreshable
11
- include Thing::Saveable
12
- include Thing::Votable
13
-
14
- alias_property :nsfw?, :over_18
15
- alias_property :self?, :is_self
16
- alias_property :comments_count, :num_comments
17
-
18
- # The shorter url for sharing.
19
- def short_url
20
- "http://redd.it/#{self[:id]}"
21
- end
22
-
23
- # Whether the comment was gilded.
24
- def gilded?
25
- self[:gilded] > 0
26
- end
27
-
28
- # Mark the thing as Not Suitable For Work.
29
- def mark_as_nsfw
30
- get("/api/marknsfw", id: fullname)
31
- self[:over_18] = true
32
- end
33
-
34
- # No longer mark the thing as Not Suitable For Work.
35
- def unmark_as_nsfw
36
- get("/api/unmarknsfw", id: fullname)
37
- self[:over_18] = false
38
- end
39
- alias_method :mark_as_safe, :unmark_as_nsfw
40
-
41
- # Reply to the thing.
42
- # @param text [String] The text to comment.
43
- # @return [Objects::Comment] The reply.
44
- def add_comment(text)
45
- client.add_comment(self, text)
46
- end
47
-
48
- # Set the submission to "contest mode" (comments are randomly sorted)
49
- def set_contest_mode
50
- post("/api/set_contest_mode", id: fullname, state: true)
51
- end
52
-
53
- # Unset the "contest mode".
54
- def unset_contest_mode
55
- post("/api/set_contest_mode", id: fullname, state: false)
56
- end
57
-
58
- # Set the submission as the sticky post of the subreddit
59
- def set_sticky
60
- post("/api/set_subreddit_sticky", id: fullname, state: true)
61
- self[:stickied] = true
62
- end
63
-
64
- # Unsticky the post from the subreddit
65
- def unset_sticky
66
- post("/api/set_subreddit_sticky", id: fullname, state: false)
67
- self[:stickied] = false
68
- end
69
-
70
- # @return [Listing] The submission's comments.
71
- # @todo Allow for various depths and contexts and what not. Maybe a
72
- # get_comment method?
73
- def comments
74
- refresh! unless @comments
75
- @comments
76
- end
77
- alias_method :replies, :comments
78
-
79
- # Refresh the submission AND its comments.
80
- # @return [Submission] The updated submission.
81
- def refresh!
82
- body = get("/comments/#{id}.json").body
83
- @comments = client.object_from_body(body[1])
84
- deep_merge!(body[0])
85
- end
86
-
87
- # Take a MoreComments and return a listing of comments.
88
- # @param [MoreComments] more The object to expand.
89
- # @return [Listing] A listing of the expanded comments.
90
- def expand_more(more)
91
- response = client.get(
92
- "/api/morechildren",
93
- children: more.join(","),
94
- link_id: fullname
95
- )
96
-
97
- client.object_from_body(
98
- kind: "Listing",
99
- data: {
100
- before: "", after: "",
101
- children: response.body[:json][:data][:things]
102
- }
103
- )
104
- end
105
-
106
- # Get the related articles.
107
- # @param [Hash] params A list of params to send with the request.
108
- # @option params [String] :after Return results after the given
109
- # fullname.
110
- # @option params [String] :before Return results before the given
111
- # fullname.
112
- # @option params [Integer] :count The number of items already seen
113
- # in the listing.
114
- # @option params [1..100] :limit The maximum number of things to
115
- # return.
116
- # @return [Objects::Listing<Objects::Thing>]
117
- def get_related(**params)
118
- related = get("/related/#{id}.json", params).body[1]
119
- client.object_from_body(related)
120
- end
121
-
122
- # Get other articles with the same URL.
123
- # @param [Hash] params A list of params to send with the request.
124
- # @option params [String] :after Return results after the given
125
- # fullname.
126
- # @option params [String] :before Return results before the given
127
- # fullname.
128
- # @option params [Integer] :count The number of items already seen
129
- # in the listing.
130
- # @option params [1..100] :limit The maximum number of things to
131
- # return.
132
- # @return [Objects::Listing<Objects::Submission>]
133
- def get_duplicates(**params)
134
- duplicates = get("/duplicates/#{id}.json", params).body[1]
135
- client.object_from_body(duplicates)
136
- end
137
- end
138
- end
139
- end
@@ -1,330 +0,0 @@
1
- require "fastimage"
2
- require_relative "thing"
3
-
4
- module Redd
5
- module Objects
6
- # A comment made on links.
7
- # @todo #subscribe! and #unsubscribe!
8
- class Subreddit < Thing
9
- include Thing::Messageable
10
- include Thing::Refreshable
11
-
12
- alias_property :header_image, :header_img
13
- alias_property :nsfw?, :over18
14
- alias_property :users_online, :accounts_active
15
- alias_property :type, :subreddit_type
16
- alias_property :times_gilded, :gilded
17
-
18
- # @!group Submissions
19
-
20
- # Submit a link or a text post to the subreddit.
21
- #
22
- # @param [String] title The title of the submission.
23
- # @param [String] captcha A possible captcha result to send if one
24
- # is required.
25
- # @param [String] identifier The identifier for the captcha if one
26
- # is required.
27
- # @param [String] text The text of the self-post.
28
- # @param [String] url The URL of the link.
29
- # @param [Boolean] resubmit Whether to post a link to the subreddit
30
- # despite it having been posted there before (you monster).
31
- # @param [Boolean] sendreplies Whether to send the replies to your
32
- # inbox.
33
- # @return [Objects::Thing] The returned result (url, id and name).
34
- def submit(
35
- title, captcha = nil, identifier = nil, text: nil, url: nil,
36
- resubmit: false, sendreplies: true
37
- )
38
-
39
- params = {
40
- extension: "json", title: title, sr: display_name,
41
- resubmit: resubmit, sendreplies: sendreplies
42
- }
43
-
44
- params.merge!(captcha: captcha, iden: identifier) if captcha
45
- params[:kind], params[:text] = :self, text if text
46
- params[:kind], params[:url] = :link, url if url
47
-
48
- response = post("/api/submit", params)
49
- Objects::Thing.new(self, response.body[:json][:data])
50
- end
51
-
52
- # Add a comment to the submission.
53
- # @param text [String] The text to comment.
54
- # @return [Objects::Comment] The reply.
55
- def add_comment(text)
56
- client.add_comment(self, text)
57
- end
58
-
59
- # @!endgroup
60
-
61
- # @!group Stylesheets
62
-
63
- # @return [String] The url for the subreddit's stylesheet.
64
- def stylesheet_url
65
- get("/r/#{display_name}/stylesheet").headers["location"]
66
- end
67
-
68
- # @return [String] The css for the subreddit.
69
- def stylesheet
70
- Faraday.get(stylesheet_url).body
71
- end
72
-
73
- # Edit the subreddit's stylesheet
74
- #
75
- # @param [String] contents The new CSS.
76
- # @param [String] reason Why you modified the stylesheet.
77
- # @author Takashi M (@beatak) and Avinash Dwarapu (@avidw)
78
- # @note https://www.reddit.com/r/naut/about/stylesheet/ is a good place
79
- # to test if you have an error.
80
- def edit_stylesheet(contents, reason = nil)
81
- params = {op: "save", stylesheet_contents: contents}
82
- params[:reason] = reason if reason
83
- post("/r/#{display_name}/api/subreddit_stylesheet", params)
84
- end
85
-
86
- # @!endgroup
87
-
88
- # @!group Invites
89
-
90
- # Accept a moderator invite from a subreddit.
91
- def accept_moderator_invite!
92
- post("/r/#{display_name}/api/accept_moderator_invite")
93
- end
94
-
95
- # Stop being a contributor of the subreddit.
96
- def leave_contributor_status!
97
- post("/api/leavecontributor", id: fullname)
98
- end
99
-
100
- # Stop being a moderator of the subreddit.
101
- def leave_moderator_status!
102
- post("/api/leavemoderator", id: fullname)
103
- end
104
-
105
- # @!endgroup
106
-
107
- # @!group Flairs
108
-
109
- # Get a list of everbody on the subreddit with a user flair.
110
- #
111
- # @param [Hash] params A list of params to send with the request.
112
- # @option params [String] :after Return results after the given
113
- # fullname.
114
- # @option params [String] :before Return results before the given
115
- # fullname.
116
- # @option params [Integer] :count The number of items already seen in the
117
- # listing.
118
- # @option params [1..1000] :limit The maximum number of things to
119
- # return.
120
- # @option params [String] :name The username when getting the flair of
121
- # just one user.
122
- # @return [Objects::Listing<Hash>] A listing of flair hashes.
123
- def get_flairlist(**params)
124
- body = get("/r/#{display_name}/api/flairlist.json", params).body
125
- client.object_from_body(
126
- kind: "Listing",
127
- data: {
128
- children: body[:users],
129
- before: body[:prev],
130
- after: body[:next]
131
- }
132
- )
133
- end
134
-
135
- # Get the flair of a user.
136
- #
137
- # @param [Objects::User, String] user The user to find.
138
- # @return [Hash, nil] Flair info about the user or nil if nobody was
139
- # found.
140
- def get_flair(user)
141
- username = client.property(user, :name)
142
- flair = get_flairlist(user: username).first
143
- flair if flair[:user].casecmp(username) == 0
144
- end
145
-
146
- # Set the flair of a user or link.
147
- # @param [Objects::Subreddit, Objects::User] thing The user or link to
148
- # set the flair to.
149
- # @param [:user, :link] type The type of thing.
150
- # @param [String] text The text to set the flair to.
151
- # @param [String] css_class The css_class of the flair.
152
- def set_flair(thing, type = nil, text = nil, css_class = nil)
153
- params = {text: text, css_class: css_class}
154
- if thing.is_a?(Objects::User) || type == :user
155
- params[:name] = client.property(thing, :name)
156
- elsif thing.is_a?(Objects::Submission) || type == :link
157
- params[:link] = client.property(thing, :fullname)
158
- else
159
- fail "You should provide a proper type."
160
- end
161
-
162
- post("/r/#{display_name}/api/flair", params)
163
- end
164
-
165
- # @!endgroup
166
-
167
- # @!group Listings
168
-
169
- # @!method get_hot(**params)
170
- # @!method get_new(**params)
171
- # @!method get_top(**params)
172
- # @!method get_controversial(**params)
173
- # @!method get_comments(**params)
174
- #
175
- # Get the appropriate listing.
176
- # @param params [Hash] A list of params to send with the request.
177
- # @option params [String] :after Return results after the given
178
- # fullname.
179
- # @option params [String :before Return results before the given
180
- # fullname.
181
- # @option params [Integer] :count (0) The number of items already seen
182
- # in the listing.
183
- # @option params [1..100] :limit (25) The maximum number of things to
184
- # return.
185
- # @option params [:hour, :day, :week, :month, :year, :all] :t The
186
- # time period to consider when sorting.
187
- #
188
- # @note The option :t only applies to the top and controversial sorts.
189
- # @return [Objects::Listing<Objects::Thing>]
190
- %w(hot new top controversial comments).each do |sort|
191
- define_method :"get_#{sort}" do |**params|
192
- client.send(:"get_#{sort}", self, **params)
193
- end
194
- end
195
-
196
- # Search.
197
- # @param query [String] The query string.
198
- # @param params [Hash] A list of params to send with the request.
199
- # @option params [String] :after Return results after the given
200
- # fullname.
201
- # @option params [String :before Return results before the given
202
- # fullname.
203
- # @option params [Integer] :count The number of items already seen in
204
- # the listing.
205
- # @option params [1..100] :limit The maximum number of things to
206
- # return.
207
- # @option params [:cloudsearch, :lucene, :plain] :syntax The type of
208
- # syntax to use.
209
- # @option params [:relevance, :new, :hot, :top, :comments] :sort The
210
- # way to sort the results.
211
- # @option params [:hour, :day, :week, :month, :year, :all] :t The
212
- # time period to consider when sorting.
213
- #
214
- # @note The option :t only applies to the top and controversial sorts.
215
- # @return [Objects::Listing<Objects::Thing>]
216
- def search(query, **params)
217
- client.search(query, self, **params)
218
- end
219
-
220
- # @!endgroup
221
-
222
- # @!group Moderation
223
-
224
- # @!method get_reports(**params)
225
- # @!method get_spam(**params)
226
- # @!method get_modqueue(**params)
227
- # @!method get_unmoderated(**params)
228
- # @!method get_edited(**params)
229
- #
230
- # Get the appropriate moderator listing.
231
- # @param [Hash] params A list of params to send with the request.
232
- # @option params [String] :after Return results after the given
233
- # fullname.
234
- # @option params [String] :before Return results before the given
235
- # fullname.
236
- # @option params [Integer] :count The number of items already seen
237
- # in the listing.
238
- # @option params [1..100] :limit The maximum number of things to
239
- # return.
240
- # @option params :location No idea what this does.
241
- # @option params [:links, :comments] :only The type of things to show.
242
- #
243
- # @return [Objects::Listing<Objects::Thing>]
244
- # @see https://www.reddit.com/dev/api#GET_about_{location}
245
- %w(reports spam modqueue unmoderated edited).each do |sort|
246
- define_method :"get_#{sort}" do |**params|
247
- client.request_object(
248
- :get, "/r/#{display_name}/about/#{sort}", params
249
- )
250
- end
251
- end
252
-
253
- # @return [Objects::Base] The current settings of a subreddit.
254
- def admin_about
255
- client.request_object(:get, "/r/#{display_name}/about/edit.json")
256
- end
257
-
258
- # @return [Objects::Base] The current moderators of a subreddit.
259
- def moderator_about
260
- body = get("/r/#{display_name}/about/moderators.json").body
261
- client.object_from_body(
262
- kind: "Listing",
263
- data: {
264
- children: body[:data][:children]
265
- }
266
- )
267
- end
268
-
269
- # Edit the subreddit's settings
270
- # @param [Hash] attributes The subreddit's new settings.
271
- # @note This method may make additional requests if not all of the
272
- # required attributes are provided. Take a look at the source for the
273
- # required attributes required to avoid making the additional request.
274
- # @see https://github.com/alaycock/MeetCal-bot/blob/master/serverInfo.conf
275
- def admin_edit(attributes)
276
- params = {
277
- # Subreddit name
278
- sr: fullname,
279
- # Apparently useless options
280
- show_cname_sidebar: true,
281
- :"header-title" => title
282
- }
283
-
284
- required = %i(
285
- allow_top collapse_deleted_comments comment_score_hide_mins
286
- css_on_cname description exclude_banned_modqueue lang link_type name
287
- over_18 public_description public_traffic show_media spam_comments
288
- spam_links spam_selfposts submit_link_label submit_text
289
- submit_text_label title type wiki_edit_age wiki_edit_karma wikimode
290
- )
291
-
292
- if required.all? { |key| attributes.key?(key) }
293
- params.merge!(attributes)
294
- else
295
- about = admin_about
296
- final = about
297
- .select { |k, _| required.include?(k) }
298
- .merge(
299
- name: display_name,
300
- type: about[:subreddit_type],
301
- lang: about[:language],
302
- link_type: about[:content_options],
303
- allow_top: true,
304
- css_on_cname: true
305
- )
306
- .merge(attributes)
307
- params.merge!(final)
308
- end
309
-
310
- post("/api/site_admin", params)
311
- end
312
-
313
- # Add or replace the subreddit image or header logo.
314
- # @param [String, IO] file The path/url to the file or the file itself.
315
- # @param [String] name The name of the uploaded file.
316
- # @return [String] The url of the image on reddit's CDN.
317
- def upload_image(file, name = nil)
318
- io = (file.is_a?(IO) ? file : File.open(file, "r"))
319
- type = FastImage.type(io)
320
- payload = Faraday::UploadIO.new(io, "image/#{type}")
321
-
322
- params = {file: payload, header: (name ? 0 : 1), img_type: type}
323
- params[:name] = name if name
324
- post("/r/#{display_name}/api/upload_sr_img", params).body[:img_src]
325
- end
326
-
327
- # @!endgroup
328
- end
329
- end
330
- end