Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caching improvement #285

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 92 additions & 24 deletions lib/discordrb/api/application.rb
Original file line number Diff line number Diff line change
@@ -1,36 +1,92 @@
# frozen_string_literal: true

# API calls for slash commands.
module Discordrb::API::Application
module_function

# Get a list of global application commands.
# https://discord.com/developers/docs/interactions/slash-commands#get-global-application-commands
# Cache and rate limit settings
CACHE_TTL = 300 # seconds
RATE_LIMIT = 50 # requests per second

# In-memory cache implementation
@cache = {}
@last_requests = {}
@request_counts = {}

def self.cache_key(method, *args)
"#{method}:#{args.join(':')}"
end

def self.cached_request(method, *args)
key = cache_key(method, *args)
current_time = Time.now.to_f

# Check cache
if @cache[key] && current_time - @cache[key][:timestamp] < CACHE_TTL
return @cache[key][:data]
end

# Rate limit handling
handle_rate_limit(method)

# Execute request and cache result
result = yield
@cache[key] = {
data: result,
timestamp: current_time
}

result
end

def self.handle_rate_limit(method)
current_time = Time.now.to_f

# Reset counter if window has passed
if @last_requests[method].nil? || current_time - @last_requests[method] >= 1
@request_counts[method] = 0
@last_requests[method] = current_time
end

@request_counts[method] ||= 0
@request_counts[method] += 1

# Sleep if rate limit is exceeded
if @request_counts[method] > RATE_LIMIT
sleep_time = (1 - (current_time - @last_requests[method]))
sleep(sleep_time) if sleep_time.positive?
@request_counts[method] = 0
@last_requests[method] = Time.now.to_f
end
end

# Modified API methods using caching
def get_global_commands(token, application_id)
Discordrb::API.request(
:applications_aid_commands,
nil,
:get,
"#{Discordrb::API.api_base}/applications/#{application_id}/commands",
Authorization: token
)
cached_request(__method__, application_id) do
Discordrb::API.request(
:applications_aid_commands,
nil,
:get,
"#{Discordrb::API.api_base}/applications/#{application_id}/commands",
Authorization: token
)
end
end

# Get a global application command by ID.
# https://discord.com/developers/docs/interactions/slash-commands#get-global-application-command
def get_global_command(token, application_id, command_id)
Discordrb::API.request(
:applications_aid_commands_cid,
nil,
:get,
"#{Discordrb::API.api_base}/applications/#{application_id}/commands/#{command_id}",
Authorization: token
)
cached_request(__method__, application_id, command_id) do
Discordrb::API.request(
:applications_aid_commands_cid,
nil,
:get,
"#{Discordrb::API.api_base}/applications/#{application_id}/commands/#{command_id}",
Authorization: token
)
end
end

