rancour 0.1 → 0.2

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: 42e2f9a65f4402abdd0fa79fa006e76462727a8028c38bc5205feed56204dd55
4
- data.tar.gz: 11e03d5d55df794755e9ab12178214aa989927506218ffde8e00d90bcd62cc39
3
+ metadata.gz: fc3119655a33ebf4adf688f896205fdbffd29f887cb436321b3686a34aa65e7d
4
+ data.tar.gz: 3640c3ce83ca27e102bde39dd74e2faee510c14532f15ac4af2b562d043cfcba
5
5
  SHA512:
6
- metadata.gz: a4cc23578a18ecb841a43a45af2bb10967bcbd1c8bbdc93f071bb669cddf478e875c713964074698fef13ffe0574abfe94875c2844156edb48ae140d525aa6e7
7
- data.tar.gz: 53f7379a2ef056cf4b4025b92996abc51f0bc83d1c98efa911384494beeb577dab80d138577e401cb04019b53c6376304c67f48e48b58e7b4f402ada469b4b75
6
+ metadata.gz: 6d864bcd094602c6fdff65f7f32ec4d3e3aa2c830a6c9c745e56028bc0dbc545aada3688201f0b251d1fb72dc9c5927f143345ceb359d264cf9b0b9c771e3e12
7
+ data.tar.gz: 9b8f84b330a36d3328a363c521e0ed4ebae3c2790df74dcd05e3ab5f22166ba4d8e6ddc467639dbbd400e35f8bc7029ffbd52481fee4ba81d015e67b7f25e28b
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ class ApplicationCommand
5
+ SUB_COMMAND = 1
6
+ SUB_COMMAND_GROUP = 2
7
+ STRING = 3
8
+ INTEGER = 4
9
+ BOOLEAN = 5
10
+ USER = 6
11
+ CHANNEL = 7
12
+ ROLE = 8
13
+ MENTIONABLE = 9
14
+ NUMBER = 10
15
+ ATTACHMENT = 11
16
+
17
+ attr_accessor :id, :guild_id, :name, :value, :options
18
+
19
+ def self.from_payload(payload)
20
+ new(
21
+ id: payload['id'],
22
+ guild_id: payload['guild_id'],
23
+ name: payload['name'],
24
+ value: payload['value'],
25
+ options: payload['options']
26
+ )
27
+ end
28
+
29
+ def initialize(id:, guild_id:, name:, value:, options:)
30
+ self.id = id
31
+ self.guild_id = guild_id
32
+ self.name = name
33
+ self.value = value
34
+ self.options = []
35
+
36
+ return if options.nil?
37
+
38
+ self.options = options.map { |option| Interaction::Data.from_payload(option) }
39
+ end
40
+ end
41
+ end
@@ -11,34 +11,78 @@ module Rancour
11
11
 
12
12
  @app_id = app_id
13
13
 
14
- @bot_token = bot_token if bot_token.present?
15
- @bearer_token = bearer_token if bearer_token.present?
14
+ @bot_token = bot_token unless bot_token.nil?
15
+ @bearer_token = bearer_token unless bearer_token.nil?
16
16
  end
17
17
 
18
- def register_application_commands(payload:, guild_id: nil)
19
- path = '/commands'
20
- path = "/guilds/#{guild_id}" + path if guild_id.present?
18
+ def fetch_application_commands(guild_id: nil)
19
+ get(commands_path(guild_id: guild_id))
20
+ end
21
21
 
22
- response = connection.post(path, json: payload)
23
- response.raise_for_status
22
+ def register_application_commands(payload, guild_id: nil)
23
+ post(commands_path(guild_id: guild_id), params: payload)
24
+ end
24
25
 
25
- response.json
26
+ def delete_application_command(command_id, guild_id: nil)
27
+ delete(commands_path(guild_id: guild_id) + "/#{command_id}")
26
28
  end
27
29
 
28
30
  private
29
31
 
