discord_store 0.1.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.
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "discord_store/version"
4
+ require_relative "discord_store/errors"
5
+ require_relative "discord_store/snowflake"
6
+ require_relative "discord_store/configuration"
7
+ require_relative "discord_store/cipher"
8
+ require_relative "discord_store/channel_shard"
9
+ require_relative "discord_store/codec"
10
+ require_relative "discord_store/log"
11
+ require_relative "discord_store/guild_limits"
12
+ require_relative "discord_store/blob_store"
13
+ require_relative "discord_store/kv"
14
+ require_relative "discord_store/transport/http"
15
+ require_relative "discord_store/transport/quota"
16
+ require_relative "discord_store/transport/bucket"
17
+ require_relative "discord_store/transport/rate_limiter"
18
+ require_relative "discord_store/transport/rest"
19
+ require_relative "discord_store/transport/fake"
20
+
21
+ # Uses Discord as a database.
22
+ #
23
+ # It works, in the sense that the data goes in and comes back out. It is also an
24
+ # explicit violation of the Discord Developer Terms of Service, it is capped at
25
+ # a few megabytes per second by rate limits nobody can raise for you, and every
26
+ # byte of it lives at the pleasure of a company that has already shipped one
27
+ # change (signed, expiring CDN links) that broke this entire category of project
28
+ # overnight.
29
+ #
30
+ # Read {Configuration#i_understand_this_violates_discord_tos} before using it.
31
+ module DiscordStore
32
+ class << self
33
+ # @return [Configuration]
34
+ def configuration
35
+ @configuration ||= Configuration.new
36
+ end
37
+
38
+ # @yieldparam config [Configuration]
39
+ # @return [Configuration]
40
+ def configure
41
+ yield(configuration) if block_given?
42
+ configuration
43
+ end
44
+
45
+ # Resets configuration and any memoized client. Mainly for tests.
46
+ #
47
+ # @return [void]
48
+ def reset!
49
+ @configuration = nil
50
+ @client = nil
51
+ end
52
+
53
+ # The process-wide client.
54
+ #
55
+ # @return [Client]
56
+ def client
57
+ @client ||= Client.new(config: configuration)
58
+ end
59
+
60
+ # Replaces the process-wide client, e.g. with one wired to the fake.
61
+ #
62
+ # @param client [Client]
63
+ # @return [Client]
64
+ attr_writer :client
65
+
66
+ # @return [String] a fresh base64 encryption key
67
+ def generate_key = Cipher.generate_key
68
+ end
69
+
70
+ # Ties the layers together: one transport, one rate limiter, and the stores
71
+ # built on top of them.
72
+ class Client
73
+ attr_reader :config, :rest
74
+
75
+ # @param config [Configuration]
76
+ # @param http [#call, nil] injectable HTTP backend; pass a
77
+ # {Transport::Fake} to run the whole stack without a token
78
+ def initialize(config: DiscordStore.configuration, http: nil)
79
+ @config = config.validate!
80
+ @rest = Transport::REST.new(config: @config, http: http)
81
+ end
82
+
83
+ # @return [Log]
84
+ def log
85
+ @log ||= Log.new(rest: @rest, config: @config)
86
+ end
87
+
88
+ # @return [BlobStore]
89
+ def blobs
90
+ @blobs ||= BlobStore.new(rest: @rest, config: @config)
91
+ end
92
+
93
+ # @return [KV]
94
+ def kv
95
+ @kv ||= KV.new(rest: @rest, config: @config)
96
+ end
97
+
98
+ # @return [GuildLimits]
99
+ def limits
100
+ @limits ||= GuildLimits.new(rest: @rest, config: @config)
101
+ end
102
+
103
+ # Confirms the token works and that the configured application_id matches
104
+ # the token's actual identity. Worth calling at boot: a mismatched
105
+ # application_id silently makes every read return nothing, because the
106
+ # own-messages guard rejects our own writes.
107
+ #
108
+ # @return [Hash] the bot user
109
+ # @raise [ConfigurationError] on an application_id mismatch
110
+ def verify!
111
+ user = @rest.current_user
112
+
113
+ if @config.own_messages_only && user["id"].to_s != @config.application_id.to_s
114
+ raise ConfigurationError,
115
+ "configured application_id #{@config.application_id.inspect} is not this token's " \
116
+ "identity (#{user["id"].inspect}). Every read would silently return nothing."
117
+ end
118
+
119
+ user
120
+ end
121
+
122
+ # @return [void]
123
+ def close = @rest.close
124
+ end
125
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: discord_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chayut
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-09-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.2'
27
+ description: |
28
+ A rate-limit-aware transport, an encrypted append-only log, an ActiveStorage
29
+ service and an ActiveRecord adapter, all backed by Discord messages and
30
+ attachments. Snowflake IDs as log sequence numbers, channels as partitions,
31
+ tombstones instead of deletes, and CDN links re-resolved on every read
32
+ instead of cached. An extended argument about what an ActiveRecord adapter
33
+ is allowed to be, which happens to work.
34
+ email:
35
+ - chayut_o@hotmail.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - CHANGELOG.md
41
+ - LICENSE.txt
42
+ - README.md
43
+ - lib/active_record/connection_adapters/discord_adapter.rb
44
+ - lib/active_storage/service/discord_service.rb
45
+ - lib/discord_store.rb
46
+ - lib/discord_store/blob_store.rb
47
+ - lib/discord_store/channel_shard.rb
48
+ - lib/discord_store/cipher.rb
49
+ - lib/discord_store/codec.rb
50
+ - lib/discord_store/configuration.rb
51
+ - lib/discord_store/errors.rb
52
+ - lib/discord_store/guild_limits.rb
53
+ - lib/discord_store/journal.rb
54
+ - lib/discord_store/kv.rb
55
+ - lib/discord_store/log.rb
56
+ - lib/discord_store/log/record.rb
57
+ - lib/discord_store/railtie.rb
58
+ - lib/discord_store/replay.rb
59
+ - lib/discord_store/snowflake.rb
60
+ - lib/discord_store/tasks.rake
61
+ - lib/discord_store/transport/bucket.rb
62
+ - lib/discord_store/transport/fake.rb
63
+ - lib/discord_store/transport/http.rb
64
+ - lib/discord_store/transport/quota.rb
65
+ - lib/discord_store/transport/rate_limiter.rb
66
+ - lib/discord_store/transport/rest.rb
67
+ - lib/discord_store/version.rb
68
+ homepage: https://github.com/chayuto/discord_store
69
+ licenses:
70
+ - MIT
71
+ metadata:
72
+ source_code_uri: https://github.com/chayuto/discord_store
73
+ bug_tracker_uri: https://github.com/chayuto/discord_store/issues
74
+ changelog_uri: https://github.com/chayuto/discord_store/blob/main/CHANGELOG.md
75
+ documentation_uri: https://rubydoc.info/gems/discord_store
76
+ rubygems_mfa_required: 'true'
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '3.1'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubygems_version: 3.5.22
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Use Discord as a database. Against your better judgement, and its Terms of
96
+ Service.
97
+ test_files: []