discord_rda 0.1.3 → 0.2.0
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 +4 -4
- data/README.md +63 -0
- data/lib/discord_rda/bot.rb +1223 -61
- data/lib/discord_rda/cache/store.rb +2 -0
- data/lib/discord_rda/connection/gateway_client.rb +12 -2
- data/lib/discord_rda/connection/rest_client.rb +10 -1
- data/lib/discord_rda/connection/rest_proxy.rb +5 -1
- data/lib/discord_rda/connection/shard_manager.rb +10 -1
- data/lib/discord_rda/core/configuration.rb +65 -2
- data/lib/discord_rda/core/error_tracker.rb +22 -0
- data/lib/discord_rda/core/execution_supervisor.rb +180 -0
- data/lib/discord_rda/core/logger.rb +17 -3
- data/lib/discord_rda/core/restart_manager.rb +83 -0
- data/lib/discord_rda/core/secrets.rb +30 -0
- data/lib/discord_rda/core/snowflake.rb +1 -1
- data/lib/discord_rda/core/tracer.rb +41 -0
- data/lib/discord_rda/entity/base.rb +10 -3
- data/lib/discord_rda/entity/message_builder.rb +2 -0
- data/lib/discord_rda/entity/support.rb +287 -0
- data/lib/discord_rda/event/base.rb +258 -7
- data/lib/discord_rda/interactions/application_command.rb +44 -6
- data/lib/discord_rda/interactions/interaction.rb +12 -0
- data/lib/discord_rda/persistence/active_record.rb +72 -0
- data/lib/discord_rda/plugin/analytics_plugin.rb +64 -2
- data/lib/discord_rda/version.rb +1 -1
- data/lib/discord_rda.rb +6 -0
- metadata +64 -2
|
@@ -54,6 +54,7 @@ module DiscordRDA
|
|
|
54
54
|
# Class-level API client
|
|
55
55
|
class << self
|
|
56
56
|
attr_accessor :api
|
|
57
|
+
attr_accessor :supervisor
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
# Get interaction type as symbol
|
|
@@ -439,6 +440,17 @@ module DiscordRDA
|
|
|
439
440
|
self.class.api.delete("/webhooks/#{application_id}/#{token}/messages/#{message_id}")
|
|
440
441
|
end
|
|
441
442
|
|
|
443
|
+
def run_isolated(ruby_code:, timeout_seconds: 15, memory_limit_mb: nil, env: {})
|
|
444
|
+
raise 'Execution supervisor not configured' unless self.class.supervisor
|
|
445
|
+
|
|
446
|
+
self.class.supervisor.run_isolated(
|
|
447
|
+
ruby_code: ruby_code,
|
|
448
|
+
timeout_seconds: timeout_seconds,
|
|
449
|
+
memory_limit_mb: memory_limit_mb,
|
|
450
|
+
env: env
|
|
451
|
+
)
|
|
452
|
+
end
|
|
453
|
+
|
|
442
454
|
# Show premium required response
|
|
443
455
|
# @return [void]
|
|
444
456
|
def premium_required
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'active_record'
|
|
5
|
+
|
|
6
|
+
module DiscordRDA
|
|
7
|
+
class ActiveRecordSystem
|
|
8
|
+
attr_reader :logger
|
|
9
|
+
|
|
10
|
+
def initialize(logger: nil)
|
|
11
|
+
@logger = logger
|
|
12
|
+
@connected = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def connect(database_url: nil, **config)
|
|
16
|
+
require 'active_record'
|
|
17
|
+
|
|
18
|
+
connection_config = config.dup
|
|
19
|
+
connection_config[:url] = database_url if database_url
|
|
20
|
+
connection_config[:url] ||= ENV['DATABASE_URL']
|
|
21
|
+
raise ArgumentError, 'database_url or DATABASE_URL is required' unless connection_config[:url]
|
|
22
|
+
|
|
23
|
+
::ActiveRecord::Base.establish_connection(connection_config)
|
|
24
|
+
::DiscordRDA::Record.connection
|
|
25
|
+
@connected = true
|
|
26
|
+
logger&.info('ActiveRecord connected', adapter: ::ActiveRecord::Base.connection_db_config.adapter)
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def connected?
|
|
31
|
+
@connected && ::ActiveRecord::Base.connected?
|
|
32
|
+
rescue StandardError
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def disconnect
|
|
37
|
+
return unless defined?(::ActiveRecord::Base)
|
|
38
|
+
|
|
39
|
+
::ActiveRecord::Base.connection_pool.disconnect!
|
|
40
|
+
@connected = false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def migration_context(paths = default_migration_paths)
|
|
44
|
+
require 'active_record'
|
|
45
|
+
|
|
46
|
+
paths.each { |path| FileUtils.mkdir_p(path) }
|
|
47
|
+
::ActiveRecord::MigrationContext.new(paths, ::ActiveRecord::SchemaMigration)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def migrate(paths = default_migration_paths)
|
|
51
|
+
migration_context(paths).migrate
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def rollback(paths = default_migration_paths, steps: 1)
|
|
55
|
+
migration_context(paths).rollback(steps)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def default_migration_paths
|
|
59
|
+
['db/migrate']
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class Record < ::ActiveRecord::Base
|
|
64
|
+
self.abstract_class = true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
module ActiveRecordMigration
|
|
68
|
+
def self.[](version = 7.1)
|
|
69
|
+
Class.new(::ActiveRecord::Migration[version])
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -182,6 +182,45 @@ module DiscordRDA
|
|
|
182
182
|
}
|
|
183
183
|
end
|
|
184
184
|
|
|
185
|
+
def prometheus_export
|
|
186
|
+
data = summary
|
|
187
|
+
|
|
188
|
+
[
|
|
189
|
+
'# HELP discord_rda_uptime_seconds Process uptime in seconds',
|
|
190
|
+
'# TYPE discord_rda_uptime_seconds gauge',
|
|
191
|
+
"discord_rda_uptime_seconds #{data[:uptime]}",
|
|
192
|
+
'# HELP discord_rda_gateway_events_per_minute Gateway events per minute',
|
|
193
|
+
'# TYPE discord_rda_gateway_events_per_minute gauge',
|
|
194
|
+
"discord_rda_gateway_events_per_minute #{data[:gateway][:events_per_minute]}",
|
|
195
|
+
'# HELP discord_rda_rest_requests_per_minute REST requests per minute',
|
|
196
|
+
'# TYPE discord_rda_rest_requests_per_minute gauge',
|
|
197
|
+
"discord_rda_rest_requests_per_minute #{data[:rest][:requests_per_minute]}",
|
|
198
|
+
'# HELP discord_rda_cache_hit_rate Cache hit rate percentage',
|
|
199
|
+
'# TYPE discord_rda_cache_hit_rate gauge',
|
|
200
|
+
"discord_rda_cache_hit_rate #{data[:cache][:hit_rate]}",
|
|
201
|
+
'# HELP discord_rda_shards_total_guilds Guilds across shards',
|
|
202
|
+
'# TYPE discord_rda_shards_total_guilds gauge',
|
|
203
|
+
"discord_rda_shards_total_guilds #{data[:shards][:total_guilds]}"
|
|
204
|
+
].join("\n") + "\n"
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def grafana_dashboard(title: 'DiscordRDA Overview')
|
|
208
|
+
dashboard = {
|
|
209
|
+
title: title,
|
|
210
|
+
schemaVersion: 39,
|
|
211
|
+
version: 1,
|
|
212
|
+
editable: true,
|
|
213
|
+
panels: [
|
|
214
|
+
metric_panel(id: 1, title: 'Gateway Events / Min', expr: 'discord_rda_gateway_events_per_minute'),
|
|
215
|
+
metric_panel(id: 2, title: 'REST Requests / Min', expr: 'discord_rda_rest_requests_per_minute'),
|
|
216
|
+
metric_panel(id: 3, title: 'Cache Hit Rate', expr: 'discord_rda_cache_hit_rate'),
|
|
217
|
+
metric_panel(id: 4, title: 'Guilds', expr: 'discord_rda_shards_total_guilds')
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
Oj.dump(dashboard, mode: :compat)
|
|
222
|
+
end
|
|
223
|
+
|
|
185
224
|
# Run comprehensive health checks
|
|
186
225
|
# @return [Hash] Health check results
|
|
187
226
|
def run_health_checks
|
|
@@ -311,6 +350,17 @@ module DiscordRDA
|
|
|
311
350
|
}
|
|
312
351
|
end
|
|
313
352
|
|
|
353
|
+
def metric_panel(id:, title:, expr:)
|
|
354
|
+
{
|
|
355
|
+
id: id,
|
|
356
|
+
type: 'stat',
|
|
357
|
+
title: title,
|
|
358
|
+
datasource: { type: 'prometheus', uid: '${DS_PROMETHEUS}' },
|
|
359
|
+
targets: [{ expr: expr, refId: "A#{id}" }],
|
|
360
|
+
gridPos: { h: 8, w: 12, x: ((id - 1) % 2) * 12, y: ((id - 1) / 2) * 8 }
|
|
361
|
+
}
|
|
362
|
+
end
|
|
363
|
+
|
|
314
364
|
# Get memory usage
|
|
315
365
|
# @return [Hash] Memory usage info
|
|
316
366
|
def memory_usage
|
|
@@ -323,10 +373,22 @@ module DiscordRDA
|
|
|
323
373
|
{ error: 'Unable to retrieve' }
|
|
324
374
|
end
|
|
325
375
|
|
|
326
|
-
# Get CPU usage
|
|
376
|
+
# Get process CPU usage
|
|
327
377
|
# @return [Hash] CPU usage info
|
|
328
378
|
def cpu_usage
|
|
329
|
-
|
|
379
|
+
process_times = Process.times
|
|
380
|
+
elapsed = uptime_seconds
|
|
381
|
+
total_cpu_seconds = process_times.utime + process_times.stime
|
|
382
|
+
cpu_percent = elapsed.positive? ? ((total_cpu_seconds / elapsed) * 100.0).round(2) : 0.0
|
|
383
|
+
|
|
384
|
+
{
|
|
385
|
+
available: true,
|
|
386
|
+
user_seconds: process_times.utime.round(4),
|
|
387
|
+
system_seconds: process_times.stime.round(4),
|
|
388
|
+
total_seconds: total_cpu_seconds.round(4),
|
|
389
|
+
utilization_percent: cpu_percent
|
|
390
|
+
}
|
|
391
|
+
rescue StandardError
|
|
330
392
|
{ available: false }
|
|
331
393
|
end
|
|
332
394
|
|
data/lib/discord_rda/version.rb
CHANGED
data/lib/discord_rda.rb
CHANGED
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
require_relative 'discord_rda/version'
|
|
8
8
|
require_relative 'discord_rda/core/async_runtime'
|
|
9
9
|
require_relative 'discord_rda/core/configuration'
|
|
10
|
+
require_relative 'discord_rda/core/execution_supervisor'
|
|
10
11
|
require_relative 'discord_rda/core/logger'
|
|
12
|
+
require_relative 'discord_rda/core/restart_manager'
|
|
13
|
+
require_relative 'discord_rda/core/tracer'
|
|
14
|
+
require_relative 'discord_rda/core/error_tracker'
|
|
15
|
+
require_relative 'discord_rda/core/secrets'
|
|
11
16
|
require_relative 'discord_rda/core/snowflake'
|
|
12
17
|
require_relative 'discord_rda/entity/base'
|
|
13
18
|
require_relative 'discord_rda/entity/factory'
|
|
@@ -25,6 +30,7 @@ require_relative 'discord_rda/entity/emoji'
|
|
|
25
30
|
require_relative 'discord_rda/entity/attachment'
|
|
26
31
|
require_relative 'discord_rda/entity/embed'
|
|
27
32
|
require_relative 'discord_rda/entity/support'
|
|
33
|
+
require_relative 'discord_rda/persistence/active_record'
|
|
28
34
|
require_relative 'discord_rda/connection/rate_limiter'
|
|
29
35
|
require_relative 'discord_rda/connection/invalid_bucket'
|
|
30
36
|
require_relative 'discord_rda/connection/request_queue'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: discord_rda
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Júlia Klee
|
|
@@ -51,6 +51,48 @@ dependencies:
|
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0.30'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: activerecord
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '7.1'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '7.1'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: base64
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: lru_redux
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '1.1'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '1.1'
|
|
54
96
|
- !ruby/object:Gem::Dependency
|
|
55
97
|
name: oj
|
|
56
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -149,6 +191,20 @@ dependencies:
|
|
|
149
191
|
- - "~>"
|
|
150
192
|
- !ruby/object:Gem::Version
|
|
151
193
|
version: '1.17'
|
|
194
|
+
- !ruby/object:Gem::Dependency
|
|
195
|
+
name: erb
|
|
196
|
+
requirement: !ruby/object:Gem::Requirement
|
|
197
|
+
requirements:
|
|
198
|
+
- - ">="
|
|
199
|
+
- !ruby/object:Gem::Version
|
|
200
|
+
version: '0'
|
|
201
|
+
type: :development
|
|
202
|
+
prerelease: false
|
|
203
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
204
|
+
requirements:
|
|
205
|
+
- - ">="
|
|
206
|
+
- !ruby/object:Gem::Version
|
|
207
|
+
version: '0'
|
|
152
208
|
- !ruby/object:Gem::Dependency
|
|
153
209
|
name: rake
|
|
154
210
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -247,8 +303,13 @@ files:
|
|
|
247
303
|
- lib/discord_rda/connection/shard_manager.rb
|
|
248
304
|
- lib/discord_rda/core/async_runtime.rb
|
|
249
305
|
- lib/discord_rda/core/configuration.rb
|
|
306
|
+
- lib/discord_rda/core/error_tracker.rb
|
|
307
|
+
- lib/discord_rda/core/execution_supervisor.rb
|
|
250
308
|
- lib/discord_rda/core/logger.rb
|
|
309
|
+
- lib/discord_rda/core/restart_manager.rb
|
|
310
|
+
- lib/discord_rda/core/secrets.rb
|
|
251
311
|
- lib/discord_rda/core/snowflake.rb
|
|
312
|
+
- lib/discord_rda/core/tracer.rb
|
|
252
313
|
- lib/discord_rda/entity/attachment.rb
|
|
253
314
|
- lib/discord_rda/entity/base.rb
|
|
254
315
|
- lib/discord_rda/entity/channel.rb
|
|
@@ -274,6 +335,7 @@ files:
|
|
|
274
335
|
- lib/discord_rda/interactions/command_system.rb
|
|
275
336
|
- lib/discord_rda/interactions/components.rb
|
|
276
337
|
- lib/discord_rda/interactions/interaction.rb
|
|
338
|
+
- lib/discord_rda/persistence/active_record.rb
|
|
277
339
|
- lib/discord_rda/plugin/analytics_plugin.rb
|
|
278
340
|
- lib/discord_rda/plugin/base.rb
|
|
279
341
|
- lib/discord_rda/plugin/registry.rb
|
|
@@ -289,7 +351,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
289
351
|
requirements:
|
|
290
352
|
- - ">="
|
|
291
353
|
- !ruby/object:Gem::Version
|
|
292
|
-
version: 3.
|
|
354
|
+
version: 3.4.0
|
|
293
355
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
294
356
|
requirements:
|
|
295
357
|
- - ">="
|