30
- def connection
31
- @connection ||= HTTPX.with(
32
+ def commands_path(guild_id: nil)
33
+ return "/guilds/#{guild_id}/commands" unless guild_id.nil?
34
+
35
+ '/commands'
36
+ end
37
+
38
+ def get(endpoint)
39
+ process_response(http_client.get(endpoint, headers: { 'content-type': 'application/json' }))
40
+ end
41
+
42
+ def post(endpoint, params: {})
43
+ process_response(http_client.post(endpoint, json: params))
44
+ end
45
+
46
+ def patch(endpoint, params: {})
47
+ process_response(http_client.put(endpoint, json: params))
48
+ end
49
+
50
+ def delete(endpoint)
51
+ process_response(http_client.delete(endpoint))
52
+ end
53
+
54
+ def http_client
55
+ @http_client ||= HTTPX.with(
32
56
  origin: 'https://discord.com',
33
57
  base_path: "/api/v#{DISCORD_API_VERSION}/applications/#{@app_id}",
34
58
  headers: {
35
- authorization: authorization_header
59
+ authorization: authorization_header,
60
+ 'content-type': 'application/json'
36
61
  }
37
62
  )
38
63
  end
39
64
 
65
+ def process_response(response)
66
+ response.raise_for_status
67
+ response.json
68
+ rescue HTTPX::TimeoutError, HTTPX::ResolveError => e
69
+ raise RequestError, e.message
70
+ rescue HTTPX::HTTPError => e
71
+ handle_api_error(e)
72
+ end
73
+
74
+ def handle_api_error(error)
75
+ klass = case error.response.status
76
+ when 400..499 then RequestError
77
+ when 500..599 then ResponseError
78
+ else Error
79
+ end
80
+
81
+ raise klass || Error, error.response.json['message']
82
+ end
83
+
40
84
  def authorization_header
41
- return "Bearer #{@bearer_token}" if @bearer_token.present?
85
+ return "Bearer #{@bearer_token}" unless @bearer_token.nil?
42
86
 
43
87
  "Bot #{@bot_token}"
44
88
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ class Error < StandardError; end
5
+
6
+ class NetworkError < Error; end
7
+ class RequestError < Error; end
8
+ class InvalidParamsError < RequestError; end
9
+ class RateLimitedError < RequestError; end
10
+ class AuthenticationError < RequestError; end
11
+ class ResponseError < Error; end
12
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ class Guild
5
+ attr_accessor :id, :locale, :features
6
+
7
+ def self.from_payload(payload)
8
+ new(
9
+ id: payload['id'],
10
+ locale: payload['locale'],
11
+ features: payload['features']
12
+ )
13
+ end
14
+
15
+ def initialize(id:, locale:, features: [])
16
+ self.id = id
17
+ self.locale = locale
18
+ self.features = features
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ class Images
5
+ class << self
6
+ BASE_URL = 'https://cdn.discordapp.com'
7
+
8
+ def image_url(path, format: 'png')
9
+ "#{BASE_URL}/#{path}.#{format}"
10
+ end
11
+
12
+ def user_avatar_url(user_id, user_avatar, format: 'png')
13
+ image_url("avatars/#{user_id}/#{user_avatar}", format: format)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ module Interaction
5
+ class Choices < Array
6
+ def add(name:, value:)
7
+ self << Choice.new(name: name, value: value)
8
+ end
9
+
10
+ def to_h
11
+ { choices: map(&:to_h) }
12
+ end
13
+ end
14
+
15
+ class Choice
16
+ attr_accessor :name, :value
17
+
18
+ def initialize(name:, value:)
19
+ self.name = name
20
+ self.value = value
21
+ end
22
+
23
+ def to_h
24
+ { name: name.to_s, value: value.to_s }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ module Interaction
5
+ class ChoicesResponse < Response
6
+ def initialize
7
+ super(type: APPLICATION_COMMAND_AUTOCOMPLETE_RESULT, data: Choices.new)
8
+ end
9
+
10
+ def add_choice(name:, value:)
11
+ data.add(name: name, value: value)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ module Interaction
5
+ class Data
6
+ attr_accessor :name, :type, :value, :focused, :options
7
+
8
+ def self.from_payload(payload)
9
+ new(
10
+ name: payload['name'],
11
+ type: payload['type'],
12
+ value: payload['value'],
13
+ focused: payload['focused'],
14
+ options: payload['options']
15
+ )
16
+ end
17
+
18
+ def initialize(name:, type:, value:, focused: false, options: [])
19
+ self.name = name
20
+ self.type = type
21
+ self.value = value
22
+ self.focused = focused
23
+ self.options = {}
24
+
25
+ return if options.nil?
26
+
27
+ self.options = options.to_h do |option|
28
+ entry = self.class.from_payload(option)
29
+ [entry.name.to_sym, entry]
30
+ end
31
+ end
32
+
33
+ def focused_option
34
+ options&.values&.select(&:focused)&.first
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ module Interaction
5
+ class Embed
6
+ RICH = 'rich'
7
+ IMAGE = 'image'
8
+ VIDEO = 'video'
9
+ GIFV = 'gifv'
10
+ ARTICLE = 'article'
11
+ LINK = 'link'
12
+ POLL_RESULT = 'poll_result'
13
+
14
+ attr_accessor :title,
15
+ :type,
16
+ :description,
17
+ :url,
18
+ :timestamp,
19
+ :colour
20
+
21
+ def initialize(title: nil, description: nil, url: nil, type: RICH, timestamp: nil)
22
+ self.title = title
23
+ self.description = description
24
+ self.url = url
25
+ self.type = type
26
+ self.timestamp = timestamp
27
+
28
+ @children = {}
29
+ end
30
+
31
+ %i[image thumbnail video].each do |field|
32
+ define_method("assign_#{field}") do |url:, proxy_url: nil, width: nil, height: nil|
33
+ @children[field] = { url: url }
34
+
35
+ @children[field][:proxy_url] = proxy_url unless proxy_url.nil?
36
+ @children[field][:width] = width unless width.nil?
37
+ @children[field][:height] = height unless height.nil?
38
+ end
39
+ end
40
+
41
+ def assign_author(name:, url: nil, icon_url: nil)
42
+ @children[:author] = { name: name }
43
+ @children[:author][:url] = url unless url.nil?
44
+ @children[:author][:icon_url] = icon_url unless icon_url.nil?
45
+ end
46
+
47
+ def assign_footer(text:, icon_url: nil)
48
+ @children[:footer] = { text: text }
49
+ @children[:footer][:icon_url] = icon_url unless icon_url.nil?
50
+ end
51
+
52
+ def to_h
53
+ {}.tap do |output|
54
+ output[:title] = title unless title.nil?
55
+ output[:description] = description unless description.nil?
56
+ output[:url] = url unless url.nil?
57
+ output[:color] = colour unless colour.nil?
58
+
59
+ @children&.each { |type, entry| output[type] = entry.to_h }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ module Interaction
5
+ class Message
6
+ attr_accessor :tts, :content, :embeds
7
+
8
+ def initialize(tts: false, content: nil)
9
+ self.tts = tts
10
+ self.content = content
11
+ self.embeds = []
12
+ end
13
+
14
+ def add_embed(embed)
15
+ raise "A message can't have more than 10 embeds" if embeds.count == 10
16
+
17
+ embeds << embed
18
+ end
19
+
20
+ def to_h
21
+ {}.tap do |output|
22
+ output[:tts] = tts unless tts.nil?
23
+ output[:content] = content unless content.nil?
24
+ output[:embeds] = embeds&.map(&:to_h) unless embeds.nil?
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ module Interaction
5
+ class MessageResponse < Response
6
+ extend Forwardable
7
+
8
+ def_delegators :@data, :add_embed
9
+
10
+ def initialize
11
+ super(type: CHANNEL_MESSAGE_WITH_SOURCE, data: Message.new)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ module Interaction
5
+ class Request
6
+ PING = 1
7
+ APPLICATION_COMMAND = 2
8
+ MESSAGE_COMPONENT = 3
9
+ APPLICATION_COMMAND_AUTOCOMPLETE = 4
10
+ MODAL_SUBMIT = 5
11
+
12
+ attr_accessor :application_id,
13
+ :context,
14
+ :command,
15
+ :component,
16
+ :modal_submission,
17
+ :guild,
18
+ :locale,
19
+ :token,
20
+ :type,
21
+ :user,
22
+ :version
23
+
24
+ def self.from_payload(payload)
25
+ new.tap do |response|
26
+ %w[application_id context locale token type version].each do |field|
27
+ response.send("#{field}=", payload[field])
28
+ end
29
+
30
+ response.guild = Guild.from_payload(payload['guild']) if payload.key?('guild')
31
+ response.user = User.from_payload(payload.dig('member', 'user')) if payload.key?('member')
32
+
33
+ response.send(:process_data, payload['data'])
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def process_data(data)
40
+ return if data.nil?
41
+
42
+ case type
43
+ when APPLICATION_COMMAND, APPLICATION_COMMAND_AUTOCOMPLETE
44
+ self.command = ApplicationCommand.from_payload(data)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ module Interaction
5
+ class Response
6
+ PONG = 1
7
+ CHANNEL_MESSAGE_WITH_SOURCE = 4
8
+ DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE = 5
9
+ DEFERRED_UPDATE_MESSAGE = 6
10
+ UPDATE_MESSAGE = 7
11
+ APPLICATION_COMMAND_AUTOCOMPLETE_RESULT = 8
12
+ MODAL = 9
13
+ LAUNCH_ACTIVITY = 12
14
+
15
+ attr_accessor :type, :data
16
+
17
+ def initialize(type:, data: nil)
18
+ self.type = type
19
+ self.data = data
20
+ end
21
+
22
+ def to_h
23
+ {
24
+ type: type,
25
+ data: data&.to_h
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rancour
4
+ class User
5
+ attr_accessor :id,
6
+ :username,
7
+ :global_name,
8
+ :discriminator,
9
+ :avatar_url
10
+
11
+ def self.from_payload(payload)
12
+ new.tap do |user|
13
+ %w[id username global_name discriminator].each do |field|
14
+ user.send("#{field}=", payload[field])
15
+ end
16
+
17
+ user.avatar_url = Images.user_avatar_url(payload['id'], payload['avatar'])
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rancour
2
- VERSION = 0.1
4
+ VERSION = 0.2
3
5
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ed25519'
4
+
5
+ module Rancour
6
+ class Webhook
7
+ def self.validate_request(public_key:, body:, signature:, timestamp:)
8
+ verification_key = Ed25519::VerifyKey.new([public_key].pack('H*'))
9
+ verification_key.verify([signature].pack('H*'), "#{timestamp}#{body}")
10
+ rescue Ed25519::VerifyError => e
11
+ raise InvalidParamsError, e
12
+ end
13
+ end
14
+ end
data/lib/rancour.rb CHANGED
@@ -2,4 +2,21 @@
2
2
 
3
3
  $LOAD_PATH.unshift(__dir__)
4
4
 
5
+ require 'rancour/errors'
6
+ require 'rancour/images'
7
+ require 'rancour/webhook'
8
+
5
9
  require 'rancour/client'
10
+ require 'rancour/guild'
11
+ require 'rancour/user'
12
+
13
+ require 'rancour/application_command'
14
+
15
+ require 'rancour/interaction/choice'
16
+ require 'rancour/interaction/data'
17
+ require 'rancour/interaction/embed'
18
+ require 'rancour/interaction/message'
19
+ require 'rancour/interaction/request'
20
+ require 'rancour/interaction/response'
21
+ require 'rancour/interaction/choices_response'
22
+ require 'rancour/interaction/message_response'
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rancour
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Bogan
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-02 00:00:00.000000000 Z
10
+ date: 2025-04-11 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ed25519
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.3'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: httpx
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -31,10 +45,22 @@ extensions: []
31
45
  extra_rdoc_files: []
32
46
  files:
33
47
  - lib/rancour.rb
48
+ - lib/rancour/application_command.rb
34
49
  - lib/rancour/client.rb
35
- - lib/rancour/interaction.rb
36
- - lib/rancour/interaction_response.rb
50
+ - lib/rancour/errors.rb
51
+ - lib/rancour/guild.rb
52
+ - lib/rancour/images.rb
53
+ - lib/rancour/interaction/choice.rb
54
+ - lib/rancour/interaction/choices_response.rb
55
+ - lib/rancour/interaction/data.rb
56
+ - lib/rancour/interaction/embed.rb
57
+ - lib/rancour/interaction/message.rb
58
+ - lib/rancour/interaction/message_response.rb
59
+ - lib/rancour/interaction/request.rb
60
+ - lib/rancour/interaction/response.rb
61
+ - lib/rancour/user.rb
37
62
  - lib/rancour/version.rb
63
+ - lib/rancour/webhook.rb
38
64
  homepage: http://github.com/waferbaby/rancour
39
65
  licenses:
40
66
  - MIT
File without changes
File without changes