discordrb 2.0.4 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of discordrb might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 86b237c8122ce65285f26a72379d063594d64468
4
- data.tar.gz: 676f4b7428f7f01a670eff05c85d7a14092affab
3
+ metadata.gz: e61613220092ee639a9cd4db8f5fad0d7485b5a8
4
+ data.tar.gz: 008902a804d2938eff3d369bb500d6a755ccb605
5
5
  SHA512:
6
- metadata.gz: 0f18865d96e89381d9aad624558331915f90bfe622f9ed0e280d7c04a3702676548e0ab093b38523fb620aef0212c5415dd39e8d14428950d38ce923d706bc10
7
- data.tar.gz: 54533279a8439c4dbadefe1bb2e70f6c660565f10dcb544fa6aa389e9ccaf7b0611857a5f8bd1055e771fc11b555b05ab030d11b4ad9c837752e0fd851cfce73
6
+ metadata.gz: fedc89d8c0ccb3229425382413e2ce4bb946e676924542092290985f4ba11b2d5e402ce210df590fbeb29393536593ef8e09d5189cd795f225b40156fa052e55
7
+ data.tar.gz: 9793bdc3c7ed44daa090145e0bf693c0ab12070ebe1e73f89204a0c8423c6d7e734461caef25b461fee1a0893a1c9717c7fda6dc2fbc7badbe458eeed44e2038
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.0
4
+
5
+ - API support for the April 29 Discord update, which was the first feature update in a while with more than a few additions to the API, was added. This includes: ([#111](https://github.com/meew0/discordrb/pull/111))
6
+ - Members' nicknames can now be set and read (`Member#nick`) and updates to them are being tracked.
7
+ - Roles now have a `mentionable?` property and a `mention` utility method.
8
+ - `Message` now tracks a message's role mentions.
9
+ - The internal REST rate limit handler was updated:
10
+ - It now tracks message rate limits server wide to properly handle new bot account rate limits. ([#100](https://github.com/meew0/discordrb/issues/100))
11
+ - It now keeps track of all requests, even those that are known not to be rate limited (it just won't do anything to them). This allows for more flexibility should future rate limits be added.
12
+ - Guild sharding is now supported using the optional `shard_id` and `num_shards` to bot initializers. Read about it here: https://github.com/hammerandchisel/discord-api-docs/issues/17 ([#98](https://github.com/meew0/discordrb/issues/98))
13
+ - Commands can now require users to have specific action permissions to be able to execute them using the `:required_permissions` attribute. ([#104](https://github.com/meew0/discordrb/issues/104) / [#112](https://github.com/meew0/discordrb/pull/112))
14
+ - A `heartbeat` event was added that gets triggered every now and then to allow for roughly periodic actions. ([#110](https://github.com/meew0/discordrb/pull/110))
15
+ - Prefixes are now more flexible in the format they can have - arrays and callables are now allowed as well. Read the documentation for more info.([#107](https://github.com/meew0/discordrb/issues/107) / [#109](https://github.com/meew0/discordrb/pull/109))
16
+
3
17
  ## 2.0.4
4
18
 
5
19
  - Added a utility method `Invite#url` ([#86](https://github.com/meew0/discordrb/issues/86)/[#101](https://github.com/meew0/discordrb/pull/101), thanks @PoVa)
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015 meew0
3
+ Copyright (c) 2015-2016 meew0
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/lib/discordrb.rb CHANGED
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- unless ENV['DISCORDRB_V2_MESSAGE']
4
- puts "You're using version 2 of discordrb which has some breaking changes!"
5
- puts "Don't worry if your bot crashes, you can find a list and migration advice here:"
6
- puts ' https://github.com/meew0/discordrb/blob/master/CHANGELOG.md#200'
7
- puts 'This message will go away in version 2.1 or can be disabled by setting the DISCORDRB_V2_MESSAGE environment variable.'
8
- end
9
-
10
3
  require 'discordrb/version'
11
4
  require 'discordrb/bot'
12
5
  require 'discordrb/commands/command_bot'
data/lib/discordrb/api.rb CHANGED
@@ -43,9 +43,7 @@ module Discordrb::API
43
43
 
44
44
  # Resets all rate limit mutexes
45
45
  def reset_mutexes
46
- @mutexes = {
47
- message: Mutex.new
48
- }
46
+ @mutexes = {}
49
47
  end
50
48
 
51
49
  # Performs a RestClient request.
@@ -68,6 +66,8 @@ module Discordrb::API
68
66
 
69
67
  begin
70
68
  if key
69
+ @mutexes[key] = Mutex.new unless @mutexes[key]
70
+
71
71
  # Lock and unlock, i. e. wait for the mutex to unlock and don't do anything with it afterwards
72
72
  @mutexes[key].lock
73
73
  @mutexes[key].unlock
@@ -106,7 +106,7 @@ module Discordrb::API
106
106
  # Ban a user from a server and delete their messages from the last message_days days
107
107
  def ban_user(token, server_id, user_id, message_days)
108
108
  request(
109
- nil,
109
+ __method__,
110
110
  :put,
111
111
  "#{api_base}/guilds/#{server_id}/bans/#{user_id}?delete-message-days=#{message_days}",
112
112
  nil,
@@ -117,7 +117,7 @@ module Discordrb::API
117
117
  # Unban a user from a server
118
118
  def unban_user(token, server_id, user_id)
119
119
  request(
120
- nil,
120
+ __method__,
121
121
  :delete,
122
122
  "#{api_base}/guilds/#{server_id}/bans/#{user_id}",
123
123
  Authorization: token
@@ -127,7 +127,7 @@ module Discordrb::API
127
127
  # Kick a user from a server
128
128
  def kick_user(token, server_id, user_id)
129
129
  request(
130
- nil,
130
+ __method__,
131
131
  :delete,
132
132
  "#{api_base}/guilds/#{server_id}/members/#{user_id}",
133
133
  Authorization: token
@@ -137,7 +137,7 @@ module Discordrb::API
137
137
  # Move a user to a different voice channel
138
138
  def move_user(token, server_id, user_id, channel_id)
139
139
  request(
140
- nil,
140
+ __method__,
141
141
  :patch,
142
142
  "#{api_base}/guilds/#{server_id}/members/#{user_id}",
143
143
  { channel_id: channel_id }.to_json,
@@ -146,10 +146,22 @@ module Discordrb::API
146
146
  )
147
147
  end
148
148
 
149
+ # Change a user's nickname on a server
150
+ def change_nickname(token, server_id, user_id, nick)
151
+ request(
152
+ __method__,
153
+ :patch,
154
+ "#{api_base}/guilds/#{server_id}/members/#{user_id}",
155
+ { nick: nick }.to_json,
156
+ Authorization: token,
157
+ content_type: :json
158
+ )
159
+ end
160
+
149
161
  # Get a server's banned users
150
162
  def bans(token, server_id)
151
163
  request(
152
- nil,
164
+ __method__,
153
165
  :get,
154
166
  "#{api_base}/guilds/#{server_id}/bans",
155
167
  Authorization: token
@@ -159,7 +171,7 @@ module Discordrb::API
159
171
  # Login to the server
160
172
  def login(email, password)
161
173
  request(
162
- nil,
174
+ __method__,
163
175
  :post,
164
176
  "#{api_base}/auth/login",
165
177
  email: email,
@@ -170,7 +182,7 @@ module Discordrb::API
170
182
  # Logout from the server
171
183
  def logout(token)
172
184
  request(
173
- nil,
185
+ __method__,
174
186
  :post,
175
187
  "#{api_base}/auth/logout",
176
188
  nil,
@@ -181,7 +193,7 @@ module Discordrb::API
181
193
  # Create an OAuth application
182
194
  def create_oauth_application(token, name, redirect_uris)
183
195
  request(
184
- nil,
196
+ __method__,
185
197
  :post,
186
198
  "#{api_base}/oauth2/applications",
187
199
  { name: name, redirect_uris: redirect_uris }.to_json,
@@ -193,7 +205,7 @@ module Discordrb::API
193
205
  # Change an OAuth application's properties
194
206
  def update_oauth_application(token, name, redirect_uris, description = '', icon = nil)
195
207
  request(
196
- nil,
208
+ __method__,
197
209
  :put,
198
210
  "#{api_base}/oauth2/applications",
199
211
  { name: name, redirect_uris: redirect_uris, description: description, icon: icon }.to_json,
@@ -205,7 +217,7 @@ module Discordrb::API
205
217
  # Create a server
206
218
  def create_server(token, name, region = :london)
207
219
  request(
208
- nil,
220
+ __method__,
209
221
  :post,
210
222
  "#{api_base}/guilds",
211
223
  { name: name, region: region.to_s }.to_json,
@@ -217,7 +229,7 @@ module Discordrb::API
217
229
  # Update a server
218
230
  def update_server(token, server_id, name, region, icon, afk_channel_id, afk_timeout)
219
231
  request(
220
- nil,
232
+ __method__,
221
233
  :patch,
222
234
  "#{api_base}/guilds/#{server_id}",
223
235
  { name: name, region: region, icon: icon, afk_channel_id: afk_channel_id, afk_timeout: afk_timeout }.to_json,
@@ -229,7 +241,7 @@ module Discordrb::API
229
241
  # Transfer server ownership
230
242
  def transfer_ownership(token, server_id, user_id)
231
243
  request(
232
- nil,
244
+ __method__,
233
245
  :patch,
234
246
  "#{api_base}/guilds/#{server_id}",
235
247
  { owner_id: user_id }.to_json,
@@ -241,7 +253,7 @@ module Discordrb::API
241
253
  # Delete a server
242
254
  def delete_server(token, server_id)
243
255
  request(
244
- nil,
256
+ __method__,
245
257
  :delete,
246
258
  "#{api_base}/guilds/#{server_id}",
247
259
  Authorization: token
@@ -251,7 +263,7 @@ module Discordrb::API
251
263
  # Leave a server
252
264
  def leave_server(token, server_id)
253
265
  request(
254
- nil,
266
+ __method__,
255
267
  :delete,
256
268
  "#{api_base}/users/@me/guilds/#{server_id}",
257
269
  Authorization: token
@@ -261,7 +273,7 @@ module Discordrb::API
261
273
  # Get a channel's data
262
274
  def channel(token, channel_id)
263
275
  request(
264
- nil,
276
+ __method__,
265
277
  :get,
266
278
  "#{api_base}/channels/#{channel_id}",
267
279
  Authorization: token
@@ -271,7 +283,7 @@ module Discordrb::API
271
283
  # Get a server's data
272
284
  def server(token, server_id)
273
285
  request(
274
- nil,
286
+ __method__,
275
287
  :get,
276
288
  "#{api_base}/guilds/#{server_id}",
277
289
  Authorization: token
@@ -281,7 +293,7 @@ module Discordrb::API
281
293
  # Get a member's data
282
294
  def member(token, server_id, user_id)
283
295
  request(
284
- nil,
296
+ __method__,
285
297
  :get,
286
298
  "#{api_base}/guilds/#{server_id}/members/#{user_id}",
287
299
  Authorization: token
@@ -291,7 +303,7 @@ module Discordrb::API
291
303
  # Create a channel
292
304
  def create_channel(token, server_id, name, type)
293
305
  request(
294
- nil,
306
+ __method__,
295
307
  :post,
296
308
  "#{api_base}/guilds/#{server_id}/channels",
297
309
  { name: name, type: type }.to_json,
@@ -303,7 +315,7 @@ module Discordrb::API
303
315
  # Update a channel's data
304
316
  def update_channel(token, channel_id, name, topic, position = 0)
305
317
  request(
306
- nil,
318
+ __method__,
307
319
  :patch,
308
320
  "#{api_base}/channels/#{channel_id}",
309
321
  { name: name, position: position, topic: topic }.to_json,
@@ -315,7 +327,7 @@ module Discordrb::API
315
327
  # Delete a channel
316
328
  def delete_channel(token, channel_id)
317
329
  request(
318
- nil,
330
+ __method__,
319
331
  :delete,
320
332
  "#{api_base}/channels/#{channel_id}",
321
333
  Authorization: token
@@ -325,7 +337,7 @@ module Discordrb::API
325
337
  # Join a server using an invite
326
338
  def join_server(token, invite_code)
327
339
  request(
328
- nil,
340
+ __method__,
329
341
  :post,
330
342
  "#{api_base}/invite/#{invite_code}",
331
343
  nil,
@@ -336,7 +348,7 @@ module Discordrb::API
336
348
  # Resolve an invite
337
349
  def resolve_invite(token, invite_code)
338
350
  request(
339
- nil,
351
+ __method__,
340
352
  :get,
341
353
  "#{api_base}/invite/#{invite_code}",
342
354
  Authorization: token
@@ -346,7 +358,7 @@ module Discordrb::API
346
358
  # Create a private channel
347
359
  def create_private(token, bot_user_id, user_id)
348
360
  request(
349
- nil,
361
+ __method__,
350
362
  :post,
351
363
  "#{api_base}/users/#{bot_user_id}/channels",
352
364
  { recipient_id: user_id }.to_json,
@@ -360,7 +372,7 @@ module Discordrb::API
360
372
  # Create an instant invite from a server or a channel id
361
373
  def create_invite(token, channel_id, max_age = 0, max_uses = 0, temporary = false, xkcd = false)
362
374
  request(
363
- nil,
375
+ __method__,
364
376
  :post,
365
377
  "#{api_base}/channels/#{channel_id}/invites",
366
378
  { max_age: max_age, max_uses: max_uses, temporary: temporary, xkcdpass: xkcd }.to_json,
@@ -372,7 +384,7 @@ module Discordrb::API
372
384
  # Delete an invite by code
373
385
  def delete_invite(token, code)
374
386
  request(
375
- nil,
387
+ __method__,
376
388
  :delete,
377
389
  "#{api_base}/invites/#{code}",
378
390
  Authorization: token
@@ -380,9 +392,9 @@ module Discordrb::API
380
392
  end
381
393
 
382
394
  # Send a message to a channel
383
- def send_message(token, channel_id, message, mentions = [], tts = false)
395
+ def send_message(token, channel_id, message, mentions = [], tts = false, guild_id = nil)
384
396
  request(
385
- :message,
397
+ "message-#{guild_id}".to_sym,
386
398
  :post,
387
399
  "#{api_base}/channels/#{channel_id}/messages",
388
400
  { content: message, mentions: mentions, tts: tts }.to_json,
@@ -396,7 +408,7 @@ module Discordrb::API
396
408
  # Delete a message
397
409
  def delete_message(token, channel_id, message_id)
398
410
  request(
399
- nil,
411
+ __method__,
400
412
  :delete,
401
413
  "#{api_base}/channels/#{channel_id}/messages/#{message_id}",
402
414
  Authorization: token
@@ -420,7 +432,7 @@ module Discordrb::API
420
432
  # so this is an easy way to catch up on messages
421
433
  def acknowledge_message(token, channel_id, message_id)
422
434
  request(
423
- nil,
435
+ __method__,
424
436
  :post,
425
437
  "#{api_base}/channels/#{channel_id}/messages/#{message_id}/ack",
426
438
  nil,
@@ -431,7 +443,7 @@ module Discordrb::API
431
443
  # Send a file as a message to a channel
432
444
  def send_file(token, channel_id, file)
433
445
  request(
434
- nil,
446
+ __method__,
435
447
  :post,
436
448
  "#{api_base}/channels/#{channel_id}/messages",
437
449
  { file: file },
@@ -442,7 +454,7 @@ module Discordrb::API
442
454
  # Create a role (parameters such as name and colour will have to be set by update_role afterwards)
443
455
  def create_role(token, server_id)
444
456
  request(
445
- nil,
457
+ __method__,
446
458
  :post,
447
459
  "#{api_base}/guilds/#{server_id}/roles",
448
460
  nil,
@@ -456,7 +468,7 @@ module Discordrb::API
456
468
  # connecting to voice, speaking and voice activity (push-to-talk isn't mandatory)
457
469
  def update_role(token, server_id, role_id, name, colour, hoist = false, packed_permissions = 36_953_089)
458
470
  request(
459
- nil,
471
+ __method__,
460
472
  :patch,
461
473
  "#{api_base}/guilds/#{server_id}/roles/#{role_id}",
462
474
  { color: colour, name: name, hoist: hoist, permissions: packed_permissions }.to_json,
@@ -468,7 +480,7 @@ module Discordrb::API
468
480
  # Delete a role
469
481
  def delete_role(token, server_id, role_id)
470
482
  request(
471
- nil,
483
+ __method__,
472
484
  :delete,
473
485
  "#{api_base}/guilds/#{server_id}/roles/#{role_id}",
474
486
  Authorization: token
@@ -478,7 +490,7 @@ module Discordrb::API
478
490
  # Update a user's roles
479
491
  def update_user_roles(token, server_id, user_id, roles)
480
492
  request(
481
- nil,
493
+ __method__,
482
494
  :patch,
483
495
  "#{api_base}/guilds/#{server_id}/members/#{user_id}",
484
496
  { roles: roles }.to_json,
@@ -490,7 +502,7 @@ module Discordrb::API
490
502
  # Update a user's permission overrides in a channel
491
503
  def update_user_overrides(token, channel_id, user_id, allow, deny)
492
504
  request(
493
- nil,
505
+ __method__,
494
506
  :put,
495
507
  "#{api_base}/channels/#{channel_id}/permissions/#{user_id}",
496
508
  { type: 'member', id: user_id, allow: allow, deny: deny }.to_json,
@@ -502,7 +514,7 @@ module Discordrb::API
502
514
  # Update a role's permission overrides in a channel
503
515
  def update_role_overrides(token, channel_id, role_id, allow, deny)
504
516
  request(
505
- nil,
517
+ __method__,
506
518
  :put,
507
519
  "#{api_base}/channels/#{channel_id}/permissions/#{role_id}",
508
520
  { type: 'role', id: role_id, allow: allow, deny: deny }.to_json,
@@ -514,7 +526,7 @@ module Discordrb::API
514
526
  # Get the gateway to be used
515
527
  def gateway(token)
516
528
  request(
517
- nil,
529
+ __method__,
518
530
  :get,
519
531
  "#{api_base}/gateway",
520
532
  Authorization: token
@@ -524,7 +536,7 @@ module Discordrb::API
524
536
  # Validate a token (this request will fail if the token is invalid)
525
537
  def validate_token(token)
526
538
  request(
527
- nil,
539
+ __method__,
528
540
  :post,
529
541
  "#{api_base}/auth/login",
530
542
  {}.to_json,
@@ -536,7 +548,7 @@ module Discordrb::API
536
548
  # Start typing (needs to be resent every 5 seconds to keep up the typing)
537
549
  def start_typing(token, channel_id)
538
550
  request(
539
- nil,
551
+ __method__,
540
552
  :post,
541
553
  "#{api_base}/channels/#{channel_id}/typing",
542
554
  nil,
@@ -547,7 +559,7 @@ module Discordrb::API
547
559
  # Get user data
548
560
  def user(token, user_id)
549
561
  request(
550
- nil,
562
+ __method__,
551
563
  :get,
552
564
  "#{api_base}/users/#{user_id}",
553
565
  Authorization: token
@@ -557,7 +569,7 @@ module Discordrb::API
557
569
  # Get profile data
558
570
  def profile(token)
559
571
  request(
560
- nil,
572
+ __method__,
561
573
  :get,
562
574
  "#{api_base}/users/@me",
563
575
  Authorization: token
@@ -567,7 +579,7 @@ module Discordrb::API
567
579
  # Get information about a user's connections
568
580
  def connections(token)
569
581
  request(
570
- nil,
582
+ __method__,
571
583
  :get,
572
584
  "#{api_base}/users/@me/connections",
573
585
  Authorization: token
@@ -577,7 +589,7 @@ module Discordrb::API
577
589
  # Update user data
578
590
  def update_user(token, email, password, new_username, avatar, new_password = nil)
579
591
  request(
580
- nil,
592
+ __method__,
581
593
  :patch,
582
594
  "#{api_base}/users/@me",
583
595
  { avatar: avatar, email: email, new_password: new_password, password: password, username: new_username }.to_json,
@@ -589,7 +601,7 @@ module Discordrb::API
589
601
  # Get the servers a user is connected to
590
602
  def servers(token)
591
603
  request(
592
- nil,
604
+ __method__,
593
605
  :get,
594
606
  "#{api_base}/users/@me/guilds",
595
607
  Authorization: token
@@ -599,7 +611,7 @@ module Discordrb::API
599
611
  # Get a list of messages from a channel's history
600
612
  def channel_log(token, channel_id, amount, before = nil, after = nil)
601
613
  request(
602
- nil,
614
+ __method__,
603
615
  :get,
604
616
  "#{api_base}/channels/#{channel_id}/messages?limit=#{amount}#{"&before=#{before}" if before}#{"&after=#{after}" if after}",
605
617
  Authorization: token
data/lib/discordrb/bot.rb CHANGED
@@ -109,6 +109,9 @@ module Discordrb
109
109
  # same codebase. Not required but I recommend setting it anyway.
110
110
  attr_accessor :name
111
111
 
112
+ # @return [Array(Integer, Integer)] the current shard key
113
+ attr_reader :shard_key
114
+
112
115
  include EventContainer
113
116
  include Cache
114
117
 
@@ -145,10 +148,15 @@ module Discordrb
145
148
  # Useful for very large bots running in debug or verbose log_mode.
146
149
  # @param parse_self [true, false] Whether the bot should react on its own messages. It's best to turn this off
147
150
  # unless you really need this so you don't inadvertently create infinite loops.
151
+ # @param shard_id [Integer] The number of the shard this bot should handle. See
152
+ # https://github.com/hammerandchisel/discord-api-docs/issues/17 for how to do sharding.
153
+ # @param num_shards [Integer] The total number of shards that should be running. See
154
+ # https://github.com/hammerandchisel/discord-api-docs/issues/17 for how to do sharding.
148
155
  def initialize(
149
156
  email: nil, password: nil, log_mode: :normal,
150
157
  token: nil, application_id: nil,
151
- type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false)
158
+ type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false,
159
+ shard_id: nil, num_shards: nil)
152
160
  # Make sure people replace the login details in the example files...
153
161
  if email.is_a?(String) && email.end_with?('example.com')
154
162
  puts 'You have to replace the login details in the example files with your own!'
@@ -172,6 +180,8 @@ module Discordrb
172
180
 
173
181
  @name = name
174
182
 
183
+ @shard_key = num_shards ? [shard_id, num_shards] : nil
184
+
175
185
  LOGGER.fancy = fancy_log
176
186
  @prevent_ready = suppress_ready
177
187
 
@@ -390,11 +400,11 @@ module Discordrb
390
400
  # @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
391
401
  # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
392
402
  # @return [Message] The message that was sent.
393
- def send_message(channel_id, content, tts = false)
403
+ def send_message(channel_id, content, tts = false, server_id = nil)
394
404
  channel_id = channel_id.resolve_id
395
405
  debug("Sending message to #{channel_id} with content '#{content}'")
396
406
 
397
- response = API.send_message(token, channel_id, content, [], tts)
407
+ response = API.send_message(token, channel_id, content, [], tts, server_id)
398
408
  Message.new(JSON.parse(response), self)
399
409
  end
400
410
 
@@ -433,7 +443,7 @@ module Discordrb
433
443
  end
434
444
 
435
445
  # Creates a new application to do OAuth authorization with. This allows you to use OAuth to authorize users using
436
- # Discord. For information how to use this, see this example: https://github.com/vishnevskiy/discord-oauth2-example
446
+ # Discord. For information how to use this, see the docs: https://discordapp.com/developers/docs/topics/oauth2
437
447
  # @param name [String] What your application should be called.
438
448
  # @param redirect_uris [Array<String>] URIs that Discord should redirect your users to after authorizing.
439
449
  # @return [Array(String, String)] your applications' client ID and client secret to be used in OAuth authorization.
@@ -457,7 +467,7 @@ module Discordrb
457
467
  # @return [User] The user identified by the mention, or `nil` if none exists.
458
468
  def parse_mention(mention)
459
469
  # Mention format: <@id>
460
- return nil unless /<@(?<id>\d+)>?/ =~ mention
470
+ return nil unless /<@!?(?<id>\d+)>?/ =~ mention
461
471
  user(id.to_i)
462
472
  end
463
473
 
@@ -724,6 +734,7 @@ module Discordrb
724
734
 
725
735
  member = server.member(data['user']['id'].to_i)
726
736
  member.update_roles(data['roles'])
737
+ member.update_nick(data['nick'])
727
738
  end
728
739
 
729
740
  # Internal handler for GUILD_MEMBER_DELETE
@@ -1095,7 +1106,7 @@ module Discordrb
1095
1106
  return if message.from_bot? && !should_parse_self
1096
1107
 
1097
1108
  unless message.author
1098
- LOGGER.warn("Edited a message with nil author! Content: #{message.content.inspect}, channel: #{message.channel.inspect}")
1109
+ LOGGER.debug("Edited a message with nil author! Content: #{message.content.inspect}, channel: #{message.channel.inspect}")
1099
1110
  return
1100
1111
  end
1101
1112
 
@@ -1296,6 +1307,9 @@ module Discordrb
1296
1307
  }
1297
1308
  }
1298
1309
 
1310
+ # Discord is very strict about the existence of the shard parameter, so only add it if it actually exists
1311
+ packet[:d][:shard] = @shard_key if @shard_key
1312
+
1299
1313
  @ws.send(packet.to_json)
1300
1314
  end
1301
1315
 
@@ -1343,6 +1357,9 @@ module Discordrb
1343
1357
 
1344
1358
  def send_heartbeat(sequence = nil)
1345
1359
  sequence ||= @sequence
1360
+
1361
+ raise_event(HeartbeatEvent.new(self))
1362
+
1346
1363
  LOGGER.out("Sending heartbeat with sequence #{sequence}")
1347
1364
  data = {
1348
1365
  op: Opcodes::HEARTBEAT,
@@ -23,9 +23,18 @@ module Discordrb::Commands
23
23
  # Creates a new CommandBot and logs in to Discord.
24
24
  # @param attributes [Hash] The attributes to initialize the CommandBot with.
25
25
  # @see {Discordrb::Bot#initialize} for other attributes that should be used to create the underlying regular bot.
26
- # @option attributes [String] :prefix The prefix that should trigger this bot's commands. Can be any string (including the empty
27
- # string), but note that it will be literal - if the prefix is "hi" then the corresponding trigger string for
28
- # a command called "test" would be "hitest". Don't forget to put spaces in if you need them!
26
+ # @option attributes [String, Array<String>, #call] :prefix The prefix that should trigger this bot's commands. It
27
+ # can be:
28
+ #
29
+ # * Any string (including the empty string). This has the effect that if a message starts with the prefix, the
30
+ # prefix will be stripped and the rest of the chain will be parsed as a command chain. Note that it will be
31
+ # literal - if the prefix is "hi" then the corresponding trigger string for a command called "test" would be
32
+ # "hitest". Don't forget to put spaces in if you need them!
33
+ # * An array of prefixes. Those will behave similarly to setting one string as a prefix, but instead of only one
34
+ # string, any of the strings in the array can be used.
35
+ # * Something Proc-like (responds to :call) that takes a string as an argument (the message) and returns either
36
+ # the command chain in raw form or `nil` if the given string shouldn't be parsed. This can be used to make more
37
+ # complicated dynamic prefixes, or even something else entirely (suffixes, or most adventurous, infixes).
29
38
  # @option attributes [true, false] :advanced_functionality Whether to enable advanced functionality (very powerful
30
39
  # way to nest commands into chains, see https://github.com/meew0/discordrb/wiki/Commands#command-chain-syntax
31
40
  # for info. Default is true.
@@ -62,7 +71,9 @@ module Discordrb::Commands
62
71
  name: attributes[:name],
63
72
  fancy_log: attributes[:fancy_log],
64
73
  suppress_ready: attributes[:suppress_ready],
65
- parse_self: attributes[:parse_self])
74
+ parse_self: attributes[:parse_self],
75
+ shard_id: attributes[:shard_id],
76
+ num_shards: attributes[:num_shards])
66
77
 
67
78
  @prefix = attributes[:prefix]
68
79
  @attributes = {
@@ -150,7 +161,8 @@ module Discordrb::Commands
150
161
  event.respond @attributes[:command_doesnt_exist_message].gsub('%command%', name.to_s) if @attributes[:command_doesnt_exist_message]
151
162
  return
152
163
  end
153
- if permission?(event.user, command.attributes[:permission_level], event.server)
164
+ if permission?(event.user, command.attributes[:permission_level], event.server) &&
165
+ required_permissions?(event.author, command.attributes[:required_permissions], event.channel)
154
166
  event.command = command
155
167
  result = command.call(event, arguments, chained)
156
168
  stringify(result)
@@ -205,8 +217,8 @@ module Discordrb::Commands
205
217
 
206
218
  event = CommandEvent.new(message, self)
207
219
 
208
- return unless message.content.start_with? @prefix
209
- chain = message.content[@prefix.length..-1]
220
+ chain = trigger?(message.content)
221
+ return unless chain
210
222
 
211
223
  # Don't allow spaces between the prefix and the command
212
224
  if chain.start_with?(' ') && !@attributes[:spaces_allowed]
@@ -222,6 +234,28 @@ module Discordrb::Commands
222
234
  execute_chain(chain, event)
223
235
  end
224
236
 
237
+ # Check whether a message should trigger command execution, and if it does, return the raw chain
238
+ def trigger?(message)
239
+ if @prefix.is_a? String
240
+ standard_prefix_trigger(message, @prefix)
241
+ elsif @prefix.is_a? Array
242
+ @prefix.map { |e| standard_prefix_trigger(message, e) }.reduce { |a, e| a || e }
243
+ elsif @prefix.respond_to? :call
244
+ @prefix.call(message)
245
+ end
246
+ end
247
+
248
+ def standard_prefix_trigger(message, prefix)
249
+ return nil unless message.start_with? prefix
250
+ message[prefix.length..-1]
251
+ end
252
+
253
+ def required_permissions?(member, required, channel = nil)
254
+ required.reduce(true) do |a, action|
255
+ a && member.permission?(action, channel)
256
+ end
257
+ end
258
+
225
259
  def execute_chain(chain, event)
226
260
  t = Thread.new do
227
261
  @event_threads << t
@@ -9,6 +9,9 @@ module Discordrb::Commands
9
9
  module CommandContainer
10
10
  include RateLimiter
11
11
 
12
+ # @return [Array<Command>] the list of commands this container has.
13
+ attr_reader :commands
14
+
12
15
  # Adds a new command to the container.
13
16
  # @param name [Symbol] The name of the command to add.
14
17
  # @param attributes [Hash] The attributes to initialize the command with.
@@ -17,6 +20,8 @@ module Discordrb::Commands
17
20
  # @option attributes [String, false] :permission_message Message to display when a user does not have sufficient
18
21
  # permissions to execute a command. %name% in the message will be replaced with the name of the command. Disable
19
22
  # the message by setting this option to false.
23
+ # @option attributes [Array<Symbol>] :required_permissions Discord action permissions (e.g. `:kick_members`) that
24
+ # should be required to use this command. See {Discordrb::Permissions::Flags} for a list.
20
25
  # @option attributes [true, false] :chain_usable Whether this command is able to be used inside of a command chain
21
26
  # or sub-chain. Typically used for administrative commands that shouldn't be done carelessly.
22
27
  # @option attributes [true, false] :help_available Whether this command is visible in the help command. See the
@@ -19,6 +19,9 @@ module Discordrb::Commands
19
19
  # Message to display when a user does not have sufficient permissions to execute a command
20
20
  permission_message: (attributes[:permission_message].is_a? FalseClass) ? nil : (attributes[:permission_message] || "You don't have permission to execute command %name%!"),
21
21
 
22
+ # Discord action permissions required to use this command
23
+ required_permissions: attributes[:required_permissions] || [],
24
+
22
25
  # Whether this command is usable in a command chain
23
26
  chain_usable: attributes[:chain_usable].nil? ? true : attributes[:chain_usable],
24
27
 
@@ -56,6 +56,21 @@ module Discordrb
56
56
  register_event(DisconnectEvent, attributes, block)
57
57
  end
58
58
 
59
+ # This **event** is raised every time the bot sends a heartbeat over the galaxy. This happens roughly every 40
60
+ # seconds, but may happen at a lower rate should Discord change their interval. It may also happen more quickly for
61
+ # periods of time, especially for unstable connections, since discordrb rather sends a heartbeat than not if there's
62
+ # a choice. (You shouldn't rely on all this to be accurately timed.)
63
+ #
64
+ # All this makes this event useful to periodically trigger something, like doing some API request every hour,
65
+ # setting some kind of uptime variable or whatever else. The only limit is yourself.
66
+ # @param attributes [Hash] Event attributes, none in this particular case
67
+ # @yield The block is executed when the event is raised.
68
+ # @yieldparam event [HeartbeatEvent] The event that was raised.
69
+ # @return [HeartbeatEventHandler] The event handler that was registered.
70
+ def heartbeat(attributes = {}, &block)
71
+ register_event(HeartbeatEvent, attributes, block)
72
+ end
73
+
59
74
  # This **event** is raised when somebody starts typing in a channel the bot is also in. The official Discord
60
75
  # client would display the typing indicator for five seconds after receiving this event. If the user continues
61
76
  # typing after five seconds, the event will be re-raised.
@@ -229,6 +229,10 @@ module Discordrb
229
229
  # @return [Time] when this member joined the server.
230
230
  attr_reader :joined_at
231
231
 
232
+ # @return [String, nil] the nickname this member has, or nil if it has none.
233
+ attr_reader :nick
234
+ alias_method :nickname, :nick
235
+
232
236
  # @return [Array<Role>] the roles this member has.
233
237
  attr_reader :roles
234
238
 
@@ -342,6 +346,8 @@ module Discordrb
342
346
  # Initialize the roles by getting the roles from the server one-by-one
343
347
  update_roles(data['roles'])
344
348
 
349
+ @nick = data['nick']
350
+
345
351
  @deaf = data['deaf']
346
352
  @mute = data['mute']
347
353
  @joined_at = data['joined_at'] ? Time.parse(data['joined_at']) : nil
@@ -379,6 +385,23 @@ module Discordrb
379
385
  API.update_user_roles(@bot.token, @server.id, @user.id, new_role_ids)
380
386
  end
381
387
 
388
+ # Sets or resets this member's nickname. Requires the Change Nickname permission for the bot itself and Manage
389
+ # Nicknames for other users.
390
+ # @param nick [String, nil] The string to set the nickname to, or nil if it should be reset.
391
+ def nick=(nick)
392
+ # Discord uses the empty string to signify 'no nickname' so we convert nil into that
393
+ nick ||= ''
394
+
395
+ API.change_nickname(@bot.token, @server.id, @user.id, nick)
396
+ end
397
+
398
+ alias_method :nickname=, :nick=
399
+
400
+ # @return [String] the name the user displays as (nickname if they have one, username otherwise)
401
+ def display_name
402
+ nickname || username
403
+ end
404
+
382
405
  # Update this member's roles
383
406
  # @note For internal use only.
384
407
  # @!visibility private
@@ -388,6 +411,13 @@ module Discordrb
388
411
  end
389
412
  end
390
413
 
414
+ # Update this member's nick
415
+ # @note For internal use only.
416
+ # @!visibility private
417
+ def update_nick(nick)
418
+ @nick = nick
419
+ end
420
+
391
421
  # Update this member's voice state
392
422
  # @note For internal use only.
393
423
  # @!visibility private
@@ -540,6 +570,10 @@ module Discordrb
540
570
  # @return [true, false] whether or not this role should be displayed separately from other users
541
571
  attr_reader :hoist
542
572
 
573
+ # @return [true, false] whether this role can be mentioned using a role mention
574
+ attr_reader :mentionable
575
+ alias_method :mentionable?, :mentionable
576
+
543
577
  # @return [ColourRGB] the role colour
544
578
  attr_reader :colour
545
579
 
@@ -567,10 +601,18 @@ module Discordrb
567
601
  @permissions = Permissions.new(data['permissions'], RoleWriter.new(self, @bot.token))
568
602
  @name = data['name']
569
603
  @id = data['id'].to_i
604
+
570
605
  @hoist = data['hoist']
606
+ @mentionable = data['mentionable']
607
+
571
608
  @colour = ColourRGB.new(data['color'])
572
609
  end
573
610
 
611
+ # @return [String] a string that will mention this role, if it is mentionable.
612
+ def mention
613
+ "<@&#{@id}>"
614
+ end
615
+
574
616
  # Updates the data cache from another Role object
575
617
  # @note For internal use only
576
618
  # @!visibility private
@@ -853,7 +895,7 @@ module Discordrb
853
895
  # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
854
896
  # @return [Message] the message that was sent.
855
897
  def send_message(content, tts = false)
856
- @bot.send_message(@id, content, tts)
898
+ @bot.send_message(@id, content, tts, @server && @server.id)
857
899
  end
858
900
 
859
901
  # Sends multiple messages to a channel
@@ -963,9 +1005,11 @@ module Discordrb
963
1005
  JSON.parse(logs).map { |message| Message.new(message, @bot) }
964
1006
  end
965
1007
 
966
- # Deletes the last N messages on this channel.
967
- # @note Each delete request is performed in a separate thread for performance reasons, so if a large number of
968
- # messages are pruned, many threads will be created.
1008
+ # Deletes the last N messages on this channel. Each delete request is performed in a separate thread for performance
1009
+ # reasons, so if a large number of messages are pruned, many threads will be created.
1010
+ # @note As of the April 29 update, the message delete request is rate limited, which means this method will take
1011
+ # a long time. It will eventually be updated to use batch deletes once those are released, but that will be in the
1012
+ # far future.
969
1013
  # @param amount [Integer] How many messages to delete. Must be 100 or less (Discord limitation)
970
1014
  # @raise [ArgumentError] if more than 100 messages are requested.
971
1015
  def prune(amount)
@@ -1097,6 +1141,9 @@ module Discordrb
1097
1141
  # @return [Array<User>] the users that were mentioned in this message.
1098
1142
  attr_reader :mentions
1099
1143
 
1144
+ # @return [Array<Role>] the roles that were mentioned in this message.
1145
+ attr_reader :role_mentions
1146
+
1100
1147
  # @return [Array<Attachment>] the files attached to this message.
1101
1148
  attr_reader :attachments
1102
1149
 
@@ -1129,6 +1176,15 @@ module Discordrb
1129
1176
  @mentions << bot.ensure_user(element)
1130
1177
  end if data['mentions']
1131
1178
 
1179
+ @role_mentions = []
1180
+
1181
+ # Role mentions can only happen on public servers so make sure we only parse them there
1182
+ unless @channel.private?
1183
+ data['mention_roles'].each do |element|
1184
+ @role_mentions << @channel.server.role(element.to_i)
1185
+ end if data['mention_roles']
1186
+ end
1187
+
1132
1188
  @attachments = []
1133
1189
  @attachments = data['attachments'].map { |e| Attachment.new(e, self, @bot) } if data['attachments']
1134
1190
  end
@@ -22,4 +22,10 @@ module Discordrb::Events
22
22
 
23
23
  # Event handler for {DisconnectEvent}
24
24
  class DisconnectEventHandler < TrueEventHandler; end
25
+
26
+ # @see Discordrb::EventContainer#heartbeat
27
+ class HeartbeatEvent < LifetimeEvent; end
28
+
29
+ # Event handler for {HeartbeatEvent}
30
+ class HeartbeatEventHandler < TrueEventHandler; end
25
31
  end
@@ -32,7 +32,9 @@ module Discordrb
32
32
  22 => :mute_members, # 4194304
33
33
  23 => :deafen_members, # 8388608
34
34
  24 => :move_members, # 16777216
35
- 25 => :use_voice_activity # 33554432
35
+ 25 => :use_voice_activity, # 33554432
36
+ 26 => :change_nickname, # 67108864
37
+ 27 => :manage_nicknames # 134217728
36
38
  }.freeze
37
39
 
38
40
  Flags.each do |position, flag|
@@ -3,5 +3,5 @@
3
3
  # Discordrb and all its functionality, in this case only the version.
4
4
  module Discordrb
5
5
  # The current version of discordrb.
6
- VERSION = '2.0.4'.freeze
6
+ VERSION = '2.1.0'.freeze
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: discordrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.4
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - meew0
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-04-19 00:00:00.000000000 Z
11
+ date: 2016-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client