# Create a global application command.
# https://discord.com/developers/docs/interactions/slash-commands#create-global-application-command
# Write methods (no caching, only rate limiting)
def create_global_command(token, application_id, name, description, options = [], default_permission = nil, type = 1, default_member_permissions = nil, contexts = nil)
handle_rate_limit(__method__)
Discordrb::API.request(
:applications_aid_commands,
nil,
Expand All @@ -42,10 +98,20 @@ def create_global_command(token, application_id, name, description, options = []
)
end

# Edit a global application command.
# https://discord.com/developers/docs/interactions/slash-commands#edit-global-application-command
# Cache invalidation helper
def self.invalidate_cache!
@cache.clear
end

# Add cache invalidation for write operations
def self.invalidate_command_cache(application_id)
@cache.delete_if { |k, _| k.include?(application_id.to_s) }
end

# Modified write methods with cache invalidation
def edit_global_command(token, application_id, command_id, name = nil, description = nil, options = nil, default_permission = nil, type = 1, default_member_permissions = nil, contexts = nil)
Discordrb::API.request(
handle_rate_limit(__method__)
result = Discordrb::API.request(
:applications_aid_commands_cid,
nil,
:patch,
Expand All @@ -54,6 +120,8 @@ def edit_global_command(token, application_id, command_id, name = nil, descripti
Authorization: token,
content_type: :json
)
self.class.invalidate_command_cache(application_id)
result
end

# Delete a global application command.
Expand Down
84 changes: 69 additions & 15 deletions lib/discordrb/api/interaction.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,65 @@
# frozen_string_literal: true

# API calls for interactions.
module Discordrb::API::Interaction
module_function

# Respond to an interaction.
# https://discord.com/developers/docs/interactions/slash-commands#create-interaction-response
CACHE_TTL = 300 # Cache TTL in seconds
RATE_LIMIT = 50 # Requests per second

@cache = {}
@last_requests = {}
@request_counts = {}

def self.cache_key(method, *args)
"#{method}:#{args.join(':')}"
end

def self.cached_request(method, *args)
key = cache_key(method, *args)
current_time = Time.now.to_f

if @cache[key] && current_time - @cache[key][:timestamp] < CACHE_TTL
return @cache[key][:data]
end

handle_rate_limit(method)

result = yield
@cache[key] = {
data: result,
timestamp: current_time
}

result
end

def self.handle_rate_limit(method)
current_time = Time.now.to_f

if @last_requests[method].nil? || current_time - @last_requests[method] >= 1
@request_counts[method] = 0
@last_requests[method] = current_time
end

@request_counts[method] ||= 0
@request_counts[method] += 1

if @request_counts[method] > RATE_LIMIT
sleep_time = (1 - (current_time - @last_requests[method]))
sleep(sleep_time) if sleep_time.positive?
@request_counts[method] = 0
@last_requests[method] = Time.now.to_f
end
end

def self.invalidate_cache!
@cache.clear
end

# API methods

def create_interaction_response(interaction_token, interaction_id, type, content = nil, tts = nil, embeds = nil, allowed_mentions = nil, flags = nil, components = nil)
handle_rate_limit(__method__)
data = { tts: tts, content: content, embeds: embeds, allowed_mentions: allowed_mentions, flags: flags, components: components }.compact

Discordrb::API.request(
Expand All @@ -19,9 +72,8 @@ def create_interaction_response(interaction_token, interaction_id, type, content
)
end

# Create a response that results in a modal.
# https://discord.com/developers/docs/interactions/slash-commands#create-interaction-response
def create_interaction_modal_response(interaction_token, interaction_id, custom_id, title, components)
handle_rate_limit(__method__)
data = { custom_id: custom_id, title: title, components: components.to_a }.compact

Discordrb::API.request(
Expand All @@ -34,21 +86,23 @@ def create_interaction_modal_response(interaction_token, interaction_id, custom_
)
end

# Get the original response to an interaction.
# https://discord.com/developers/docs/interactions/slash-commands#get-original-interaction-response
def get_original_interaction_response(interaction_token, application_id)
Discordrb::API::Webhook.token_get_message(interaction_token, application_id, '@original')
cached_request(__method__, interaction_token, application_id) do
Discordrb::API::Webhook.token_get_message(interaction_token, application_id, '@original')
end
end

# Edit the original response to an interaction.
# https://discord.com/developers/docs/interactions/slash-commands#edit-original-interaction-response
def edit_original_interaction_response(interaction_token, application_id, content = nil, embeds = nil, allowed_mentions = nil, components = nil)
Discordrb::API::Webhook.token_edit_message(interaction_token, application_id, '@original', content, embeds, allowed_mentions, components)
handle_rate_limit(__method__)
result = Discordrb::API::Webhook.token_edit_message(interaction_token, application_id, '@original', content, embeds, allowed_mentions, components)
invalidate_cache!
result
end

# Delete the original response to an interaction.
# https://discord.com/developers/docs/interactions/slash-commands#delete-original-interaction-response
def delete_original_interaction_response(interaction_token, application_id)
Discordrb::API::Webhook.token_delete_message(interaction_token, application_id, '@original')
handle_rate_limit(__method__)
result = Discordrb::API::Webhook.token_delete_message(interaction_token, application_id, '@original')
invalidate_cache!
result
end
end
end
2 changes: 1 addition & 1 deletion lib/discordrb/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
# Discordrb and all its functionality, in this case only the version.
module Discordrb
# The current version of discordrb.
VERSION = '3.5.0'
VERSION = '3.6.0'
end
2 changes: 1 addition & 1 deletion lib/discordrb/webhooks/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
module Discordrb
module Webhooks
# The current version of discordrb-webhooks.
VERSION = '3.5.0'
VERSION = '3.6.0'
end
end