disrb 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +28 -1
- data/lib/disrb/guild.rb +186 -185
- data/lib/disrb/logger.rb +27 -13
- data/lib/disrb/message.rb +3 -3
- data/lib/disrb/user.rb +37 -39
- data/lib/disrb.rb +172 -68
- metadata +1 -1
data/lib/disrb.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'net/http'
|
|
4
3
|
require 'json'
|
|
5
4
|
require 'async'
|
|
6
5
|
require 'async/http/endpoint'
|
|
@@ -11,9 +10,6 @@ require 'disrb/logger'
|
|
|
11
10
|
require 'disrb/user'
|
|
12
11
|
require 'disrb/message'
|
|
13
12
|
|
|
14
|
-
# TODO: If there is more than 1 optional parameter in a function, I should change it from setting the default value via
|
|
15
|
-
# = to : so that the user can pass only the parameters they want to set
|
|
16
|
-
|
|
17
13
|
# DiscordApi
|
|
18
14
|
# The class that contains everything that interacts with the Discord API.
|
|
19
15
|
class DiscordApi
|
|
@@ -25,41 +21,49 @@ class DiscordApi
|
|
|
25
21
|
@authorization_token_type = authorization_token_type
|
|
26
22
|
@authorization_token = authorization_token
|
|
27
23
|
@authorization_header = "#{authorization_token_type} #{authorization_token}"
|
|
28
|
-
url = URI("#{@base_url}/applications/@me")
|
|
29
|
-
headers = { 'Authorization': @authorization_header }
|
|
30
|
-
@application_id = JSON.parse(Net::HTTP.get(url, headers))['id']
|
|
31
24
|
@interaction_created = false
|
|
32
25
|
@interaction = {}
|
|
33
26
|
if verbosity_level.nil?
|
|
34
|
-
@verbosity_level =
|
|
27
|
+
@verbosity_level = 4
|
|
35
28
|
elsif verbosity_level.is_a?(String)
|
|
36
29
|
case verbosity_level.downcase
|
|
37
30
|
when 'all'
|
|
38
|
-
@verbosity_level =
|
|
31
|
+
@verbosity_level = 5
|
|
39
32
|
when 'info'
|
|
40
|
-
@verbosity_level =
|
|
33
|
+
@verbosity_level = 4
|
|
41
34
|
when 'warning'
|
|
42
|
-
@verbosity_level =
|
|
35
|
+
@verbosity_level = 3
|
|
43
36
|
when 'error'
|
|
37
|
+
@verbosity_level = 2
|
|
38
|
+
when 'fatal_error'
|
|
44
39
|
@verbosity_level = 1
|
|
45
40
|
when 'none'
|
|
46
41
|
@verbosity_level = 0
|
|
47
42
|
else
|
|
48
43
|
Logger2.s_error("Unknown verbosity level: #{verbosity_level}. Defaulting to 'info'.")
|
|
49
|
-
@verbosity_level =
|
|
44
|
+
@verbosity_level = 4
|
|
50
45
|
end
|
|
51
46
|
elsif verbosity_level.is_a?(Integer)
|
|
52
|
-
if verbosity_level >= 0 && verbosity_level <=
|
|
47
|
+
if verbosity_level >= 0 && verbosity_level <= 5
|
|
53
48
|
@verbosity_level = verbosity_level
|
|
54
49
|
else
|
|
55
50
|
Logger2.s_error("Unknown verbosity level: #{verbosity_level}. Defaulting to 'info'.")
|
|
56
|
-
@verbosity_level =
|
|
51
|
+
@verbosity_level = 4
|
|
57
52
|
end
|
|
58
53
|
else
|
|
59
54
|
Logger2.s_error("Unknown verbosity level: #{verbosity_level}. Defaulting to 'info'.")
|
|
60
|
-
@verbosity_level =
|
|
55
|
+
@verbosity_level = 4
|
|
61
56
|
end
|
|
62
57
|
@logger = Logger2.new(@verbosity_level)
|
|
58
|
+
url = "#{@base_url}/applications/@me"
|
|
59
|
+
headers = { 'Authorization': @authorization_header }
|
|
60
|
+
response = DiscordApi.get(url, headers)
|
|
61
|
+
if response.status == 200
|
|
62
|
+
@application_id = JSON.parse(response.body)['id']
|
|
63
|
+
else
|
|
64
|
+
@logger.fatal_error("Failed to get application ID with response: #{response.body}")
|
|
65
|
+
exit
|
|
66
|
+
end
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
def self.handle_query_strings(query_string_hash)
|
|
@@ -91,21 +95,25 @@ class DiscordApi
|
|
|
91
95
|
|
|
92
96
|
def create_guild_application_command(guild_id, name, name_localizations: nil, description: nil,
|
|
93
97
|
description_localizations: nil, options: nil, default_member_permissions: nil,
|
|
94
|
-
default_permission:
|
|
98
|
+
default_permission: nil, type: nil, nsfw: nil)
|
|
95
99
|
output = {}
|
|
96
100
|
output[:name] = name
|
|
97
101
|
output[:name_localizations] = name_localizations unless name_localizations.nil?
|
|
98
102
|
output[:description] = description unless description.nil?
|
|
99
103
|
output[:description_localizations] = description_localizations unless description_localizations.nil?
|
|
100
104
|
output[:options] = options unless options.nil?
|
|
101
|
-
output[:default_permission] = default_permission
|
|
102
|
-
output[:type] = type
|
|
103
|
-
output[:nsfw] = nsfw
|
|
105
|
+
output[:default_permission] = default_permission unless default_permission.nil?
|
|
106
|
+
output[:type] = type unless default_permission.nil?
|
|
107
|
+
output[:nsfw] = nsfw unless nsfw.nil?
|
|
104
108
|
output[:default_member_permissions] = default_member_permissions unless default_member_permissions.nil?
|
|
105
|
-
url =
|
|
109
|
+
url = "#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands"
|
|
106
110
|
data = JSON.generate(output)
|
|
107
111
|
headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
|
|
108
|
-
|
|
112
|
+
response = DiscordApi.post(url, data, headers)
|
|
113
|
+
return response unless response.status != 201 || response.status != 200
|
|
114
|
+
|
|
115
|
+
@logger.error("Failed to create guild application command in guild with ID #{guild_id}. Response: #{response.body}")
|
|
116
|
+
response
|
|
109
117
|
end
|
|
110
118
|
|
|
111
119
|
def create_guild_application_commands(application_commands_array)
|
|
@@ -124,24 +132,28 @@ class DiscordApi
|
|
|
124
132
|
|
|
125
133
|
def create_global_application_command(name, name_localizations: nil, description: nil,
|
|
126
134
|
description_localizations: nil, options: nil,
|
|
127
|
-
default_member_permissions: nil, default_permission:
|
|
128
|
-
integration_types: nil, contexts: nil, type:
|
|
135
|
+
default_member_permissions: nil, default_permission: nil,
|
|
136
|
+
integration_types: nil, contexts: nil, type: nil, nsfw: nil)
|
|
129
137
|
output = {}
|
|
130
138
|
output[:name] = name
|
|
131
139
|
output[:name_localizations] = name_localizations unless name_localizations.nil?
|
|
132
140
|
output[:description] = description unless description.nil?
|
|
133
141
|
output[:description_localizations] = description_localizations unless description_localizations.nil?
|
|
134
142
|
output[:options] = options unless options.nil?
|
|
135
|
-
output[:default_permission] = default_permission
|
|
136
|
-
output[:type] = type
|
|
137
|
-
output[:nsfw] = nsfw
|
|
143
|
+
output[:default_permission] = default_permission unless default_permission.nil?
|
|
144
|
+
output[:type] = type unless type.nil?
|
|
145
|
+
output[:nsfw] = nsfw unless nsfw.nil?
|
|
138
146
|
output[:default_member_permissions] = default_member_permissions unless default_member_permissions.nil?
|
|
139
147
|
output[:integration_types] = integration_types unless integration_types.nil?
|
|
140
148
|
output[:contexts] = contexts unless contexts.nil?
|
|
141
|
-
url =
|
|
149
|
+
url = "#{@base_url}/applications/#{@application_id}/commands"
|
|
142
150
|
data = JSON.generate(output)
|
|
143
151
|
headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
|
|
144
|
-
|
|
152
|
+
response = DiscordApi.post(url, data, headers)
|
|
153
|
+
return response unless response.status != 201 || response.status != 200
|
|
154
|
+
|
|
155
|
+
@logger.error("Failed to create global application command. Response: #{response.body}")
|
|
156
|
+
response
|
|
145
157
|
end
|
|
146
158
|
|
|
147
159
|
def create_global_application_commands(application_commands_array)
|
|
@@ -158,112 +170,173 @@ class DiscordApi
|
|
|
158
170
|
end
|
|
159
171
|
end
|
|
160
172
|
|
|
161
|
-
def edit_global_application_command(command_id, name
|
|
162
|
-
description_localizations
|
|
163
|
-
default_permission:
|
|
173
|
+
def edit_global_application_command(command_id, name: nil, name_localizations: nil, description: nil,
|
|
174
|
+
description_localizations: nil, options: nil, default_member_permissions: nil,
|
|
175
|
+
default_permission: nil, integration_types: nil, contexts: nil, nsfw: nil)
|
|
164
176
|
output = {}
|
|
165
177
|
output[:name] = name
|
|
166
178
|
output[:name_localizations] = name_localizations unless name_localizations.nil?
|
|
167
179
|
output[:description] = description unless description.nil?
|
|
168
180
|
output[:description_localizations] = description_localizations unless description_localizations.nil?
|
|
169
181
|
output[:options] = options unless options.nil?
|
|
170
|
-
output[:default_permission] = default_permission
|
|
171
|
-
output[:nsfw] = nsfw
|
|
182
|
+
output[:default_permission] = default_permission unless default_permission.nil?
|
|
183
|
+
output[:nsfw] = nsfw unless nsfw.nil?
|
|
172
184
|
output[:default_member_permissions] = default_member_permissions unless default_member_permissions.nil?
|
|
173
185
|
output[:integration_types] = integration_types unless integration_types.nil?
|
|
174
186
|
output[:contexts] = contexts unless contexts.nil?
|
|
175
|
-
url =
|
|
187
|
+
url = "#{@base_url}/applications/#{@application_id}/commands/#{command_id}"
|
|
176
188
|
data = JSON.generate(output)
|
|
177
189
|
headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
|
|
178
|
-
|
|
190
|
+
response = DiscordApi.patch(url, data, headers)
|
|
191
|
+
return response unless response.status != 200
|
|
192
|
+
|
|
193
|
+
@logger.error("Failed to edit global application command with ID #{command_id}. Response: #{response.body}")
|
|
194
|
+
response
|
|
179
195
|
end
|
|
180
196
|
|
|
181
|
-
def edit_guild_application_command(guild_id, command_id, name
|
|
182
|
-
description_localizations
|
|
183
|
-
default_permission:
|
|
197
|
+
def edit_guild_application_command(guild_id, command_id, name: nil, name_localizations: nil, description: nil,
|
|
198
|
+
description_localizations: nil, options: nil, default_member_permissions: nil,
|
|
199
|
+
default_permission: nil, nsfw: nil)
|
|
184
200
|
output = {}
|
|
185
201
|
output[:name] = name
|
|
186
202
|
output[:name_localizations] = name_localizations unless name_localizations.nil?
|
|
187
203
|
output[:description] = description unless description.nil?
|
|
188
204
|
output[:description_localizations] = description_localizations unless description_localizations.nil?
|
|
189
205
|
output[:options] = options unless options.nil?
|
|
190
|
-
output[:default_permission] = default_permission
|
|
191
|
-
output[:nsfw] = nsfw
|
|
206
|
+
output[:default_permission] = default_permission unless default_permission.nil?
|
|
207
|
+
output[:nsfw] = nsfw unless nsfw.nil?
|
|
192
208
|
output[:default_member_permissions] = default_member_permissions unless default_member_permissions.nil?
|
|
193
|
-
url =
|
|
209
|
+
url = "#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/#{command_id}"
|
|
194
210
|
data = JSON.generate(output)
|
|
195
211
|
headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
|
|
196
|
-
|
|
212
|
+
response = DiscordApi.patch(url, data, headers)
|
|
213
|
+
return response unless response.status != 200
|
|
214
|
+
|
|
215
|
+
@logger.error("Failed to edit guild application command with ID #{command_id}. Response: #{response.body}")
|
|
216
|
+
response
|
|
197
217
|
end
|
|
198
218
|
|
|
199
219
|
def delete_global_application_command(command_id)
|
|
200
|
-
url =
|
|
220
|
+
url = "#{@base_url}/applications/#{@application_id}/commands/#{command_id}"
|
|
201
221
|
headers = { 'Authorization': @authorization_header }
|
|
202
|
-
|
|
222
|
+
response = DiscordApi.delete(url, headers)
|
|
223
|
+
return response unless response.status != 204
|
|
224
|
+
|
|
225
|
+
@logger.error("Failed to delete global application command with ID #{command_id}. Response: #{response.body}")
|
|
226
|
+
response
|
|
203
227
|
end
|
|
204
228
|
|
|
205
229
|
def delete_guild_application_command(guild_id, command_id)
|
|
206
|
-
url =
|
|
230
|
+
url = "#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/#{command_id}"
|
|
207
231
|
headers = { 'Authorization': @authorization_header }
|
|
208
|
-
|
|
232
|
+
response = DiscordApi.delete(url, headers)
|
|
233
|
+
return response unless response.status != 204
|
|
234
|
+
|
|
235
|
+
@logger.error("Failed to delete guild application command with ID #{command_id} in guild with ID #{guild_id}. " \
|
|
236
|
+
"Response: #{response.body}")
|
|
209
237
|
end
|
|
210
238
|
|
|
211
|
-
def get_guild_application_commands(guild_id, with_localizations:
|
|
212
|
-
|
|
213
|
-
|
|
239
|
+
def get_guild_application_commands(guild_id, with_localizations: nil)
|
|
240
|
+
query_string_hash = {}
|
|
241
|
+
query_string_hash[:with_localizations] = with_localizations unless with_localizations.nil?
|
|
242
|
+
query_string = DiscordApi.handle_query_strings(query_string_hash)
|
|
243
|
+
url = "#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands#{query_string}"
|
|
214
244
|
headers = { 'Authorization': @authorization_header }
|
|
215
|
-
|
|
245
|
+
response = DiscordApi.get(url, headers)
|
|
246
|
+
return response unless response.status != 200
|
|
247
|
+
|
|
248
|
+
@logger.error("Failed to get guild application commands for guild with ID #{guild_id}. Response: #{response.body}")
|
|
249
|
+
response
|
|
216
250
|
end
|
|
217
251
|
|
|
218
252
|
def get_global_application_commands(with_localizations: false)
|
|
219
|
-
|
|
253
|
+
query_string_hash = {}
|
|
254
|
+
query_string_hash[:with_localizations] = with_localizations unless with_localizations.nil?
|
|
255
|
+
query_string = DiscordApi.handle_query_strings(query_string_hash)
|
|
256
|
+
url = "#{@base_url}/applications/#{@application_id}/commands#{query_string}"
|
|
220
257
|
headers = { 'Authorization': @authorization_header }
|
|
221
|
-
|
|
258
|
+
response = DiscordApi.get(url, headers)
|
|
259
|
+
return response unless response.status != 200
|
|
260
|
+
|
|
261
|
+
@logger.error("Failed to get global application commands. Response: #{response.body}")
|
|
262
|
+
response
|
|
222
263
|
end
|
|
223
264
|
|
|
224
265
|
def get_global_application_command(command_id)
|
|
225
|
-
url =
|
|
266
|
+
url = "#{@base_url}/applications/#{@application_id}/commands/#{command_id}"
|
|
226
267
|
headers = { 'Authorization': @authorization_header }
|
|
227
|
-
|
|
268
|
+
response = DiscordApi.get(url, headers)
|
|
269
|
+
return response unless response.status != 200
|
|
270
|
+
|
|
271
|
+
@logger.error("Failed to get global application command with ID #{command_id}. Response: #{response.body}")
|
|
272
|
+
response
|
|
228
273
|
end
|
|
229
274
|
|
|
230
275
|
def get_guild_application_command(guild_id, command_id)
|
|
231
|
-
url =
|
|
276
|
+
url = "#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/#{command_id}"
|
|
232
277
|
headers = { 'Authorization': @authorization_header }
|
|
233
|
-
|
|
278
|
+
response = DiscordApi.get(url, headers)
|
|
279
|
+
return response unless response.status != 200
|
|
280
|
+
|
|
281
|
+
@logger.error("Failed to get guild application command with ID #{command_id}. Response: #{response.body}")
|
|
282
|
+
response
|
|
234
283
|
end
|
|
235
284
|
|
|
236
285
|
def bulk_overwrite_global_application_commands(commands)
|
|
237
|
-
url =
|
|
286
|
+
url = "#{@base_url}/applications/#{@application_id}/commands"
|
|
238
287
|
data = JSON.generate(commands)
|
|
239
288
|
headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
|
|
240
|
-
|
|
289
|
+
response = DiscordApi.put(url, data, headers)
|
|
290
|
+
return response unless response.status != 200
|
|
291
|
+
|
|
292
|
+
@logger.error("Failed to bulk overwrite global application commands. Response: #{response.body}")
|
|
293
|
+
response
|
|
241
294
|
end
|
|
242
295
|
|
|
243
296
|
def bulk_overwrite_guild_application_commands(guild_id, commands)
|
|
244
|
-
url =
|
|
297
|
+
url = "#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands"
|
|
245
298
|
data = JSON.generate(commands)
|
|
246
299
|
headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
|
|
247
|
-
|
|
300
|
+
response = DiscordApi.put(url, data, headers)
|
|
301
|
+
return response unless response.status != 200
|
|
302
|
+
|
|
303
|
+
@logger.error("Failed to bulk overwrite guild application commands in guild with ID #{guild_id}. " \
|
|
304
|
+
"Response: #{response.body}")
|
|
305
|
+
response
|
|
248
306
|
end
|
|
249
307
|
|
|
250
308
|
def get_guild_application_command_permissions(guild_id)
|
|
251
|
-
url =
|
|
309
|
+
url = "#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/permissions"
|
|
252
310
|
headers = { 'Authorization': @authorization_header }
|
|
253
|
-
|
|
311
|
+
response = DiscordApi.get(url, headers)
|
|
312
|
+
return response unless response.status != 200
|
|
313
|
+
|
|
314
|
+
@logger.error("Failed to get guild application command permissions for guild with ID #{guild_id}. " \
|
|
315
|
+
"Response: #{response.body}")
|
|
316
|
+
response
|
|
254
317
|
end
|
|
255
318
|
|
|
256
319
|
def get_application_command_permissions(guild_id, command_id)
|
|
257
|
-
url =
|
|
320
|
+
url = "#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/#{command_id}/permissions"
|
|
258
321
|
headers = { 'Authorization': @authorization_header }
|
|
259
|
-
|
|
322
|
+
response = DiscordApi.get(url, headers)
|
|
323
|
+
return response unless response.status != 200
|
|
324
|
+
|
|
325
|
+
@logger.error("Failed to get appliaction command permissions for command with ID #{command_id} in guild with ID " \
|
|
326
|
+
"#{guild_id}. Response: #{response.body}")
|
|
327
|
+
response
|
|
260
328
|
end
|
|
261
329
|
|
|
262
330
|
def edit_application_command_permissions(guild_id, command_id, permissions)
|
|
263
|
-
url =
|
|
331
|
+
url = "#{@base_url}/applications/#{@application_id}/guilds/#{guild_id}/commands/#{command_id}/permissions"
|
|
264
332
|
data = JSON.generate(permissions)
|
|
265
333
|
headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
|
|
266
|
-
|
|
334
|
+
response = DiscordApi.put(url, data, headers)
|
|
335
|
+
return response unless response.status != 200
|
|
336
|
+
|
|
337
|
+
@logger.error("Failed to edit application command permissions for command with ID #{command_id} in guild with ID " \
|
|
338
|
+
"#{guild_id}. Response: #{response.body}")
|
|
339
|
+
response
|
|
267
340
|
end
|
|
268
341
|
|
|
269
342
|
def connect_gateway(activities: nil, os: nil, browser: nil, device: nil, intents: nil, presence_since: nil,
|
|
@@ -346,7 +419,13 @@ class DiscordApi
|
|
|
346
419
|
rescue_connection, sequence, resume_gateway_url, session_id = nil
|
|
347
420
|
loop do
|
|
348
421
|
url = if rescue_connection.nil?
|
|
349
|
-
|
|
422
|
+
response = DiscordApi.get("#{@base_url}/gateway")
|
|
423
|
+
if response.status == 200
|
|
424
|
+
"#{JSON.parse(response)['url']}/?v=#{@api_version}&encoding=json"
|
|
425
|
+
else
|
|
426
|
+
@logger.fatal_error("Failed to get gateway URL. Response: #{response.body}")
|
|
427
|
+
exit
|
|
428
|
+
end
|
|
350
429
|
else
|
|
351
430
|
"#{rescue_connection[:resume_gateway_url]}/?v=#{@api_version}&encoding=json"
|
|
352
431
|
end
|
|
@@ -595,7 +674,6 @@ class DiscordApi
|
|
|
595
674
|
intents.reduce(0) { |acc, n| acc | n }
|
|
596
675
|
end
|
|
597
676
|
|
|
598
|
-
# TODO: Transition from Net::HTTP to below wrapper methods
|
|
599
677
|
def self.get(url, headers = nil)
|
|
600
678
|
split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
|
|
601
679
|
@logger.error("Empty/invalid URL provided: #{url}. Cannot perform GET request.") if split_url.empty?
|
|
@@ -634,4 +712,30 @@ class DiscordApi
|
|
|
634
712
|
conn.post('', data)
|
|
635
713
|
end
|
|
636
714
|
end
|
|
715
|
+
|
|
716
|
+
def self.patch(url, data, headers = nil)
|
|
717
|
+
split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
|
|
718
|
+
@logger.error("Empty/invalid URL provided: #{url}. Cannot perform PATCH request.") if split_url.empty?
|
|
719
|
+
host = split_url[0]
|
|
720
|
+
path = split_url[1] if split_url[1]
|
|
721
|
+
conn = Faraday.new(url: host, headers: headers)
|
|
722
|
+
if path
|
|
723
|
+
conn.patch(path, data)
|
|
724
|
+
else
|
|
725
|
+
conn.patch('', data)
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
def self.put(url, data, headers = nil)
|
|
730
|
+
split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
|
|
731
|
+
@logger.error("Empty/invalid URL provided: #{url}. Cannot perform PUT request.") if split_url.empty?
|
|
732
|
+
host = split_url[0]
|
|
733
|
+
path = split_url[1] if split_url[1]
|
|
734
|
+
conn = Faraday.new(url: host, headers: headers)
|
|
735
|
+
if path
|
|
736
|
+
conn.put(path, data)
|
|
737
|
+
else
|
|
738
|
+
conn.put('', data)
|
|
739
|
+
end
|
|
740
|
+
end
|
|
637
741
|
end
|