Class: DiscordStore::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/discord_store/configuration.rb

Overview

Process-wide settings. Most applications configure this once at boot:

DiscordStore.configure do |c|
c.i_understand_this_violates_discord_tos = true
c.token       = ENV.fetch("DISCORD_BOT_TOKEN")
c.application_id = ENV.fetch("DISCORD_APPLICATION_ID")
c.guild_id    = ENV.fetch("DISCORD_GUILD_ID")
c.secret_key  = ENV.fetch("DISCORD_STORE_KEY") # 32 raw bytes, base64
end

Constant Summary collapse

DISCORD_GLOBAL_LIMIT =

Discord's documented global ceiling for a bot token, in requests/second. We aim below it by default; see #global_rate_limit.

50
MESSAGE_CONTENT_LIMIT =

Maximum characters in a normal message's content field.

2000
DEFAULTS =

Every setting that has a sensible default, and its default.

{
  global_rate_limit: 45,
  quota_timeout: 15.0,
  max_retries: 5,
  # Discord answers 202 with retry_after 0 when it has no better estimate,
  # and says to "retry after a short delay". This is what short means.
  # Tests drop it so the suite exercises the retry rather than sleeping
  # through it, which is the same reason the fake's rate limit is settable.
  search_retry_floor: 0.5,
  open_timeout: 5.0,
  read_timeout: 30.0,
  write_timeout: 30.0,
  user_agent: nil,
  api_base: "https://discord.com/api/v10",
  cipher: :aes_256_gcm,
  content_budget: 1900,
  max_concurrent_transfers: 24,
  chunk_size: nil,
  delete_policy: :tombstone,
  own_messages_only: true,
  logger: nil
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize ⇒ Configuration

Returns a new instance of Configuration.



123
124
125
126
127
128
# File 'lib/discord_store/configuration.rb', line 123

def initialize
  DEFAULTS.each { |name, value| public_send(:"#{name}=", value) }
  @i_understand_this_violates_discord_tos = false
  @log_channel_ids = []
  @blob_channel_ids = []
end

Instance Attribute Details

#api_base ⇒ Object

--- Transport -----------------------------------------------------------



91
92
93
# File 'lib/discord_store/configuration.rb', line 91

def api_base
  @api_base
end

#application_id ⇒ Object

The bot's application (user) ID. Used to enforce that we only ever read messages our own bot wrote. Strongly recommended; see #own_messages_only.



59
60
61
# File 'lib/discord_store/configuration.rb', line 59

def application_id
  @application_id
end

#blob_channel_ids ⇒ Object

Channels used for blob chunks (ActiveStorage). Sharded like the log.



72
73
74
# File 'lib/discord_store/configuration.rb', line 72

def blob_channel_ids
  @blob_channel_ids
end

#chunk_size ⇒ Object

Bytes per blob chunk. Left nil to be discovered at runtime from the guild's boost tier, because Discord's attachment ceiling has moved several times and hardcoding it is how these libraries break.



113
114
115
# File 'lib/discord_store/configuration.rb', line 113

def chunk_size
  @chunk_size
end

#cipher ⇒ Object

:aes_256_gcm (default) or :none. :none writes readable JSON into the channel, which is useful when you want humans to read the data in the Discord client, and which forfeits the encryption-at-rest that Discord's own developer terms require of anyone storing end-user data.



87
88
89
# File 'lib/discord_store/configuration.rb', line 87

def cipher
  @cipher
end

#content_budget ⇒ Object

How many characters of a message's content we are willing to fill. Kept under MESSAGE_CONTENT_LIMIT so that framing overhead can never push a message over the hard limit.



105
106
107
# File 'lib/discord_store/configuration.rb', line 105

def content_budget
  @content_budget
end

#delete_policy ⇒ Object

What to do when a blob or record is deleted:

:tombstone  — edit the message to a tombstone marker, never delete.
            Cheapest, and survives the 14-day bulk-delete window.
:bulk       — bulk-delete where possible, tombstone the rest.
:aggressive — delete everything, one request at a time if we must.


121
122
123
# File 'lib/discord_store/configuration.rb', line 121

def delete_policy
  @delete_policy
end

#document_channel_id ⇒ Object

Channel used for mutable documents (the KV layer).



69
70
71
# File 'lib/discord_store/configuration.rb', line 69

def document_channel_id
  @document_channel_id
end

#global_rate_limit ⇒ Object

--- Transport -----------------------------------------------------------



91
92
93
# File 'lib/discord_store/configuration.rb', line 91

def global_rate_limit
  @global_rate_limit
end

#guild_id ⇒ Object

The guild that owns the channels we write to.



62
63
64
# File 'lib/discord_store/configuration.rb', line 62

def guild_id
  @guild_id
end

#i_understand_this_violates_discord_tos ⇒ Object

Storing application data in Discord messages violates the Discord Developer Terms of Service. Nothing in this library will make a network request until this is explicitly set to true.



50
51
52
# File 'lib/discord_store/configuration.rb', line 50

def i_understand_this_violates_discord_tos
  @i_understand_this_violates_discord_tos
end

#log_channel_ids ⇒ Object

Channels used for the append-only log. More than one channel multiplies write throughput, because Discord's harshest message limit is per-channel.



66
67
68
# File 'lib/discord_store/configuration.rb', line 66

def log_channel_ids
  @log_channel_ids
end

#logger ⇒ Object

--- Transport -----------------------------------------------------------



91
92
93
# File 'lib/discord_store/configuration.rb', line 91

def logger
  @logger
end

#manifest_channel_id ⇒ Object

Channel holding blob manifests. Kept separate from chunks so that a manifest scan does not have to page past gigabytes of chunk messages.



76
77
78
# File 'lib/discord_store/configuration.rb', line 76

def manifest_channel_id
  @manifest_channel_id
end

#max_concurrent_transfers ⇒ Object

Concurrency ceiling for blob chunk transfers.



108
109
110
# File 'lib/discord_store/configuration.rb', line 108

def max_concurrent_transfers
  @max_concurrent_transfers
end

#max_retries ⇒ Object

--- Transport -----------------------------------------------------------



91
92
93
# File 'lib/discord_store/configuration.rb', line 91

def max_retries
  @max_retries
end

#open_timeout ⇒ Object

--- Transport -----------------------------------------------------------



91
92
93
# File 'lib/discord_store/configuration.rb', line 91

def open_timeout
  @open_timeout
end

#own_messages_only ⇒ Object

Refuse to read any message not authored by application_id. This is the line between a storage backend and a scraper, and it is enforced in code rather than in documentation. Leave it on.



98
99
100
# File 'lib/discord_store/configuration.rb', line 98

def own_messages_only
  @own_messages_only
end

#quota_timeout ⇒ Object

--- Transport -----------------------------------------------------------



91
92
93
# File 'lib/discord_store/configuration.rb', line 91

def quota_timeout
  @quota_timeout
end

#read_timeout ⇒ Object

--- Transport -----------------------------------------------------------



91
92
93
# File 'lib/discord_store/configuration.rb', line 91

def read_timeout
  @read_timeout
end

#search_retry_floor ⇒ Object

--- Transport -----------------------------------------------------------



91
92
93
# File 'lib/discord_store/configuration.rb', line 91

def search_retry_floor
  @search_retry_floor
end

#secret_key ⇒ Object

32 raw bytes, base64-encoded. Required unless cipher is :none.



81
82
83
# File 'lib/discord_store/configuration.rb', line 81

def secret_key
  @secret_key
end

#token ⇒ Object

Bot token, without the "Bot " prefix.



55
56
57
# File 'lib/discord_store/configuration.rb', line 55

def token
  @token
end

#user_agent ⇒ Object

--- Transport -----------------------------------------------------------



91
92
93
# File 'lib/discord_store/configuration.rb', line 91

def user_agent
  @user_agent
end

#write_timeout ⇒ Object

--- Transport -----------------------------------------------------------



91
92
93
# File 'lib/discord_store/configuration.rb', line 91

def write_timeout
  @write_timeout
end

Instance Method Details

#all_channel_ids ⇒ Array<String>

Every channel this configuration knows about, deduplicated.

Returns:

  • (Array<String>)


165
166
167
168
169
170
171
172
# File 'lib/discord_store/configuration.rb', line 165

def all_channel_ids
  [
    *log_channel_ids,
    *blob_channel_ids,
    document_channel_id,
    manifest_channel_id
  ].compact.map(&:to_s).uniq
end

#secret_key_bytes ⇒ String?

The raw 32-byte encryption key.

Returns:

  • (String, nil)


152
153
154
155
156
157
158
159
160
# File 'lib/discord_store/configuration.rb', line 152

def secret_key_bytes
  return nil if cipher == :none
  return nil if blank?(secret_key)

  require "base64"
  Base64.strict_decode64(secret_key.to_s)
rescue ArgumentError
  raise ConfigurationError, "secret_key is not valid base64"
end

#validate! ⇒ void

This method returns an undefined value.

Raises:



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/discord_store/configuration.rb', line 133

def validate!
  raise UnacknowledgedError unless i_understand_this_violates_discord_tos
  raise ConfigurationError, "token is required" if blank?(token)

  if own_messages_only && blank?(application_id)
    raise ConfigurationError,
          "application_id is required while own_messages_only is enabled " \
          "(it is what makes the check possible). Set it, or explicitly " \
          "set own_messages_only = false and accept what that means."
  end

  validate_cipher!
  validate_budget!
  self
end