async-discord 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 33834a7af00dffadb4f4c2c050e62dda6aa9823401a7be3814616422bce948a1
4
+ data.tar.gz: 40e8682837b6325b3d1a14bdb8913b535cccc96d5cd569e81822a63851275850
5
+ SHA512:
6
+ metadata.gz: d07684c7f494a3cc8893b345190d2fd495b5ec57251be6a11f158e22c12aeade6a6f975ae078287bc7e1171db4adaf942f426009a685caf39df731725f0ad96d
7
+ data.tar.gz: d316e91be104bf9634f4ec8242c7c9f4410e0a823de3627c62232d23c7c7c85cf099c158cfb3e7adba9150d1ce1808bdd0e3b54a62774a5f18414891f5e8415e
checksums.yaml.gz.sig ADDED
Binary file
@@ -0,0 +1,46 @@
1
+ require_relative "representation"
2
+
3
+ module Async
4
+ module Discord
5
+ class Message < Representation
6
+ end
7
+
8
+ class Channel < Representation
9
+ def send_message(content)
10
+ payload = {
11
+ content: content
12
+ }
13
+
14
+ Message.post(@resource.with(path: "messages"), payload)
15
+ end
16
+
17
+ def id
18
+ self.value[:id]
19
+ end
20
+
21
+ def text?
22
+ self.value[:type] == 0
23
+ end
24
+
25
+ def voice?
26
+ self.value[:type] == 2
27
+ end
28
+ end
29
+
30
+ class Channels < Representation
31
+ def each(&block)
32
+ return to_enum unless block_given?
33
+
34
+ self.value.each do |value|
35
+ path = "/api/v10/channels/#{value[:id]}"
36
+
37
+ yield Channel.new(@resource.with(path: path), value: value)
38
+ end
39
+ end
40
+
41
+ def to_a
42
+ each.to_a
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literals: true
2
+ #
3
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require_relative 'representation'
24
+
25
+ require_relative 'guilds'
26
+ require_relative 'gateway'
27
+
28
+ module Async
29
+ module Discord
30
+ class Client < Async::REST::Resource
31
+ ENDPOINT = Async::HTTP::Endpoint.parse("https://discord.com/api/v10/")
32
+ USER_AGENT = "#{self.name} (https://github.com/socketry/async-discord, v#{Async::Discord::VERSION})"
33
+
34
+ def authenticated(bot: nil, bearer: nil)
35
+ headers = {}
36
+
37
+ headers["user-agent"] ||= USER_AGENT
38
+
39
+ if bot
40
+ headers["authorization"] = "Bot #{bot}"
41
+ elsif bearer
42
+ headers["authorization"] = "Bearer #{bearer}"
43
+ else
44
+ raise ArgumentError, "You must provide either a bot or bearer token!"
45
+ end
46
+
47
+ return self.with(headers: headers)
48
+ end
49
+
50
+ def guilds
51
+ Guilds.new(self.with(path: "users/@me/guilds"))
52
+ end
53
+
54
+ def gateway
55
+ Gateway.new(self.with(path: "gateway/bot"))
56
+ end
57
+
58
+ def channel(id)
59
+ Channel.new(self.with(path: "channels/#{id}"))
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,196 @@
1
+ require_relative "representation"
2
+
3
+ require "async/websocket"
4
+
5
+ module Async
6
+ module Discord
7
+ class GatewayConnection < Async::WebSocket::Connection
8
+ # Gateway Opcodes:
9
+ DISPATCH = 0
10
+ HEARTBEAT = 1
11
+ IDENTIFY = 2
12
+ PRESENCE_UPDATE = 3
13
+ VOICE_STATE_UPDATE = 4
14
+ RESUME = 6
15
+ RECONNECT = 7
16
+ REQUEST_GUILD_MEMBERS = 8
17
+ INVALID_SESSION = 9
18
+ HELLO = 10
19
+ HEARTBEAT_ACK = 11
20
+ REQUEST_SOUNDBOARD_SOUNDS = 31
21
+
22
+ # Gateway Error Codes
23
+ ERROR_CODES = {
24
+ 4000 => "UNKNOWN_ERROR",
25
+ 4001 => "UNKNOWN_OPCODE",
26
+ 4002 => "DECODE_ERROR",
27
+ 4003 => "NOT_AUTHENTICATED",
28
+ 4004 => "AUTHENTICATION_FAILED",
29
+ 4005 => "ALREADY_AUTHENTICATED",
30
+ 4007 => "INVALID_SEQUENCE",
31
+ 4008 => "RATE_LIMITED",
32
+ 4009 => "SESSION_TIMEOUT",
33
+ 4010 => "INVALID_SHARD",
34
+ 4011 => "SHARDING_REQUIRED",
35
+ 4012 => "INVALID_VERSION",
36
+ 4013 => "INVALID_INTENT",
37
+ 4014 => "DISALLOWED_INTENT"
38
+ }
39
+
40
+ # Guild Intents:
41
+ module Intent
42
+ GUILDS = 1 << 0
43
+ GUILD_MEMBERS = 1 << 1
44
+ GUILD_MODERATION = 1 << 2
45
+ GUILD_EXPRESSIONS = 1 << 3
46
+ GUILD_INTEGRATIONS = 1 << 4
47
+ GUILD_WEBHOOKS = 1 << 5
48
+ GUILD_INVITES = 1 << 6
49
+ GUILD_VOICE_STATES = 1 << 7
50
+ GUILD_PRESENCES = 1 << 8
51
+ GUILD_MESSAGES = 1 << 9
52
+ GUILD_MESSAGE_REACTIONS = 1 << 10
53
+ GUILD_MESSAGE_TYPING = 1 << 11
54
+ DIRECT_MESSAGES = 1 << 12
55
+ DIRECT_MESSAGE_REACTIONS = 1 << 13
56
+ DIRECT_MESSAGE_TYPING = 1 << 14
57
+ MESSAGE_CONTENT = 1 << 15
58
+ GUILD_SCHEDULED_EVENTS = 1 << 16
59
+ AUTO_MODERATION_CONFIGURATION = 1 << 20
60
+ AUTO_MODERATION_EXECUTION = 1 << 21
61
+ GUILD_MESSAGE_POLLS = 1 << 24
62
+ DIRECT_MESSAGE_POLLS = 1 << 25
63
+ end
64
+
65
+ DEFAULT_INTENT = Intent::GUILDS | Intent::GUILD_MESSAGES | Intent::DIRECT_MESSAGES
66
+
67
+ DEFAULT_PROPERTIES = {
68
+ os: RUBY_PLATFORM,
69
+ browser: Async::Discord.name,
70
+ device: Async::Discord.name,
71
+ }
72
+
73
+ DEFAULT_PRESENCE = {
74
+ status: "online",
75
+ afk: false,
76
+ activities: [],
77
+ }
78
+
79
+ def initialize(...)
80
+ super
81
+
82
+ @heartbeat_task = nil
83
+ @sequence = nil
84
+ end
85
+
86
+ def close(...)
87
+ if heartbeat_task = @heartbeat_task
88
+ @heartbeat_task = nil
89
+ heartbeat_task.stop
90
+ end
91
+
92
+ super
93
+ end
94
+
95
+ def identify(**identity)
96
+ while message = self.read
97
+ payload = message.parse
98
+
99
+ case payload[:op]
100
+ when HELLO
101
+ @heartbeat_task ||= self.run_heartbeat(payload[:d][:heartbeat_interval])
102
+ break
103
+ else
104
+ Console.warn(self, "Unexpected payload during identify: #{payload}")
105
+ end
106
+ end
107
+
108
+ identity[:intents] ||= DEFAULT_INTENT
109
+ identity[:properties] ||= DEFAULT_PROPERTIES
110
+ identity[:presence] ||= DEFAULT_PRESENCE
111
+
112
+ Console.debug(self, "Identifying...", identity: identity)
113
+ ::Protocol::WebSocket::TextMessage.generate(op: IDENTIFY, d: identity).send(self)
114
+
115
+ while message = self.read
116
+ payload = message.parse
117
+
118
+ if payload[:op] == DISPATCH && payload[:t] == "READY"
119
+ Console.info(self, "Identified successfully.")
120
+
121
+ # Store the sequence number for future heartbeats:
122
+ @sequence = payload[:s]
123
+
124
+ return payload[:d]
125
+ elsif payload[:op] == INVALID_SESSION
126
+ Console.warn(self, "Invalid session.")
127
+ break
128
+ else
129
+ Console.warn(self, "Unexpected payload during identify: #{payload}")
130
+ end
131
+ end
132
+ end
133
+
134
+ def listen
135
+ while message = self.read
136
+ payload = message.parse
137
+
138
+ case payload[:op]
139
+ when DISPATCH
140
+ @sequence = payload[:s]
141
+ yield payload
142
+ when HEARTBEAT
143
+ Console.debug(self, "Received heartbeat request.", payload: payload)
144
+ heartbeat_message = ::Protocol::WebSocket::TextMessage.generate(op: HEARTBEAT_ACK, d: @sequence)
145
+ heartbeat_message.send(self)
146
+ when HEARTBEAT_ACK
147
+ Console.debug(self, "Received heartbeat ACK.", payload: payload)
148
+ else
149
+ yield payload
150
+ end
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ def run_heartbeat(duration_ms)
157
+ duration = duration_ms / 1000.0
158
+ Console.debug(self, "Running heartbeat every #{duration} seconds.")
159
+
160
+ Async do |task|
161
+ sleep(duration * rand)
162
+
163
+ while !self.closed?
164
+ Console.debug(self, "Sending heartbeat.", sequence: @sequence)
165
+ heartbeat_message = ::Protocol::WebSocket::TextMessage.generate(op: HEARTBEAT, d: @sequence)
166
+ heartbeat_message.send(self)
167
+ self.flush
168
+
169
+ sleep(duration)
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ class Gateway < Representation
176
+ def url
177
+ self.value[:url]
178
+ end
179
+
180
+ def shards
181
+ self.value[:shards]
182
+ end
183
+
184
+ def session_start_limit
185
+ self.value[:session_start_limit]
186
+ end
187
+
188
+ def connect(shard: nil, &block)
189
+ endpoint = Async::HTTP::Endpoint.parse(self.url, alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
190
+
191
+ Console.info(self, "Connecting to gateway...", endpoint: endpoint)
192
+ Async::WebSocket::Client.connect(endpoint, handler: GatewayConnection, &block)
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,32 @@
1
+ require_relative "representation"
2
+ require_relative "channels"
3
+
4
+ module Async
5
+ module Discord
6
+ class Guild < Representation
7
+ def channels
8
+ Channels.new(@resource.with(path: "channels"))
9
+ end
10
+
11
+ def id
12
+ self.value[:id]
13
+ end
14
+ end
15
+
16
+ class Guilds < Representation
17
+ def each(&block)
18
+ return to_enum unless block_given?
19
+
20
+ self.value.each do |value|
21
+ path = "/api/v10/guilds/#{value[:id]}"
22
+
23
+ yield Guild.new(@resource.with(path: path), value: value)
24
+ end
25
+ end
26
+
27
+ def to_a
28
+ each.to_a
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literals: true
2
+ #
3
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'async/rest/representation'
24
+ require 'async/rest/wrapper/form'
25
+
26
+ module Async
27
+ module Discord
28
+ class Wrapper < Async::REST::Wrapper::JSON
29
+ end
30
+
31
+ class Representation < Async::REST::Representation[Wrapper]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ module Async
7
+ module Discord
8
+ VERSION = "0.1.0"
9
+ end
10
+ end
data/license.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright, 2024, by Samuel Williams.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/readme.md ADDED
@@ -0,0 +1,3 @@
1
+ # Async::Discord
2
+
3
+ Provides a simple `Async::REST` client for interacting with the Discord API.
data.tar.gz.sig ADDED
Binary file
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: async-discord
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
14
+ ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
15
+ CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
16
+ MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
17
+ MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
18
+ bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
19
+ igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
20
+ 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
21
+ sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
22
+ e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
23
+ XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
24
+ RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
25
+ tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
26
+ zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
27
+ xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
28
+ BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
29
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
30
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
31
+ cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
32
+ xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
33
+ c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
34
+ 8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
35
+ JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
36
+ eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
37
+ Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
38
+ voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
39
+ -----END CERTIFICATE-----
40
+ date: 2024-11-25 00:00:00.000000000 Z
41
+ dependencies:
42
+ - !ruby/object:Gem::Dependency
43
+ name: async-rest
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: async-websocket
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ description:
71
+ email:
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - lib/async/discord/channels.rb
77
+ - lib/async/discord/client.rb
78
+ - lib/async/discord/gateway.rb
79
+ - lib/async/discord/guilds.rb
80
+ - lib/async/discord/representation.rb
81
+ - lib/async/discord/version.rb
82
+ - license.md
83
+ - readme.md
84
+ homepage: https://github.com/socketry/async-discord
85
+ licenses:
86
+ - MIT
87
+ metadata:
88
+ source_code_uri: https://github.com/socketry/async-discord.git
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '3.1'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubygems_version: 3.5.22
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: Build Discord bots and use real time messaging.
108
+ test_files: []
metadata.gz.sig ADDED
Binary file