disrb 0.1.4 → 0.1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 755465c33626010b7a97992a3afbcee4459e9feae3249870900531ce315ae4f5
4
- data.tar.gz: dec666a3607d7d21b3a526f6435a291122239ab66ad908d6d3a2626c1fd7ea45
3
+ metadata.gz: 27b3ea7d837fad369bc6abba57e0567143ce819dd3a13ca2516e32d49bc22991
4
+ data.tar.gz: c024c1d8817e3dfff8684cf3dfa2a5023361f25ef0941cd8e2bded03062ab0c2
5
5
  SHA512:
6
- metadata.gz: 1120cf0957b7e7ab2c7fdbf011b27816cb9085cb957f8c3eee37813a1a3627a4649adcb2ca001052740fca0fa7db50e6a0731b87d0ad73b46d0771f1b6fad764
7
- data.tar.gz: d554f6fe8e41c6846d5ed186cc27988e050dc57af37a041203fa5c887414185238dce3c282c88b3c3a14c4e7f3f528eb7c6257b5fdd92e16b167fdd06b86b4df
6
+ metadata.gz: d8dba8108d27f12906108120d0acbb8009f3023a336ddcc24bae46f35ee2badbb026314ef1428e1916c6d5ceda2221595a318e19d2c9ff606ca4b91db90a3158
7
+ data.tar.gz: 278ab5b9ca773e2dbfed4280204cace3b5b26f577647cfd4317c86b976372298bb9760dffddc9162169b0065a4e6be864568e32709409f030b412e2f89f225e7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # v0.1.4.1 (2026-04-14)
2
+
3
+ - File uploading was just not implemented in `edit_message`
4
+ - Fix file uploading function being used incorrectly in `create_message`
5
+ - Add new testing function in `test.rb`
6
+ - Fix HTTP handlers getting called incorrectly sometimes
7
+ - Add file uploading support to `respond_interaction` (you will need to specify `attachments` in the response yourself for now)
8
+ - Add uploading files to Discord also via PATCH requests
9
+ - Fix Faraday request types being set incorrectly
10
+ - Fix payload_json being added to multipart form data incorrectly
11
+ - Add info on how to create a new release in `CONTRIBUTING.md`
12
+ - Fix some typos and minor mistakes
13
+ - Add `Gemfile.lock` and remove it from `.gitignore`
14
+
1
15
  # v0.1.4 (2026-04-05)
2
16
 
3
17
  - Add support for file uploading
data/README.md CHANGED
@@ -19,7 +19,7 @@ If you wanted to use this project, but found that something is missing, doesn't
19
19
 
20
20
  ## Demonstration
21
21
 
22
- The test.rb file creates two commands "test" and "test2", that return "Hi" and "Hello World!" respectively, sets the bot's current activity to 'Watching if i work', and sets the presence to online since the program was started.
22
+ The test.rb file creates three commands "test", "test2" and "file", that return "Hi", "Hello World!" and then "Hello World!" plus a file called "file.txt" with content "Hello World in a file!" respectively, sets the bot's current activity to 'Watching if i work', and sets the presence to online since the program was started. If the bot itself has been mentioned, it replies to that message with 'pong'.
23
23
 
24
24
  > [!NOTE]
25
25
  > You will need to fill out the required data in `env.rb.template` and rename it to `env.rb` before running `test.rb`.
@@ -47,7 +47,7 @@ If you want to get the contents of the response, use the `body` instance method
47
47
  - [ ] Alpha release (v0.2.0)
48
48
  - [ ] Add support for all Discord API endpoints (v0.2.0)
49
49
  - [ ] Add support for all Discord Gateway events and properly handle the connection (v0.1.5)
50
- - [ ] Add support for uploading files (v0.1.4)
50
+ - [x] Add support for uploading files (v0.1.4)
51
51
  - [x] Documentation (v0.1.3)
52
52
  - [x] Full message resource support (v0.1.2)
53
53
  - [x] Transition to Faraday for HTTP requests (v0.1.1)
data/lib/disrb/message.rb CHANGED
@@ -115,7 +115,7 @@ class DiscordApi
115
115
  data = JSON.generate(output)
