revoltrb 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: be934ade9a4122fe6ea640bf5aafe2930eb1a11ee2a8f4340d282b3fa99f163c
4
+ data.tar.gz: 6e384401d88c604fa0bfa05b1a840697c0382a288683ac27db23d98e425b4a25
5
+ SHA512:
6
+ metadata.gz: e36bad7e4adec3333f2341b6d1e85aadc1cbab691552d69a1f9e777a7df5722b2fc048e1e98be0e361a4a1447d2597e65f3cc8888d7afb9fb589e9dfa4f79b27
7
+ data.tar.gz: db5fa947e54394afb19495145a3b3beb8ef50bf6bc17c584fc8369659a281603c48f522fe7a7c0ea4dcf56c7a1f1a35185d5a593ffaea5586951862a19a40535
data/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # revoltrb
2
+
3
+ Revoltrb is a Ruby package (a.k.a. Gem) that allows you to make Revolt.chat bots using the Ruby programming language.
4
+
5
+ This package (a.k.a. Gem) is not officially endorsed by Revolt.chat amd this is not an official Revolt.chat product.
6
+
7
+ You need Ruby 3.0 or newer in order to use this package (Ruby 3.2 or newer is recommended)
8
+
9
+ ## ToDo
10
+
11
+ This list contains a list of things that I know is broken and gotta fix. Contributing will be super helpful.
12
+
13
+ - Fix reactions support
14
+ - Fix obtaining server information
15
+ - Fix not being able to have an embed color
16
+
17
+ ## Setup
18
+
19
+ You can install Revoltrb through several methods:
20
+
21
+ #### Method 1: Install from rubygems.org (Bundler)
22
+
23
+ Add the following to your Gemfile file and run the "bundle install" command:
24
+
25
+ ```ruby
26
+ gem 'discordrb'
27
+ ```
28
+
29
+ #### Method 2: Install via Git
30
+
31
+ Add the following to your Gemfile file and run the "bundle install" command:
32
+
33
+ ```ruby
34
+ gem 'revoltrb', git: 'https://gitlab.com/roxannewolf/revoltrb'
35
+ ```
36
+
37
+ #### Troubleshooting
38
+
39
+ If you encounter the "Exited with code: 16 output:Ignoring debug-1.7.1 because its extensions are not built." error, most likely, there is something wrong with the parser package. This can be fixed by installing parser manually (gem install parser)
40
+
41
+ ## Usage
42
+
43
+ You can make a simple bot like this:
44
+
45
+ ```ruby
46
+ require 'revoltrb'
47
+
48
+ bot = Revoltrb::RevoltBot.new('REVOLT_BOT_TOKEN_HERE')
49
+
50
+ bot.on_message do |message|
51
+ next if message['author'] == bot.user_id
52
+ content = message['content']&.downcase
53
+ channel_id = message['channel']
54
+
55
+ if content.include?("ping")
56
+ bot.send_message(channel_id, text: "Pong!")
57
+ end
58
+ end
59
+
60
+ begin
61
+ unless bot.login
62
+ puts "Bot failed to log in. Exiting."
63
+ exit(1)
64
+ end
65
+ puts "Bot is ONLINE and READY! Press Ctrl+C to stop."
66
+ bot.instance_variable_get(:@websocket_thread).join
67
+ rescue Interrupt
68
+ puts "\nCtrl+C detected. Shutting down bot"
69
+ rescue => e
70
+ puts "An unhandled error occurred in the main script loop: #{e.message}"
71
+ puts e.backtrace.join("\n")
72
+ ensure
73
+ bot.stop
74
+ puts "Bot process ended."
75
+ end
76
+ ```
77
+
78
+ or you can make a bot with full prefix commands like this:
79
+
80
+ ```ruby
81
+ require 'revoltrb'
82
+
83
+ bot = Revoltrb::RevoltBot.new('REVOLT_BOT_TOKEN_HERE', prefix: '!')
84
+
85
+ bot.command(:ping) do |message, args|
86
+ channel_id = message['channel']
87
+ bot.send_message(channel_id, text: "Pong! You sent: #{args.join(' ')}")
88
+ end
89
+
90
+ begin
91
+ unless bot.login
92
+ puts "Bot failed to log in. Exiting."
93
+ exit(1)
94
+ end
95
+ puts "Bot is online and running. Press Ctrl+C to stop."
96
+ bot.instance_variable_get(:@websocket_thread).join
97
+ rescue Interrupt
98
+ puts "\nCtrl+C detected. Shutting down bot gracefully..."
99
+ rescue => e
100
+ puts "An unhandled error occurred in the main script loop: #{e.message}"
101
+ puts e.backtrace.join("\n")
102
+ ensure
103
+ bot.stop
104
+ puts "Bot process ended."
105
+ end
106
+ ```
107
+
108
+ ## Support and Help
109
+
110
+ If you need help with this ruby package (a.k.a. Gem), feel free to join the [Roxanne Studios Revolt Server](https://rvlt.gg/r4Ee2R1Z) and use the REVOLTRB category to talk about this package.
111
+
112
+ ## Contributing
113
+
114
+ We are working to support more of Revolt's API. Remember, the creator of this package is only a Ruby beginner so contributing to this project will mean a lot and can help with more coverage with the Revolt.chat API. Opening issues and Pull requests are welcome at our [Gitlab repo](https://gitlab.com/roxannewolf/revoltrb) and [Codeberg repo](https://codeberg.org/roxannewolf/revoltrb)
@@ -0,0 +1,488 @@
1
+ # lib/revoltrb/bot.rb
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+ require 'websocket-client-simple' # This is required for WebSocket communication
6
+ require 'thread'
7
+ require 'time'
8
+ require_relative 'debuglogger'
9
+ require_relative 'request_queue'
10
+
11
+ module Revoltrb
12
+ class RevoltBot
13
+ attr_reader :token, :user_id, :bot_name, :servers, :prefix, :bot_owner_id, :bot_discriminator, :bot_discoverable, :bot_creation_date
14
+ attr_accessor :websocket_url, :api_url, :cdn_url
15
+ # Initializes the bot with the provided token, API endpoints, and configuration.
16
+ def initialize(token, api_url: 'https://api.revolt.chat', websocket_url: 'wss://app.revolt.chat/events', cdn_url: 'https://cdn.revoltusercontent.com', prefix: nil, debuglogs: false, selfbot: false)
17
+ @token = token
18
+ @api_url = api_url
19
+ @websocket_url = websocket_url
20
+ @cdn_url = cdn_url
21
+
22
+ @user_id = nil
23
+ @bot_name = nil
24
+ @servers = {}
25
+ @commands = {}
26
+ @message_handlers = []
27
+
28
+ @websocket = nil
29
+ @websocket_thread = nil
30
+ @heartbeat_interval = 30 # Default heartbeat interval in seconds
31
+ @last_heartbeat_sent = Time.now.to_i
32
+ @running = false
33
+ @ready_event_received = false
34
+ @logger = Revoltrb::DebugLogger.new(debuglogs)
35
+ @request_queue = Revoltrb::RequestQueue.new(500)
36
+
37
+ @prefix = "!"
38
+ @prefix = prefix if prefix
39
+ @selfbot = selfbot
40
+ @logger.debug "RevoltBot initialized. API: #{@api_url}, WS: #{@websocket_url}, CDN: #{@cdn_url}, Prefix: #{@prefix}"
41
+ end
42
+
43
+ def get_botinfo
44
+ {
45
+ 'bot_id' => @user_id,
46
+ 'bot_name' => @bot_name,
47
+ 'bot_discriminator' => @bot_discriminator,
48
+ 'bot_ownerid' => @bot_owner_id,
49
+ 'bot_discoverable' => @bot_discoverable,
50
+ 'bot_creationdate' => @bot_creation_date,
51
+ 'bot_prefix' => @prefix,
52
+ 'bot_token' => @token
53
+ }
54
+ end
55
+
56
+ # Log into the Revolt.chat bot
57
+ def login
58
+ @logger.debug "BOT: Bot attempting to start...."
59
+ # Step 1: Fetch initial bot user details via REST API using /users/@me with X-Bot-Token | As confirmed, /users/@me returns the bot's user object directly.
60
+ uri = URI("#{@api_url}/users/@me")
61
+ req = Net::HTTP::Get.new(uri)
62
+ _add_auth_header(req)
63
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
64
+ http.request(req)
65
+ end
66
+
67
+ if res.is_a?(Net::HTTPSuccess)
68
+ bot_user_data = JSON.parse(res.body)
69
+ @logger.debug "Response Body (successful login attempt from /users/@me): #{res.body}"
70
+ @user_id = bot_user_data&.[]('_id')
71
+ @bot_name = bot_user_data&.[]('username')
72
+ @bot_discriminator = bot_user_data&.[]('discriminator')
73
+ bot_specific_info = bot_user_data&.[]('bot')
74
+ @bot_owner_id = bot_specific_info&.[]('owner')
75
+ @bot_discoverable = nil
76
+ @bot_creation_date = Time.at(bot_user_data&.[]('created_at').to_i / 1000) rescue nil
77
+ if @user_id.nil? || @bot_name.nil?
78
+ @logger.debug "Error: Essential properties (_id, username) missing or nil in API response from /users/@me."
79
+ @logger.debug "Please inspect the 'Response Body' above for unexpected format."
80
+ return false
81
+ end
82
+
83
+ @logger.debug "Successfully identified as #{@bot_name} (ID: #{@user_id}) via REST API."
84
+ @logger.debug "Bot owner ID after parsing: #{@bot_owner_id}"
85
+ @logger.debug "Owner ID: #{@bot_owner_id}, Discoverable: #{@bot_discoverable.inspect}, Created: #{@bot_creation_date}"
86
+ else
87
+ @logger.debug "Initial REST API call failed: #{res.message} (Code: #{res.code})"
88
+ @logger.debug "Response Body: #{res.body}"
89
+ @logger.debug "Please check your bot token. Cannot proceed with WebSocket connection."
90
+ return false
91
+ end
92
+ # Step 2: Connect to the WebSocket
93
+ @running = true
94
+ connect_websocket
95
+ @request_queue.start_processing
96
+ true
97
+ end
98
+
99
+ def connect_websocket
100
+ if @websocket && @websocket.open? && @running
101
+ @logger.debug "WebSocket already open and running."
102
+ return
103
+ end
104
+
105
+ @logger.debug "Connecting to WebSocket: #{@websocket_url}"
106
+ ws_url_with_token = "#{@websocket_url}?token=#{@token}"
107
+ bot_instance = self
108
+ thread_logger = @logger
109
+
110
+ @websocket_thread = Thread.new do
111
+ begin
112
+ @websocket = WebSocket::Client::Simple.connect ws_url_with_token
113
+ @websocket.on :open do
114
+ thread_logger.debug "WebSocket connection opened!"
115
+ end
116
+ @websocket.on :message do |msg|
117
+ bot_instance.handle_websocket_message(msg.data)
118
+ end
119
+ @websocket.on :close do |e|
120
+ close_code = e&.code || 'N/A'
121
+ close_reason = e&.reason || 'No reason provided'
122
+ thread_logger.debug "WebSocket closed: #{close_code} - #{close_reason}."
123
+ bot_instance.instance_variable_set(:@websocket, nil)
124
+ if bot_instance.instance_variable_get(:@running)
125
+ thread_logger.debug "Attempting to reconnect in 5 seconds..."
126
+ sleep 5
127
+ bot_instance.connect_websocket
128
+ else
129
+ thread_logger.debug "BOT: Bot has stopped and will not try to reconnect" # Use local thread_logger
130
+ end
131
+ end
132
+ @websocket.on :error do |e|
133
+ error_message = e&.message || 'Unknown error'
134
+ thread_logger.debug "WebSocket error: #{error_message}"
135
+ @websocket.close if @websocket&.open?
136
+ end
137
+
138
+ while bot_instance.instance_variable_get(:@running)
139
+ if Time.now.to_i - @last_heartbeat_sent > @heartbeat_interval
140
+ bot_instance.send_heartbeat
141
+ end
142
+ sleep 1
143
+ end
144
+ thread_logger.debug "WebSocket thread loop finished."
145
+ rescue => e
146
+ thread_logger.debug "WebSocket thread unhandled exception: #{e.message}"
147
+ thread_logger.debug e.backtrace.join("\n")
148
+ bot_instance.instance_variable_set(:@websocket, nil)
149
+ if bot_instance.instance_variable_get(:@running)
150
+ thread_logger.debug "Attempting to reconnect in 5 seconds due to unhandled error..."
151
+ sleep 5
152
+ bot_instance.connect_websocket
153
+ else
154
+ thread_logger.debug "Bot is stopped, not attempting to reconnect after unhandled error."
155
+ end
156
+ end
157
+ end
158
+ sleep 1
159
+ end
160
+
161
+ def send_heartbeat
162
+ if @websocket && @websocket.open?
163
+ payload = { type: 'Ping', data: Time.now.to_i }
164
+ @websocket.send(payload.to_json)
165
+ @last_heartbeat_sent = Time.now.to_i
166
+ end
167
+ rescue OpenSSL::SSL::SSLError => e
168
+ @logger.debug "AN ERROR HAS OCCURED: Error sending heartbeat (SSL): #{e.message}"
169
+ @websocket&.close
170
+ rescue => e
171
+ @logger.debug "AN ERROR HAS OCCURED: Error sending heartbeat: #{e.message}"
172
+ @websocket&.close
173
+ end
174
+
175
+ def handle_websocket_message(raw_data)
176
+ begin
177
+ event = JSON.parse(raw_data)
178
+ event_type = event['type']
179
+ case event_type
180
+ when 'Ready'
181
+ @logger.debug "Received 'Ready' event. Populating initial data..."
182
+ if event['servers']
183
+ event['servers'].each do |server_data|
184
+ @servers[server_data['_id']] = {
185
+ 'name' => server_data['name'],
186
+ 'id' => server_data['_id']
187
+ }
188
+ @logger.debug "Stored server ID from Ready event: #{server_data['_id'].inspect}" # New debug
189
+ end
190
+ @logger.debug "Loaded #{event['servers'].count} real servers from 'Ready' event."
191
+ else
192
+ @logger.debug "'Ready' event received but no 'servers' array found."
193
+ end
194
+ @ready_event_received = true
195
+ @logger.debug "@ready_event_received set to true."
196
+ when 'Message'
197
+ unless event['author'] == @user_id
198
+ process_message(event)
199
+ end
200
+ when 'Authenticated'
201
+ @logger.debug "Successfully authenticated with WebSocket."
202
+ when 'Pong'
203
+ # @logger.debug "Received Pong response."
204
+ when 'Error'
205
+ @logger.debug "AN ERROR HAS OCCURED: Revolt API Error received via WebSocket: #{event['error']}"
206
+ else
207
+ # @logger.debug "AN ERROR HAS OCCURED: Unhandled WebSocket event type: #{event_type}"
208
+ end
209
+ rescue JSON::ParserError => e
210
+ @logger.debug "Failed to parse WebSocket message as JSON: #{e.message}"
211
+ @logger.debug "Raw message: #{raw_data}"
212
+ rescue => e
213
+ @logger.debug "Error processing WebSocket message: #{e.message}"
214
+ @logger.debug e.backtrace.join("\n")
215
+ end
216
+ end
217
+
218
+ def on_message(&block)
219
+ @message_handlers << block
220
+ end
221
+ def command(command_name, required_permissions: [], nsfw_channel_required: false, &block)
222
+ cmd_key = command_name.to_s.downcase
223
+ @commands[cmd_key] = {
224
+ 'block' => block,
225
+ 'permissions' => required_permissions,
226
+ 'nsfw_cmd' => nsfw_channel_required
227
+ }
228
+ @logger.debug "Command '#{command_name}' registered with permissions: #{required_permissions.inspect}, NSFW required: #{nsfw_channel_required}."
229
+ end
230
+
231
+ def check_permissions(message, required_permissions, nsfw_channel_required)
232
+ @logger.debug "check_permissions called with required_permissions: #{required_permissions.inspect}, NSFW required: #{nsfw_channel_required}."
233
+ @logger.debug "message['channel'] value: #{message['channel'].inspect}"
234
+
235
+ server_id_from_message = message['member']&.[]('_id')&.[]('server')
236
+ @logger.debug "message['member']['_id']['server'] value: #{server_id_from_message.inspect}"
237
+
238
+ if required_permissions.empty? && !nsfw_channel_required
239
+ @logger.debug "Permission Check: No specific permissions or NSFW requirement. Allowing command."
240
+ return true
241
+ end
242
+ user_id = message['author']
243
+
244
+ @logger.debug "\n--- PERMISSION DEBUG START ---"
245
+ @logger.debug "User ID from message (user_id): '#{user_id}' | Bot Owner ID stored (@bot_owner_id): '#{@bot_owner_id}'"
246
+ @logger.debug "Are they equal? (user_id == @bot_owner_id): #{user_id == @bot_owner_id}"
247
+ # @logger.debug "User ID char codes: #{user_id.each_char.map(&:ord).join(', ')}"
248
+ # @logger.debug "Bot Owner ID char codes: #{@bot_owner_id.each_char.map(&:ord).join(', ')}"
249
+ @logger.debug "--- PERMISSION DEBUG END ---\n"
250
+ # --- BotOwner Check ---
251
+ if required_permissions.include?('BotOwner')
252
+ if user_id == @bot_owner_id
253
+ @logger.debug "Permission Check: User is the bot owner. Allowing command."
254
+ return true
255
+ else
256
+ @logger.debug "Permission Check: User is NOT the bot owner. Denying command."
257
+ return false
258
+ end
259
+ end
260
+ # --- NSFW Channel Check ---
261
+ channel_id = message['channel']
262
+ if server_id_from_message.nil?
263
+ if nsfw_channel_required
264
+ @logger.debug "Permission Check: Command requires NSFW channel, but message is in DM. Denying command."
265
+ return false
266
+ end
267
+ else
268
+ channel_details = get_channel_details(channel_id)
269
+ if channel_details.nil?
270
+ @logger.debug "Permission Check: Command requires NSFW channel but could not retrieve channel details. Denying command to be safe."
271
+ return false
272
+ end
273
+ is_channel_nsfw = channel_details['nsfw'] || false
274
+ if nsfw_channel_required && !is_channel_nsfw
275
+ @logger.debug "Permission Check: Command requires NSFW channel, but current channel is NOT NSFW marked. Denying."
276
+ return false
277
+ elsif !nsfw_channel_required && is_channel_nsfw
278
+ @logger.debug "Permission Check: Command does not require NSFW channel, but is in NSFW marked channel. Allowing."
279
+ end
280
+ end
281
+
282
+ if !required_permissions.empty?
283
+ @logger.debug "User #{user_id} is not the bot owner. Requires permissions: #{required_permissions.inspect}. (Full permission check for non-owner, server-specific permissions not implemented)."
284
+ return false
285
+ end
286
+
287
+ @logger.debug "Permission Check: All checks passed. Allowing command."
288
+ true
289
+ end
290
+
291
+ def process_message(message)
292
+ unless @ready_event_received
293
+ @logger.debug "AN ERROR HAS OCCURED: Bot is not ready for commands"
294
+ return
295
+ end
296
+
297
+ unless @selfbot
298
+ return if message['author'] == @user_id
299
+ end
300
+
301
+ content = message['content']&.strip
302
+ return if content.nil? || content.empty?
303
+ @logger.debug "Full Message object received in process_message: #{message.inspect}"
304
+
305
+ @commands.each do |cmd_name, cmd_data|
306
+ command_full_string = "#{@prefix}#{cmd_name}"
307
+ if content.downcase.start_with?(command_full_string.downcase)
308
+ args_string = content[command_full_string.length..]&.strip
309
+ args = args_string.to_s.split(/\s+/)
310
+ args = [] if args == ['']
311
+ # --- Permission Check (Pass both permissions and nsfw_channel_required to check_permissions) ---
312
+ if check_permissions(message, cmd_data['permissions'], cmd_data['nsfw_cmd'])
313
+ @logger.debug "Executing command: '#{cmd_name}' with args: #{args.inspect}"
314
+ cmd_data['block'].call(message, args)
315
+ else
316
+ channel_id = message['channel']
317
+ if cmd_data['nsfw_cmd'] && message['member']
318
+ channel_details = get_channel_details(channel_id)
319
+ if channel_details && !channel_details['nsfw']
320
+ self.send_message(channel_id, text: "⛔ A NSFW marked channel is required to use the following command: `#{cmd_name}`")
321
+ else
322
+ self.send_message(channel_id, text: "⛔ You don't have permission to use the `#{cmd_name}` command.")
323
+ end
324
+ else
325
+ self.send_message(channel_id, text: "⛔ You don't have permission to use the `#{cmd_name}` command.")
326
+ end
327
+ end
328
+ return
329
+ end
330
+ end
331
+
332
+ @message_handlers.each do |handler|
333
+ @logger.debug "Calling general message handler for: '#{content}'"
334
+ handler.call(message)
335
+ end
336
+ end
337
+
338
+ def send_message(channel_id, text: nil, embeds: nil, masquerade_name: nil, masquerade_avatar_url: nil)
339
+ if text.nil? && (embeds.nil? || embeds.empty?)
340
+ @logger.debug "AN ERROR HAS OCCURED: Cannot send empty message or embeds."
341
+ return
342
+ end
343
+
344
+ payload = {}
345
+ payload[:content] = text if text
346
+
347
+ if embeds && !embeds.empty?
348
+ filtered_embeds = embeds.map do |embed|
349
+ supported_keys = ['title', 'description', 'color', 'url', 'image']
350
+ embed.select { |k, v| supported_keys.include?(k.to_s) }
351
+ end
352
+ payload[:embeds] = filtered_embeds
353
+ end
354
+ if masquerade_name || masquerade_avatar_url
355
+ payload[:masquerade] = {}
356
+ payload[:masquerade][:name] = masquerade_name if masquerade_name
357
+ payload[:masquerade][:avatar] = masquerade_avatar_url if masquerade_avatar_url
358
+ end
359
+
360
+ @request_queue.enqueue do
361
+ @logger.debug "Attempting to send message to channel '#{channel_id}' via queue..."
362
+ uri = URI("#{@api_url}/channels/#{channel_id}/messages")
363
+ req = Net::HTTP::Post.new(uri)
364
+ _add_auth_header(req)
365
+ req['Content-Type'] = 'application/json'
366
+ req.body = payload.to_json
367
+
368
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
369
+ http.request(req)
370
+ end
371
+
372
+ if res.is_a?(Net::HTTPSuccess)
373
+ @logger.debug "Message sent successfully!"
374
+ else
375
+ @logger.debug "AN ERROR HAS OCCURED: Failed to send message: #{res.message} (Code: #{res.code})"
376
+ @logger.debug "Response Body: #{res.body}"
377
+ end
378
+ end
379
+ rescue => e
380
+ @logger.debug "AN ERROR HAS OCCURED: Error enqueuing message: #{e.message}"
381
+ @logger.debug e.backtrace.join("\n")
382
+ end
383
+
384
+
385
+ def add_reaction(channel_id, message_id, emoji_id)
386
+ @request_queue.enqueue do
387
+ @logger.debug "Attempting to add reaction '#{emoji_id}' to message '#{message_id}' in channel '#{channel_id}' via queue."
388
+ uri = URI("#{@api_url}/channels/#{channel_id}/messages/#{message_id}/reactions/#{emoji_id}")
389
+ req = Net::HTTP::Put.new(uri) # Reactions use PUT
390
+ req['x-bot-token'] = @token
391
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
392
+ http.request(req)
393
+ end
394
+ if res.is_a?(Net::HTTPSuccess) || res.code == '204'
395
+ @logger.debug "Reaction has been added successfully!"
396
+ else
397
+ @logger.debug "AN ERROR HAS OCCURED: Failed to add reaction: #{res.message} (Code: #{res.code})"
398
+ @logger.debug "Response Body: #{res.body}"
399
+ end
400
+ end
401
+ rescue => e
402
+ @logger.debug "AN ERROR HAS OCCURED: Error enqueuing add reaction: #{e.message}"
403
+ @logger.debug e.backtrace.join("\n")
404
+ end
405
+
406
+ def remove_reaction(channel_id, message_id, emoji_id, user_id: nil)
407
+ target_user_id = user_id || @user_id # Default to bot's own ID
408
+ @request_queue.enqueue do
409
+ @logger.debug "Attempting to remove reaction '#{emoji_id}' from message '#{message_id}' by user '#{target_user_id}' in channel '#{channel_id}' via queue."
410
+ uri = URI("#{@api_url}/channels/#{channel_id}/messages/#{message_id}/reactions/#{emoji_id}?user_id=#{target_user_id}")
411
+ req = Net::HTTP::Delete.new(uri)
412
+ req['x-bot-token'] = @token
413
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
414
+ http.request(req)
415
+ end
416
+ if res.is_a?(Net::HTTPSuccess) || res.code == '204'
417
+ @logger.debug "Reaction has been removed successfully!"
418
+ else
419
+ @logger.debug "AN ERROR HAS OCCURED: Failed to remove reaction: #{res.message} (Code: #{res.code})"
420
+ @logger.debug "Response Body: #{res.body}"
421
+ end
422
+ end
423
+ rescue => e
424
+ @logger.debug "AN ERROR HAS OCCURED: Error enqueuing remove reaction: #{e.message}"
425
+ @logger.debug e.backtrace.join("\n")
426
+ end
427
+
428
+ def get_channel_details(channel_id)
429
+ @logger.debug "Fetching channel details for ID: #{channel_id} (direct API call for permission check)."
430
+ uri = URI("#{@api_url}/channels/#{channel_id}")
431
+ req = Net::HTTP::Get.new(uri)
432
+ req['x-bot-token'] = @token
433
+
434
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
435
+ http.request(req)
436
+ end
437
+
438
+ if res.is_a?(Net::HTTPSuccess)
439
+ JSON.parse(res.body)
440
+ else
441
+ @logger.debug "AN ERROR HAS OCCURED: Failed to fetch channel details for #{channel_id}: #{res.message} (Code: #{res.code})"
442
+ nil
443
+ end
444
+ rescue => e
445
+ @logger.debug "AN ERROR HAS OCCURED: Error fetching channel details: #{e.message}"
446
+ nil
447
+ end
448
+
449
+ def get_server_info(server_id)
450
+ @logger.debug "get_server_info called with server_id: '#{server_id}' (Type: #{server_id.class}, Length: #{server_id.length})"
451
+ @logger.debug "Available server IDs in cache (@servers.keys): #{@servers.keys.inspect}"
452
+ found_server = @servers[server_id]
453
+ @logger.debug "Result of @servers[server_id]: #{found_server.inspect}"
454
+ found_server
455
+ end
456
+ def get_server_name(server_id)
457
+ @servers[server_id]&.[]('name')
458
+ end
459
+
460
+ def stop
461
+ @logger.debug "Stopping bot..."
462
+ @running = false
463
+ if @websocket_thread && @websocket_thread.alive?
464
+ unless @websocket_thread.join(5)
465
+ @logger.debug "WebSocket thread did not terminate gracefully, forcing kill."
466
+ @websocket_thread.kill
467
+ end
468
+ @logger.debug "WebSocket thread terminated."
469
+ end
470
+ if @websocket && @websocket.open?
471
+ @websocket.close
472
+ @logger.debug "WebSocket closed."
473
+ end
474
+ @request_queue.stop_processing
475
+ @logger.debug "Bot stopped."
476
+ end
477
+
478
+ private
479
+
480
+ def _add_auth_header(request)
481
+ if @selfbot
482
+ request['x-session-token'] = @token
483
+ else
484
+ request['x-bot-token'] = @token
485
+ end
486
+ end
487
+ end
488
+ end
@@ -0,0 +1,13 @@
1
+ # lib/revoltrb/debuglogger.rb
2
+
3
+ module Revoltrb
4
+ class DebugLogger
5
+ def initialize(enabled = false)
6
+ @enabled = enabled
7
+ end
8
+
9
+ def debug(message)
10
+ puts message if @enabled
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,59 @@
1
+ # lib/revoltrb/request_queue.rb
2
+
3
+ require 'thread'
4
+
5
+ module Revoltrb
6
+ class RequestQueue
7
+ def initialize(delay_between_requests_ms = 100)
8
+ @queue = []
9
+ @mutex = Mutex.new
10
+ @condition = ConditionVariable.new
11
+ @processing_thread = nil
12
+ @running = false
13
+ @delay_between_requests = delay_between_requests_ms / 1000.0 # Convert ms to seconds
14
+ @logger = Revoltrb::DebugLogger.new
15
+ end
16
+
17
+ def enqueue(&block)
18
+ @mutex.synchronize do
19
+ @queue << block
20
+ @condition.signal
21
+ end
22
+ end
23
+
24
+ def start_processing
25
+ return if @running
26
+
27
+ @running = true
28
+ @processing_thread = Thread.new do
29
+ @logger.debug "RequestQueue processing thread started."
30
+ while @running
31
+ request = nil
32
+ @mutex.synchronize do
33
+ @condition.wait(@mutex) if @queue.empty?
34
+ request = @queue.shift unless @queue.empty?
35
+ end
36
+
37
+ if request
38
+ begin
39
+ request.call
40
+ sleep @delay_between_requests
41
+ rescue => e
42
+ @logger.debug "AN ERROR HAS OCCURED: Error processing queued request: #{e.message}"
43
+ @logger.debug e.backtrace.join("\n")
44
+ end
45
+ end
46
+ end
47
+ @logger.debug "RequestQueue processing thread stopped."
48
+ end
49
+ end
50
+
51
+ def stop_processing
52
+ @running = false
53
+ @mutex.synchronize do
54
+ @condition.signal
55
+ end
56
+ @processing_thread.join if @processing_thread&.alive?
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,6 @@
1
+ # lib/revoltrb/version.rb
2
+
3
+ module Revoltrb
4
+ # The current version of revoltrb
5
+ VERSION = "0.0.1"
6
+ end
data/lib/revoltrb.rb ADDED
@@ -0,0 +1,8 @@
1
+ require_relative 'revoltrb/bot'
2
+ require_relative 'revoltrb/version'
3
+ require_relative 'revoltrb/debuglogger'
4
+ require_relative 'revoltrb/request_queue'
5
+
6
+ module Revoltrb
7
+ # This module can serve as a namespace for the gem's classes. For now, it's a direct require.
8
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: revoltrb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Roxanne Studios
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: json
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: net-http
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: thread
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: parser
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ description: The first Ruby package (a.k.a. gem) to exist for making Revolt.chat bots.
69
+ This project is not officially endorsed by revolt.chat
70
+ email:
71
+ - ''
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - README.md
77
+ - lib/revoltrb.rb
78
+ - lib/revoltrb/bot.rb
79
+ - lib/revoltrb/debuglogger.rb
80
+ - lib/revoltrb/request_queue.rb
81
+ - lib/revoltrb/version.rb
82
+ homepage: https://gitlab.com/roxannewolf/revoltrb
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '3.0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubygems_version: 3.6.9
101
+ specification_version: 4
102
+ summary: Ruby gem for Revolt.chat bots
103
+ test_files: []