116
116
  headers = { 'Authorization': @authorization_header }
117
117
  if files
118
- response = file_upload(url, files, data, headers)
118
+ response = file_upload(url, files, payload_json: data, headers: headers)
119
119
  else
120
120
  headers['Content-Type'] = 'application/json'
121
121
  response = post(url, data, headers)
@@ -272,7 +272,7 @@ class DiscordApi
272
272
  components: nil, files: nil, attachments: nil)
273
273
  if args[2..].all?(&:nil?)
274
274
  @logger.warn("No modifications provided for message with ID #{message_id} in channel with ID #{channel_id}. " \
275
- 'Only modifying Skipping function.')
275
+ 'Skipping function.')
276
276
  return
277
277
  end
278
278
  output = {}
@@ -289,7 +289,12 @@ class DiscordApi
289
289
  url = "#{@base_url}/channels/#{channel_id}/messages/#{message_id}"
290
290
  data = JSON.generate(output)
291
291
  headers = { 'Authorization': @authorization_header, 'Content-Type': 'application/json' }
292
- response = patch(url, data, headers)
292
+ if files
293
+ response = file_upload(url, files, headers: headers, payload_json: data, request_type: :patch)
294
+ else
295
+ headers['Content-Type'] = 'application/json'
296
+ response = patch(url, data, headers)
297
+ end
293
298
  return response if response.is_a?(Faraday::Response) && response.status == 200
294
299
 
295
300
  @logger.error("Failed to edit message with ID #{message_id} in channel with ID #{channel_id}. " \
data/lib/disrb.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require 'uri'
4
5
  require 'async'
5
6
  require 'async/http/endpoint'
6
7
  require 'async/websocket/client'
@@ -50,7 +51,6 @@ class Snowflake
50
51
  end
51
52
 
52
53
  # Class that contains functions that allow interacting with the Discord API.
53
- # @version 0.1.4
54
54
  class DiscordApi
55
55
  # @!attribute [r] base_url
56
56
  # @return [String] the base URL that is used to access the Discord API. ex: "https://discord.com/api/v10"
@@ -126,7 +126,7 @@ class DiscordApi
126
126
  end
127
127
  url = "#{@base_url}/applications/@me"
128
128
  headers = { 'Authorization': @authorization_header }
129
- response = DiscordApi.get(url, headers)
129
+ response = get(url, headers)
130
130
  if response.is_a?(Faraday::Response) && response.status == 200
131
131
  @application_id = JSON.parse(response.body)['id']
132
132
  else
@@ -253,7 +253,7 @@ class DiscordApi
253
253
  loop do
254
254
  recieved_ready = false
255
255
  url = if rescue_connection.nil?
256
- response = DiscordApi.get("#{@base_url}/gateway")
256
+ response = get("#{@base_url}/gateway")
257
257
  if response.is_a?(Faraday::Response) && response.status == 200
258
258
  "#{JSON.parse(response.body)['url']}/?v=#{@api_version}&encoding=json"
259
259
  else
@@ -369,15 +369,21 @@ class DiscordApi
369
369
  # @param interaction [Hash] The interaction payload received from the Gateway.
370
370
  # @param response [Hash] The interaction response payload.
371
371
  # @param with_response [TrueClass, FalseClass] Whether to request the created message in the response.
372
+ # @param files [Array] An array of arrays, each inner-array first has its filename (index 0),
373
+ # raw file data as a string (index 1), and then the MIME type of the file (index 2).
372
374
  # @return [Faraday::Response] The response from the Discord API.
373
- def respond_interaction(interaction, response, with_response: false)
375
+ def respond_interaction(interaction, response, with_response: false, files: nil)
374
376
  query_string_hash = {}
375
377
  query_string_hash[:with_response] = with_response
376
378
  query_string = DiscordApi.handle_query_strings(query_string_hash)
377
379
  url = "#{@base_url}/interactions/#{interaction[:d][:id]}/#{interaction[:d][:token]}/callback#{query_string}"
378
380
  data = JSON.generate(response)
379
- headers = { 'content-type': 'application/json' }
380
- response = DiscordApi.post(url, data, headers)
381
+ if files
382
+ response = file_upload(url, files, payload_json: data)
383
+ else
384
+ headers = { 'Content-Type' => 'application/json' }
385
+ response = post(url, data, headers)
386
+ end
381
387
  return response if response.is_a?(Faraday::Response) &&
382
388
  ((response.status == 204 && !with_response) || (response.status == 200 && with_response))
383
389
 
@@ -508,18 +514,37 @@ class DiscordApi
508
514
  'Empty'
509
515
  end
510
516
 
517
+ # Parses a full URL and returns connection host and request path.
518
+ # @param url [String] Full URL.
519
+ # @param method_name [String] HTTP method name used for logging context.
520
+ # @return [Hash, nil] { host:, path: } or nil if URL is invalid.
521
+ def parse_request_url(url, method_name)
522
+ begin
523
+ uri = URI.parse(url.to_s)
524
+ rescue URI::InvalidURIError
525
+ @logger.error("Empty/invalid URL provided: #{url}. Cannot perform #{method_name} request.")
526
+ return nil
527
+ end
528
+ if uri.scheme.nil? || uri.host.nil?
529
+ @logger.error("Empty/invalid URL provided: #{url}. Cannot perform #{method_name} request.")
530
+ return nil
531
+ end
532
+
533
+ host = "#{uri.scheme}://#{uri.host}"
534
+ host = "#{host}:#{uri.port}" if uri.port && uri.port != uri.default_port
535
+ { host: host, path: uri.request_uri }
536
+ end
537
+
511
538
  # Performs an HTTP GET request using Faraday.
512
539
  # @param url [String] Full URL including scheme and host; path may be included.
513
540
  # @param headers [Hash, nil] Optional request headers.
514
541
  # @return [Faraday::Response, nil] The Faraday response object, or nil if an error was encountered.
515
542
  def get(url, headers = nil)
516
- split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
517
- if split_url.empty?
518
- @logger.error("Empty/invalid URL provided: #{url}. Cannot perform GET request.")
519
- return
520
- end
521
- host = split_url[0]
522
- path = split_url[1] if split_url[1]
543
+ parsed_url = parse_request_url(url, 'GET')
544
+ return if parsed_url.nil?
545
+
546
+ host = parsed_url[:host]
547
+ path = parsed_url[:path]
523
548
  if headers.is_a?(Hash)
524
549
  headers['User-Agent'] = @user_agent
525
550
  elsif headers.nil?
@@ -529,11 +554,7 @@ class DiscordApi
529
554
  headers = { 'User-Agent' => @user_agent }
530
555
  end
531
556
  conn = Faraday.new(url: host, headers: headers)
532
- if path
533
- conn.get(path)
534
- else
535
- conn.get
536
- end
557
+ conn.get(path)
537
558
  end
538
559
 
539
560
  # Performs an HTTP DELETE request using Faraday.
@@ -541,13 +562,11 @@ class DiscordApi
541
562
  # @param headers [Hash, nil] Optional request headers.
542
563
  # @return [Faraday::Response, nil] The Faraday response object, or nil if an error was encountered.
543
564
  def delete(url, headers = nil)
544
- split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
545
- if split_url.empty?
546
- @logger.error("Empty/invalid URL provided: #{url}. Cannot perform DELETE request.")
547
- return
548
- end
549
- host = split_url[0]
550
- path = split_url[1] if split_url[1]
565
+ parsed_url = parse_request_url(url, 'DELETE')
566
+ return if parsed_url.nil?
567
+
568
+ host = parsed_url[:host]
569
+ path = parsed_url[:path]
551
570
  if headers.is_a?(Hash)
552
571
  headers['User-Agent'] = @user_agent
553
572
  elsif headers.nil?
@@ -557,11 +576,7 @@ class DiscordApi
557
576
  headers = { 'User-Agent' => @user_agent }
558
577
  end
559
578
  conn = Faraday.new(url: host, headers: headers)
560
- if path
561
- conn.delete(path)
562
- else
563
- conn.delete
564
- end
579
+ conn.delete(path)
565
580
  end
566
581
 
567
582
  # Performs an HTTP POST request using Faraday.
@@ -570,13 +585,11 @@ class DiscordApi
570
585
  # @param headers [Hash, nil] Optional request headers.
571
586
  # @return [Faraday::Response, nil] The Faraday response object, or nil if an error was encountered.
572
587
  def post(url, data, headers = nil)
573
- split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
574
- if split_url.empty?
575
- @logger.error("Empty/invalid URL provided: #{url}. Cannot perform POST request.")
576
- return
577
- end
578
- host = split_url[0]
579
- path = split_url[1] if split_url[1]
588
+ parsed_url = parse_request_url(url, 'POST')
589
+ return if parsed_url.nil?
590
+
591
+ host = parsed_url[:host]
592
+ path = parsed_url[:path]
580
593
  if headers.is_a?(Hash)
581
594
  headers['User-Agent'] = @user_agent
582
595
  elsif headers.nil?
@@ -586,11 +599,7 @@ class DiscordApi
586
599
  headers = { 'User-Agent' => @user_agent }
587
600
  end
588
601
  conn = Faraday.new(url: host, headers: headers)
589
- if path
590
- conn.post(path, data)
591
- else
592
- conn.post('', data)
593
- end
602
+ conn.post(path, data)
594
603
  end
595
604
 
596
605
  # Performs an HTTP PATCH request using Faraday.
@@ -599,13 +608,11 @@ class DiscordApi
599
608
  # @param headers [Hash, nil] Optional request headers.
600
609
  # @return [Faraday::Response, nil] The Faraday response object, or nil if an error was encountered.
601
610
  def patch(url, data, headers = nil)
602
- split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
603
- if split_url.empty?
604
- @logger.error("Empty/invalid URL provided: #{url}. Cannot perform PATCH request.")
605
- return
606
- end
607
- host = split_url[0]
608
- path = split_url[1] if split_url[1]
611
+ parsed_url = parse_request_url(url, 'PATCH')
612
+ return if parsed_url.nil?
613
+
614
+ host = parsed_url[:host]
615
+ path = parsed_url[:path]
609
616
  if headers.is_a?(Hash)
610
617
  headers['User-Agent'] = @user_agent
611
618
  elsif headers.nil?
@@ -615,11 +622,7 @@ class DiscordApi
615
622
  headers = { 'User-Agent' => @user_agent }
616
623
  end
617
624
  conn = Faraday.new(url: host, headers: headers)
618
- if path
619
- conn.patch(path, data)
620
- else
621
- conn.patch('', data)
622
- end
625
+ conn.patch(path, data)
623
626
  end
624
627
 
625
628
  # Performs an HTTP PUT request using Faraday.
@@ -628,13 +631,11 @@ class DiscordApi
628
631
  # @param headers [Hash, nil] Optional request headers.
629
632
  # @return [Faraday::Response, nil] The Faraday response object, or nil if an error was encountered.
630
633
  def put(url, data, headers = nil)
631
- split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
632
- if split_url.empty?
633
- @logger.error("Empty/invalid URL provided: #{url}. Cannot perform PUT request.")
634
- return
635
- end
636
- host = split_url[0]
637
- path = split_url[1] if split_url[1]
634
+ parsed_url = parse_request_url(url, 'PUT')
635
+ return if parsed_url.nil?
636
+
637
+ host = parsed_url[:host]
638
+ path = parsed_url[:path]
638
639
  if headers.is_a?(Hash)
639
640
  headers['User-Agent'] = @user_agent
640
641
  elsif headers.nil?
@@ -644,14 +645,10 @@ class DiscordApi
644
645
  headers = { 'User-Agent' => @user_agent }
645
646
  end
646
647
  conn = Faraday.new(url: host, headers: headers)
647
- if path
648
- conn.put(path, data)
649
- else
650
- conn.put('', data)
651
- end
648
+ conn.put(path, data)
652
649
  end
653
650
 
654
- # Sends a HTTP POST request to the specified URL, containing multipart/form-data data structured
651
+ # Sends a HTTP POST/PATCH request to the specified URL, containing multipart/form-data data structured
655
652
  # according to Discord documentation.
656
653
  # See https://docs.discord.com/developers/reference#uploading-files
657
654
  # @param url [String] Full URL including scheme and host; path may be included.
@@ -660,15 +657,14 @@ class DiscordApi
660
657
  # @param files [Array] An array of arrays, each inner-array first has its filename (index 0),
661
658
  # raw file data as a string (index 1), and then the MIME type of the file (index 2).
662
659
  # @param headers [Hash, nil] Optional request headers.
660
+ # @param request_type [Symbol, nil] Specify whether to make a POST (:post) or PATCH (:patch) request. POST by default.
663
661
  # @return [Faraday::Response, nil] The Faraday response object, or nil if an error was encountered.
664
- def file_upload(url, files, payload_json: nil, headers: nil)
665
- split_url = url.split(%r{(http[^/]+)(/.*)}).reject(&:empty?)
666
- if split_url.empty?
667
- @logger.error("Empty/invalid URL provided: #{url}. Cannot perform Discord multipart/form-data POST request.")
668
- return
669
- end
670
- host = split_url[0]
671
- path = split_url[1] if split_url[1]
662
+ def file_upload(url, files, payload_json: nil, headers: nil, request_type: :post)
663
+ parsed_url = parse_request_url(url, 'multipart/form-data')
664
+ return if parsed_url.nil?
665
+
666
+ host = parsed_url[:host]
667
+ path = parsed_url[:path]
672
668
  if headers.is_a?(Hash)
673
669
  headers['User-Agent'] = @user_agent
674
670
  elsif headers.nil?
@@ -677,35 +673,28 @@ class DiscordApi
677
673
  @logger.warn('Invalid headers parameter. It will be discarded.')
678
674
  headers = { 'User-Agent' => @user_agent }
679
675
  end
680
- conn = Faraday.new(url: host, headers: headers, request: :multipart, content_type: 'multipart/form-data')
681
- payload = {}
682
- # FilePart expects File/IO objects as the first argument.
683
- # However, since the function is being given raw data instead of File/IO objects, we should be using ParamPart
684
- # But, ParamPart doesn't let us use a customized Content-Disposition, which is what we need
685
- # So we will just have to wrap the raw data in an IO class with StringIO
686
- if payload_json
687
- payload[:payload_json] = Faraday::Multipart::FilePart.new(
688
- StringIO.new(payload_json),
689
- 'application/json',
690
- nil,
691
- 'Content-Disposition' => 'form-data; name="payload_json"'
692
- )
676
+ conn = Faraday.new(url: host, headers: headers) do |faraday|
677
+ faraday.request :multipart
693
678
  end
679
+ payload = {}
680
+ payload['payload_json'] = Faraday::Multipart::ParamPart.new(payload_json, 'application/json') if payload_json
694
681
  files.each_with_index do |(filename, raw_bytes, mime_type), i|
695
- payload[:"file_#{i}"] = Faraday::Multipart::FilePart.new(
682
+ payload["files[#{i}]"] = Faraday::Multipart::FilePart.new(
696
683
  StringIO.new(raw_bytes),
697
684
  mime_type,
698
- filename,
699
- 'Content-Disposition' => "form-data; name=\"files[#{i}]\"; filename=\"#{filename}\""
685
+ filename
700
686
  )
701
687
  end
702
688
  if payload.empty?
703
689
  @logger.warn("Payload empty, not sending Discord multipart/form-data POST request to #{url}.")
704
690
  nil
705
- elsif path
706
- conn.post(path, payload)
707
691
  else
708
- conn.post('', payload)
692
+ case request_type
693
+ when :post
694
+ conn.post(path, payload)
695
+ when :patch
696
+ conn.patch(path, payload)
697
+ end
709
698
  end
710
699
  end
711
700
 
data/lib/version.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # @version 0.1.4.1
3
4
  class DiscordApi
4
- VERSION = '0.1.3'
5
+ VERSION = '0.1.4.1'
5
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: disrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - hoovad