async-matrix 2.1.0-aarch64-linux → 3.0.1-aarch64-linux
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 +33 -58
- data/lib/async/matrix/3.3/async_matrix_e2ee.so +0 -0
- data/lib/async/matrix/3.4/async_matrix_e2ee.so +0 -0
- data/lib/async/matrix/4.0/async_matrix_e2ee.so +0 -0
- data/lib/async/matrix/api/chain.rb +120 -94
- data/lib/async/matrix/api/concat.rb +28 -22
- data/lib/async/matrix/api/path_tree.rb +71 -44
- data/lib/async/matrix/api.rb +6 -1
- data/lib/async/matrix/app_service_client.rb +213 -0
- data/lib/async/matrix/auth_error.rb +20 -0
- data/lib/async/matrix/bad_json_error.rb +20 -0
- data/lib/async/matrix/client/encryption.rb +413 -0
- data/lib/async/matrix/client/rooms.rb +478 -0
- data/lib/async/matrix/client/sync.rb +508 -0
- data/lib/async/matrix/client.rb +217 -113
- data/lib/async/matrix/config/vivify.rb +107 -0
- data/lib/async/matrix/config.rb +184 -0
- data/lib/async/matrix/device_store.rb +1621 -0
- data/lib/async/matrix/double_puppet_client.rb +1 -1
- data/lib/async/matrix/e2ee/pickle_key.rb +182 -0
- data/lib/async/matrix/error.rb +9 -36
- data/lib/async/matrix/{application_service/error_response.rb → error_response.rb} +10 -10
- data/lib/async/matrix/homeserver_error.rb +20 -0
- data/lib/async/matrix/invalid_endpoint_error.rb +20 -0
- data/lib/async/matrix/media_client.rb +46 -35
- data/lib/async/matrix/not_found_error.rb +20 -0
- data/lib/async/matrix/notifier.rb +7 -3
- data/lib/async/matrix/response_too_large_error.rb +20 -0
- data/lib/async/matrix/version.rb +1 -1
- data/lib/async/matrix.rb +16 -7
- data/lib/protocol/matrix/canonical_json.rb +200 -0
- data/lib/protocol/matrix/content.rb +106 -0
- data/lib/protocol/matrix/encrypted_message.rb +1018 -0
- data/lib/protocol/matrix/error.rb +49 -0
- data/lib/protocol/matrix/event.rb +208 -0
- data/lib/protocol/matrix/key_backup.rb +347 -0
- data/lib/protocol/matrix/keys.rb +381 -0
- data/lib/protocol/matrix/message_batch.rb +482 -0
- data/lib/protocol/matrix/schema/registry.rb +381 -0
- data/lib/protocol/matrix/schema/validation_error.rb +234 -0
- data/lib/protocol/matrix/schema.rb +170 -0
- data/lib/protocol/matrix/secret_storage.rb +535 -0
- data/lib/protocol/matrix/signing.rb +278 -0
- data/lib/protocol/matrix.rb +25 -0
- metadata +43 -136
- data/lib/async/discord/api/path_tree.rb +0 -127
- data/lib/async/discord/api.rb +0 -151
- data/lib/async/discord/client.rb +0 -283
- data/lib/async/discord/error.rb +0 -84
- data/lib/async/discord/gateway.rb +0 -359
- data/lib/async/discord.rb +0 -15
- data/lib/async/matrix/application_service/bot.rb +0 -232
- data/lib/async/matrix/application_service/config/schema/analytics.json +0 -21
- data/lib/async/matrix/application_service/config/schema/appservice.json +0 -82
- data/lib/async/matrix/application_service/config/schema/backfill.json +0 -91
- data/lib/async/matrix/application_service/config/schema/bridge.json +0 -209
- data/lib/async/matrix/application_service/config/schema/config.json +0 -61
- data/lib/async/matrix/application_service/config/schema/database.json +0 -38
- data/lib/async/matrix/application_service/config/schema/direct_media.json +0 -35
- data/lib/async/matrix/application_service/config/schema/double_puppet.json +0 -24
- data/lib/async/matrix/application_service/config/schema/encryption.json +0 -164
- data/lib/async/matrix/application_service/config/schema/homeserver.json +0 -58
- data/lib/async/matrix/application_service/config/schema/logging.json +0 -50
- data/lib/async/matrix/application_service/config/schema/management_room_texts.json +0 -25
- data/lib/async/matrix/application_service/config/schema/matrix.json +0 -45
- data/lib/async/matrix/application_service/config/schema/permissions.json +0 -54
- data/lib/async/matrix/application_service/config/schema/provisioning.json +0 -23
- data/lib/async/matrix/application_service/config/schema/public_media.json +0 -39
- data/lib/async/matrix/application_service/config/schema/relay.json +0 -43
- data/lib/async/matrix/application_service/config/vivify.rb +0 -109
- data/lib/async/matrix/application_service/config.rb +0 -225
- data/lib/async/matrix/application_service/dispatcher.rb +0 -185
- data/lib/async/matrix/application_service/event.rb +0 -285
- data/lib/async/matrix/application_service/server.rb +0 -430
- data/lib/async/matrix/application_service/transaction.rb +0 -66
- data/lib/async/matrix/application_service/transaction_handler.rb +0 -185
- data/lib/async/matrix/application_service/transaction_store.rb +0 -80
- data/lib/async/matrix/bridge/discord/db/connection.rb +0 -141
- data/lib/async/matrix/bridge/discord/db/file.rb +0 -118
- data/lib/async/matrix/bridge/discord/db/guild.rb +0 -120
- data/lib/async/matrix/bridge/discord/db/message.rb +0 -160
- data/lib/async/matrix/bridge/discord/db/migrations/001_create_users.rb +0 -14
- data/lib/async/matrix/bridge/discord/db/migrations/002_create_guilds.rb +0 -14
- data/lib/async/matrix/bridge/discord/db/migrations/003_create_portals.rb +0 -23
- data/lib/async/matrix/bridge/discord/db/migrations/004_create_puppets.rb +0 -19
- data/lib/async/matrix/bridge/discord/db/migrations/005_create_messages.rb +0 -20
- data/lib/async/matrix/bridge/discord/db/migrations/006_create_reactions.rb +0 -19
- data/lib/async/matrix/bridge/discord/db/migrations/007_create_files.rb +0 -18
- data/lib/async/matrix/bridge/discord/db/portal.rb +0 -150
- data/lib/async/matrix/bridge/discord/db/puppet.rb +0 -128
- data/lib/async/matrix/bridge/discord/db/reaction.rb +0 -165
- data/lib/async/matrix/bridge/discord/db/schema.rb +0 -18
- data/lib/async/matrix/bridge/discord/db/user.rb +0 -112
- data/lib/async/matrix/bridge/discord/db.rb +0 -138
- data/lib/async/matrix/schema/registry.rb +0 -354
- data/lib/async/matrix/schema/validation_error.rb +0 -225
- data/lib/async/matrix/schema.rb +0 -170
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Released under the Apache License, Version 2.0.
|
|
4
|
-
# Copyright, 2026, by General Intelligence Systems.
|
|
5
|
-
|
|
6
|
-
module Async
|
|
7
|
-
module Matrix
|
|
8
|
-
module ApplicationService
|
|
9
|
-
# In-memory idempotency store for appservice transaction IDs.
|
|
10
|
-
class TransactionStore
|
|
11
|
-
DEFAULT_CAPACITY = 1024
|
|
12
|
-
|
|
13
|
-
def initialize(capacity: DEFAULT_CAPACITY)
|
|
14
|
-
@seen = {}
|
|
15
|
-
@capacity = capacity
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def seen?(txn_id)
|
|
19
|
-
@seen.key?(txn_id)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def mark_seen(txn_id)
|
|
23
|
-
prune! if @seen.size >= @capacity
|
|
24
|
-
@seen[txn_id] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def size
|
|
28
|
-
@seen.size
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def prune!
|
|
34
|
-
sorted = @seen.sort_by { |_, ts| ts }
|
|
35
|
-
drop = sorted.size / 2
|
|
36
|
-
sorted.first(drop).each { |id, _| @seen.delete(id) }
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
__END__
|
|
44
|
-
describe "Async::Matrix::ApplicationService::TransactionStore" do
|
|
45
|
-
it "tracks seen transaction IDs" do
|
|
46
|
-
store = Async::Matrix::ApplicationService::TransactionStore.new
|
|
47
|
-
store.seen?("txn1").should == false
|
|
48
|
-
store.mark_seen("txn1")
|
|
49
|
-
store.seen?("txn1").should == true
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
it "reports size" do
|
|
53
|
-
store = Async::Matrix::ApplicationService::TransactionStore.new
|
|
54
|
-
store.size.should == 0
|
|
55
|
-
store.mark_seen("a")
|
|
56
|
-
store.mark_seen("b")
|
|
57
|
-
store.size.should == 2
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
it "prunes oldest entries when capacity is reached" do
|
|
61
|
-
store = Async::Matrix::ApplicationService::TransactionStore.new(capacity: 4)
|
|
62
|
-
store.mark_seen("a")
|
|
63
|
-
store.mark_seen("b")
|
|
64
|
-
store.mark_seen("c")
|
|
65
|
-
store.mark_seen("d")
|
|
66
|
-
# This triggers prune — drops oldest half (a, b)
|
|
67
|
-
store.mark_seen("e")
|
|
68
|
-
store.seen?("a").should == false
|
|
69
|
-
store.seen?("b").should == false
|
|
70
|
-
store.seen?("d").should == true
|
|
71
|
-
store.seen?("e").should == true
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
it "does not prune below capacity" do
|
|
75
|
-
store = Async::Matrix::ApplicationService::TransactionStore.new(capacity: 10)
|
|
76
|
-
5.times { |i| store.mark_seen("txn#{i}") }
|
|
77
|
-
store.size.should == 5
|
|
78
|
-
store.seen?("txn0").should == true
|
|
79
|
-
end
|
|
80
|
-
end
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Released under the Apache License, Version 2.0.
|
|
4
|
-
# Copyright, 2026, by General Intelligence Systems.
|
|
5
|
-
|
|
6
|
-
require "sequel"
|
|
7
|
-
|
|
8
|
-
module Async
|
|
9
|
-
module Matrix
|
|
10
|
-
module Bridge
|
|
11
|
-
module Discord
|
|
12
|
-
module DB
|
|
13
|
-
# Establishes a Sequel database connection from the bridge config's
|
|
14
|
-
# database section.
|
|
15
|
-
#
|
|
16
|
-
# Supports both SQLite (with foreign keys and WAL mode) and PostgreSQL,
|
|
17
|
-
# matching the mautrix bridgev2 database config schema.
|
|
18
|
-
#
|
|
19
|
-
# db = Connection.establish(config.database)
|
|
20
|
-
# db[:users].all # => [...]
|
|
21
|
-
#
|
|
22
|
-
module Connection
|
|
23
|
-
SQLITE_TYPE = "sqlite3-fk-wal"
|
|
24
|
-
POSTGRES_TYPE = "postgres"
|
|
25
|
-
|
|
26
|
-
# Establish a database connection from a config.database section.
|
|
27
|
-
#
|
|
28
|
-
# @param database_config [Hash, Vivify] config.database with :type, :uri, :max_open_conns
|
|
29
|
-
# @return [Sequel::Database]
|
|
30
|
-
def self.establish(database_config)
|
|
31
|
-
type = database_config.type || SQLITE_TYPE
|
|
32
|
-
uri = database_config.uri || "sqlite://bridge.db"
|
|
33
|
-
|
|
34
|
-
db = case type
|
|
35
|
-
when SQLITE_TYPE
|
|
36
|
-
connect_sqlite(uri, database_config)
|
|
37
|
-
when POSTGRES_TYPE
|
|
38
|
-
connect_postgres(uri, database_config)
|
|
39
|
-
else
|
|
40
|
-
raise ArgumentError, "Unknown database type: #{type.inspect}. Expected #{SQLITE_TYPE.inspect} or #{POSTGRES_TYPE.inspect}."
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
db
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
class << self
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def connect_sqlite(uri, config)
|
|
50
|
-
# Normalize mautrix-style URIs: "file:bridge.db?..." -> "sqlite://bridge.db"
|
|
51
|
-
sequel_uri = if uri.start_with?("file:")
|
|
52
|
-
path = uri.sub(%r{\Afile:}, "").split("?").first
|
|
53
|
-
"sqlite://#{path}"
|
|
54
|
-
elsif uri.start_with?("sqlite://")
|
|
55
|
-
uri
|
|
56
|
-
else
|
|
57
|
-
"sqlite://#{uri}"
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
db = Sequel.connect(sequel_uri, max_connections: max_conns(config))
|
|
61
|
-
db.run("PRAGMA foreign_keys = ON")
|
|
62
|
-
db.run("PRAGMA journal_mode = WAL")
|
|
63
|
-
db
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def connect_postgres(uri, config)
|
|
67
|
-
Sequel.connect(uri, max_connections: max_conns(config))
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def max_conns(config)
|
|
71
|
-
val = config.respond_to?(:max_open_conns) ? config.max_open_conns : nil
|
|
72
|
-
val && val > 0 ? val : 5
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
__END__
|
|
83
|
-
describe "Async::Matrix::Bridge::Discord::DB::Connection" do
|
|
84
|
-
it "connects to an in-memory SQLite database" do
|
|
85
|
-
config = Object.new
|
|
86
|
-
config.define_singleton_method(:type) { "sqlite3-fk-wal" }
|
|
87
|
-
config.define_singleton_method(:uri) { "sqlite:/" }
|
|
88
|
-
config.define_singleton_method(:max_open_conns) { 1 }
|
|
89
|
-
|
|
90
|
-
db = Async::Matrix::Bridge::Discord::DB::Connection.establish(config)
|
|
91
|
-
db.should.be.kind_of Sequel::Database
|
|
92
|
-
db.database_type.should == :sqlite
|
|
93
|
-
db.disconnect
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
it "enables foreign keys and WAL on SQLite" do
|
|
97
|
-
config = Object.new
|
|
98
|
-
config.define_singleton_method(:type) { "sqlite3-fk-wal" }
|
|
99
|
-
config.define_singleton_method(:uri) { "sqlite:/" }
|
|
100
|
-
config.define_singleton_method(:max_open_conns) { 1 }
|
|
101
|
-
|
|
102
|
-
db = Async::Matrix::Bridge::Discord::DB::Connection.establish(config)
|
|
103
|
-
fk = db["PRAGMA foreign_keys"].first
|
|
104
|
-
fk[:foreign_keys].should == 1
|
|
105
|
-
db.disconnect
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
it "normalizes mautrix file: URIs to sequel sqlite:// URIs" do
|
|
109
|
-
config = Object.new
|
|
110
|
-
config.define_singleton_method(:type) { "sqlite3-fk-wal" }
|
|
111
|
-
config.define_singleton_method(:uri) { "file:test.db?_txlock=immediate" }
|
|
112
|
-
config.define_singleton_method(:max_open_conns) { 1 }
|
|
113
|
-
|
|
114
|
-
# Should not raise — the URI is normalized
|
|
115
|
-
db = Async::Matrix::Bridge::Discord::DB::Connection.establish(config)
|
|
116
|
-
db.should.be.kind_of Sequel::Database
|
|
117
|
-
db.disconnect
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
it "defaults to sqlite with 5 connections when config is empty" do
|
|
121
|
-
config = Object.new
|
|
122
|
-
config.define_singleton_method(:type) { nil }
|
|
123
|
-
config.define_singleton_method(:uri) { nil }
|
|
124
|
-
config.define_singleton_method(:max_open_conns) { nil }
|
|
125
|
-
|
|
126
|
-
db = Async::Matrix::Bridge::Discord::DB::Connection.establish(config)
|
|
127
|
-
db.should.be.kind_of Sequel::Database
|
|
128
|
-
db.disconnect
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
it "raises on unknown database type" do
|
|
132
|
-
config = Object.new
|
|
133
|
-
config.define_singleton_method(:type) { "mysql" }
|
|
134
|
-
config.define_singleton_method(:uri) { "mysql://localhost/db" }
|
|
135
|
-
config.define_singleton_method(:max_open_conns) { 1 }
|
|
136
|
-
|
|
137
|
-
lambda {
|
|
138
|
-
Async::Matrix::Bridge::Discord::DB::Connection.establish(config)
|
|
139
|
-
}.should.raise(ArgumentError)
|
|
140
|
-
end
|
|
141
|
-
end
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Released under the Apache License, Version 2.0.
|
|
4
|
-
# Copyright, 2026, by General Intelligence Systems.
|
|
5
|
-
|
|
6
|
-
require_relative "schema"
|
|
7
|
-
|
|
8
|
-
module Async
|
|
9
|
-
module Matrix
|
|
10
|
-
module Bridge
|
|
11
|
-
module Discord
|
|
12
|
-
module DB
|
|
13
|
-
# Caches uploaded media to avoid re-uploading the same Discord
|
|
14
|
-
# attachment to the Matrix homeserver.
|
|
15
|
-
#
|
|
16
|
-
# Uses a composite primary key of (url, encrypted) because the same
|
|
17
|
-
# source URL may be uploaded both encrypted and unencrypted to
|
|
18
|
-
# different portal rooms.
|
|
19
|
-
#
|
|
20
|
-
# cached = CachedFile.create(
|
|
21
|
-
# url: "https://cdn.discordapp.com/...",
|
|
22
|
-
# encrypted: false,
|
|
23
|
-
# mxc: "mxc://example.com/abc"
|
|
24
|
-
# )
|
|
25
|
-
#
|
|
26
|
-
class CachedFile < Sequel::Model
|
|
27
|
-
unrestrict_primary_key
|
|
28
|
-
|
|
29
|
-
def validate
|
|
30
|
-
super
|
|
31
|
-
errors.add(:url, "cannot be empty") if url.nil? || url.empty?
|
|
32
|
-
errors.add(:mxc, "cannot be empty") if mxc.nil? || mxc.empty?
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Look up a cached file by source URL and encryption status.
|
|
36
|
-
def self.by_url(url, encrypted: false)
|
|
37
|
-
first(url: url, encrypted: encrypted)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
__END__
|
|
47
|
-
describe "Async::Matrix::Bridge::Discord::DB::CachedFile" do
|
|
48
|
-
def setup_db
|
|
49
|
-
db = Sequel.sqlite
|
|
50
|
-
Sequel::Migrator.run(db, File.join(__dir__, "migrations"))
|
|
51
|
-
db
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it "creates and retrieves a cached file" do
|
|
55
|
-
db = setup_db
|
|
56
|
-
Async::Matrix::Bridge::Discord::DB::CachedFile.dataset = db[:files]
|
|
57
|
-
|
|
58
|
-
cached = Async::Matrix::Bridge::Discord::DB::CachedFile.create(
|
|
59
|
-
url: "https://cdn.discordapp.com/attachments/123/456/image.png",
|
|
60
|
-
mxc: "mxc://example.com/abc",
|
|
61
|
-
mime_type: "image/png",
|
|
62
|
-
size: 102400,
|
|
63
|
-
width: 800,
|
|
64
|
-
height: 600
|
|
65
|
-
)
|
|
66
|
-
cached.url.should.include "discordapp.com"
|
|
67
|
-
cached.mxc.should == "mxc://example.com/abc"
|
|
68
|
-
cached.mime_type.should == "image/png"
|
|
69
|
-
cached.width.should == 800
|
|
70
|
-
cached.height.should == 600
|
|
71
|
-
db.disconnect
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
it "looks up by URL and encryption status" do
|
|
75
|
-
db = setup_db
|
|
76
|
-
Async::Matrix::Bridge::Discord::DB::CachedFile.dataset = db[:files]
|
|
77
|
-
|
|
78
|
-
Async::Matrix::Bridge::Discord::DB::CachedFile.create(
|
|
79
|
-
url: "https://example.com/file.png", encrypted: false,
|
|
80
|
-
mxc: "mxc://x/plain"
|
|
81
|
-
)
|
|
82
|
-
Async::Matrix::Bridge::Discord::DB::CachedFile.create(
|
|
83
|
-
url: "https://example.com/file.png", encrypted: true,
|
|
84
|
-
mxc: "mxc://x/encrypted",
|
|
85
|
-
decryption_info: '{"key":"abc"}'
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
plain = Async::Matrix::Bridge::Discord::DB::CachedFile.by_url("https://example.com/file.png")
|
|
89
|
-
plain.mxc.should == "mxc://x/plain"
|
|
90
|
-
|
|
91
|
-
enc = Async::Matrix::Bridge::Discord::DB::CachedFile.by_url("https://example.com/file.png", encrypted: true)
|
|
92
|
-
enc.mxc.should == "mxc://x/encrypted"
|
|
93
|
-
enc.decryption_info.should == '{"key":"abc"}'
|
|
94
|
-
db.disconnect
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
it "defaults encrypted to false" do
|
|
98
|
-
db = setup_db
|
|
99
|
-
Async::Matrix::Bridge::Discord::DB::CachedFile.dataset = db[:files]
|
|
100
|
-
|
|
101
|
-
cached = Async::Matrix::Bridge::Discord::DB::CachedFile.create(
|
|
102
|
-
url: "https://example.com/a.jpg", mxc: "mxc://x/y"
|
|
103
|
-
)
|
|
104
|
-
cached.encrypted.should == false
|
|
105
|
-
db.disconnect
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
it "validates required fields" do
|
|
109
|
-
db = setup_db
|
|
110
|
-
Async::Matrix::Bridge::Discord::DB::CachedFile.dataset = db[:files]
|
|
111
|
-
|
|
112
|
-
f = Async::Matrix::Bridge::Discord::DB::CachedFile.new(url: "", mxc: "")
|
|
113
|
-
f.valid?.should == false
|
|
114
|
-
f.errors[:url].should.not.be.empty
|
|
115
|
-
f.errors[:mxc].should.not.be.empty
|
|
116
|
-
db.disconnect
|
|
117
|
-
end
|
|
118
|
-
end
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Released under the Apache License, Version 2.0.
|
|
4
|
-
# Copyright, 2026, by General Intelligence Systems.
|
|
5
|
-
|
|
6
|
-
require_relative "schema"
|
|
7
|
-
|
|
8
|
-
module Async
|
|
9
|
-
module Matrix
|
|
10
|
-
module Bridge
|
|
11
|
-
module Discord
|
|
12
|
-
module DB
|
|
13
|
-
# Maps a Discord guild (server) to a Matrix Space room.
|
|
14
|
-
#
|
|
15
|
-
# Bridging modes control how aggressively the bridge creates portals:
|
|
16
|
-
# BRIDGE_NOTHING = 0 — never bridge
|
|
17
|
-
# BRIDGE_IF_PORTAL_EXISTS = 1 — only bridge existing portals
|
|
18
|
-
# BRIDGE_CREATE_ON_MESSAGE = 2 — create portals on first message
|
|
19
|
-
# BRIDGE_EVERYTHING = 3 — proactively create all portals
|
|
20
|
-
#
|
|
21
|
-
# guild = Guild.create(discord_id: "999", name: "My Server", bridging_mode: 3)
|
|
22
|
-
# guild.portals # => [Portal, ...]
|
|
23
|
-
#
|
|
24
|
-
class Guild < Sequel::Model
|
|
25
|
-
unrestrict_primary_key
|
|
26
|
-
|
|
27
|
-
BRIDGE_NOTHING = 0
|
|
28
|
-
BRIDGE_IF_PORTAL_EXISTS = 1
|
|
29
|
-
BRIDGE_CREATE_ON_MESSAGE = 2
|
|
30
|
-
BRIDGE_EVERYTHING = 3
|
|
31
|
-
|
|
32
|
-
one_to_many :portals,
|
|
33
|
-
class: "Async::Matrix::Bridge::Discord::DB::Portal",
|
|
34
|
-
key: :discord_guild_id,
|
|
35
|
-
primary_key: :discord_id
|
|
36
|
-
|
|
37
|
-
def validate
|
|
38
|
-
super
|
|
39
|
-
errors.add(:discord_id, "cannot be empty") if discord_id.nil? || discord_id.empty?
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def bridge_nothing?
|
|
43
|
-
bridging_mode == BRIDGE_NOTHING
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def bridge_everything?
|
|
47
|
-
bridging_mode == BRIDGE_EVERYTHING
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
__END__
|
|
57
|
-
describe "Async::Matrix::Bridge::Discord::DB::Guild" do
|
|
58
|
-
def setup_db
|
|
59
|
-
db = Sequel.sqlite
|
|
60
|
-
Sequel::Migrator.run(db, File.join(__dir__, "migrations"))
|
|
61
|
-
db
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
it "creates and retrieves a guild" do
|
|
65
|
-
db = setup_db
|
|
66
|
-
Async::Matrix::Bridge::Discord::DB::Guild.dataset = db[:guilds]
|
|
67
|
-
|
|
68
|
-
guild = Async::Matrix::Bridge::Discord::DB::Guild.create(
|
|
69
|
-
discord_id: "999",
|
|
70
|
-
name: "Test Guild",
|
|
71
|
-
mxid: "!space:example.com"
|
|
72
|
-
)
|
|
73
|
-
guild.discord_id.should == "999"
|
|
74
|
-
guild.name.should == "Test Guild"
|
|
75
|
-
guild.mxid.should == "!space:example.com"
|
|
76
|
-
db.disconnect
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
it "defaults bridging_mode to 0 (nothing)" do
|
|
80
|
-
db = setup_db
|
|
81
|
-
Async::Matrix::Bridge::Discord::DB::Guild.dataset = db[:guilds]
|
|
82
|
-
|
|
83
|
-
guild = Async::Matrix::Bridge::Discord::DB::Guild.create(discord_id: "100")
|
|
84
|
-
guild.bridging_mode.should == 0
|
|
85
|
-
guild.bridge_nothing?.should == true
|
|
86
|
-
guild.bridge_everything?.should == false
|
|
87
|
-
db.disconnect
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
it "supports all bridging modes" do
|
|
91
|
-
db = setup_db
|
|
92
|
-
Async::Matrix::Bridge::Discord::DB::Guild.dataset = db[:guilds]
|
|
93
|
-
|
|
94
|
-
guild = Async::Matrix::Bridge::Discord::DB::Guild.create(discord_id: "200", bridging_mode: 3)
|
|
95
|
-
guild.bridge_everything?.should == true
|
|
96
|
-
guild.bridge_nothing?.should == false
|
|
97
|
-
db.disconnect
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
it "validates discord_id is present" do
|
|
101
|
-
db = setup_db
|
|
102
|
-
Async::Matrix::Bridge::Discord::DB::Guild.dataset = db[:guilds]
|
|
103
|
-
|
|
104
|
-
guild = Async::Matrix::Bridge::Discord::DB::Guild.new(discord_id: "")
|
|
105
|
-
guild.valid?.should == false
|
|
106
|
-
guild.errors[:discord_id].should.not.be.empty
|
|
107
|
-
db.disconnect
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
it "enforces unique mxid" do
|
|
111
|
-
db = setup_db
|
|
112
|
-
Async::Matrix::Bridge::Discord::DB::Guild.dataset = db[:guilds]
|
|
113
|
-
|
|
114
|
-
Async::Matrix::Bridge::Discord::DB::Guild.create(discord_id: "300", mxid: "!a:x")
|
|
115
|
-
lambda {
|
|
116
|
-
Async::Matrix::Bridge::Discord::DB::Guild.create(discord_id: "301", mxid: "!a:x")
|
|
117
|
-
}.should.raise(Sequel::UniqueConstraintViolation)
|
|
118
|
-
db.disconnect
|
|
119
|
-
end
|
|
120
|
-
end
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Released under the Apache License, Version 2.0.
|
|
4
|
-
# Copyright, 2026, by General Intelligence Systems.
|
|
5
|
-
|
|
6
|
-
require_relative "schema"
|
|
7
|
-
|
|
8
|
-
module Async
|
|
9
|
-
module Matrix
|
|
10
|
-
module Bridge
|
|
11
|
-
module Discord
|
|
12
|
-
module DB
|
|
13
|
-
# Maps a Discord message to one or more Matrix events.
|
|
14
|
-
#
|
|
15
|
-
# A single Discord message may produce multiple Matrix events (one per
|
|
16
|
-
# attachment), distinguished by discord_attachment_id. The composite
|
|
17
|
-
# unique index on (discord_id, discord_attachment_id, discord_channel_id,
|
|
18
|
-
# discord_channel_receiver) ensures no duplicates.
|
|
19
|
-
#
|
|
20
|
-
# msg = Message.create(
|
|
21
|
-
# discord_id: "msg1", discord_channel_id: "ch1",
|
|
22
|
-
# discord_sender: "user1", mxid: "$evt1", timestamp: 1234567890
|
|
23
|
-
# )
|
|
24
|
-
# msg.portal # => Portal
|
|
25
|
-
#
|
|
26
|
-
class Message < Sequel::Model
|
|
27
|
-
many_to_one :portal,
|
|
28
|
-
class: "Async::Matrix::Bridge::Discord::DB::Portal",
|
|
29
|
-
key: [:discord_channel_id, :discord_channel_receiver],
|
|
30
|
-
primary_key: [:discord_id, :receiver]
|
|
31
|
-
|
|
32
|
-
def validate
|
|
33
|
-
super
|
|
34
|
-
errors.add(:discord_id, "cannot be empty") if discord_id.nil? || discord_id.empty?
|
|
35
|
-
errors.add(:mxid, "cannot be empty") if mxid.nil? || mxid.empty?
|
|
36
|
-
errors.add(:discord_sender, "cannot be empty") if discord_sender.nil? || discord_sender.empty?
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Find all parts of a Discord message (text + attachments).
|
|
40
|
-
def self.by_discord_id(discord_id, channel_id, receiver = "")
|
|
41
|
-
where(
|
|
42
|
-
discord_id: discord_id,
|
|
43
|
-
discord_channel_id: channel_id,
|
|
44
|
-
discord_channel_receiver: receiver
|
|
45
|
-
).order(:discord_attachment_id).all
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Find a message by its Matrix event ID.
|
|
49
|
-
def self.by_mxid(mxid)
|
|
50
|
-
first(mxid: mxid)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
__END__
|
|
60
|
-
describe "Async::Matrix::Bridge::Discord::DB::Message" do
|
|
61
|
-
def setup_db
|
|
62
|
-
db = Sequel.sqlite
|
|
63
|
-
Sequel::Migrator.run(db, File.join(__dir__, "migrations"))
|
|
64
|
-
db
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def set_datasets(db)
|
|
68
|
-
Async::Matrix::Bridge::Discord::DB::Message.dataset = db[:messages]
|
|
69
|
-
Async::Matrix::Bridge::Discord::DB::Portal.dataset = db[:portals]
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
it "creates and retrieves a message" do
|
|
73
|
-
db = setup_db
|
|
74
|
-
set_datasets(db)
|
|
75
|
-
|
|
76
|
-
msg = Async::Matrix::Bridge::Discord::DB::Message.create(
|
|
77
|
-
discord_id: "msg1",
|
|
78
|
-
discord_channel_id: "ch1",
|
|
79
|
-
discord_sender: "user1",
|
|
80
|
-
mxid: "$evt1",
|
|
81
|
-
timestamp: 1234567890
|
|
82
|
-
)
|
|
83
|
-
msg.discord_id.should == "msg1"
|
|
84
|
-
msg.mxid.should == "$evt1"
|
|
85
|
-
msg.discord_attachment_id.should == ""
|
|
86
|
-
db.disconnect
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
it "supports multi-part messages with attachments" do
|
|
90
|
-
db = setup_db
|
|
91
|
-
set_datasets(db)
|
|
92
|
-
|
|
93
|
-
Async::Matrix::Bridge::Discord::DB::Message.create(
|
|
94
|
-
discord_id: "msg2", discord_attachment_id: "",
|
|
95
|
-
discord_channel_id: "ch1", discord_sender: "u1",
|
|
96
|
-
mxid: "$text", timestamp: 100
|
|
97
|
-
)
|
|
98
|
-
Async::Matrix::Bridge::Discord::DB::Message.create(
|
|
99
|
-
discord_id: "msg2", discord_attachment_id: "att1",
|
|
100
|
-
discord_channel_id: "ch1", discord_sender: "u1",
|
|
101
|
-
mxid: "$img1", timestamp: 100
|
|
102
|
-
)
|
|
103
|
-
Async::Matrix::Bridge::Discord::DB::Message.create(
|
|
104
|
-
discord_id: "msg2", discord_attachment_id: "att2",
|
|
105
|
-
discord_channel_id: "ch1", discord_sender: "u1",
|
|
106
|
-
mxid: "$img2", timestamp: 100
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
parts = Async::Matrix::Bridge::Discord::DB::Message.by_discord_id("msg2", "ch1")
|
|
110
|
-
parts.length.should == 3
|
|
111
|
-
parts.map(&:mxid).should == ["$text", "$img1", "$img2"]
|
|
112
|
-
db.disconnect
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
it "finds by Matrix event ID" do
|
|
116
|
-
db = setup_db
|
|
117
|
-
set_datasets(db)
|
|
118
|
-
|
|
119
|
-
Async::Matrix::Bridge::Discord::DB::Message.create(
|
|
120
|
-
discord_id: "msg3", discord_channel_id: "ch1",
|
|
121
|
-
discord_sender: "u1", mxid: "$find_me", timestamp: 200
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
found = Async::Matrix::Bridge::Discord::DB::Message.by_mxid("$find_me")
|
|
125
|
-
found.should.not.be.nil
|
|
126
|
-
found.discord_id.should == "msg3"
|
|
127
|
-
db.disconnect
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
it "enforces unique composite key" do
|
|
131
|
-
db = setup_db
|
|
132
|
-
set_datasets(db)
|
|
133
|
-
|
|
134
|
-
Async::Matrix::Bridge::Discord::DB::Message.create(
|
|
135
|
-
discord_id: "msg4", discord_attachment_id: "",
|
|
136
|
-
discord_channel_id: "ch1", discord_channel_receiver: "",
|
|
137
|
-
discord_sender: "u1", mxid: "$a", timestamp: 300
|
|
138
|
-
)
|
|
139
|
-
lambda {
|
|
140
|
-
Async::Matrix::Bridge::Discord::DB::Message.create(
|
|
141
|
-
discord_id: "msg4", discord_attachment_id: "",
|
|
142
|
-
discord_channel_id: "ch1", discord_channel_receiver: "",
|
|
143
|
-
discord_sender: "u1", mxid: "$b", timestamp: 300
|
|
144
|
-
)
|
|
145
|
-
}.should.raise(Sequel::UniqueConstraintViolation)
|
|
146
|
-
db.disconnect
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
it "validates required fields" do
|
|
150
|
-
db = setup_db
|
|
151
|
-
set_datasets(db)
|
|
152
|
-
|
|
153
|
-
msg = Async::Matrix::Bridge::Discord::DB::Message.new(discord_id: "", mxid: "", discord_sender: "")
|
|
154
|
-
msg.valid?.should == false
|
|
155
|
-
msg.errors[:discord_id].should.not.be.empty
|
|
156
|
-
msg.errors[:mxid].should.not.be.empty
|
|
157
|
-
msg.errors[:discord_sender].should.not.be.empty
|
|
158
|
-
db.disconnect
|
|
159
|
-
end
|
|
160
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
Sequel.migration do
|
|
4
|
-
change do
|
|
5
|
-
create_table(:users) do
|
|
6
|
-
String :mxid, primary_key: true
|
|
7
|
-
String :discord_id, unique: true
|
|
8
|
-
String :discord_token
|
|
9
|
-
String :management_room
|
|
10
|
-
String :space_room
|
|
11
|
-
String :dm_space_room
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
Sequel.migration do
|
|
4
|
-
change do
|
|
5
|
-
create_table(:guilds) do
|
|
6
|
-
String :discord_id, primary_key: true
|
|
7
|
-
String :mxid, unique: true
|
|
8
|
-
String :name
|
|
9
|
-
String :avatar_hash
|
|
10
|
-
String :avatar_url
|
|
11
|
-
Integer :bridging_mode, default: 0, null: false
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
Sequel.migration do
|
|
4
|
-
change do
|
|
5
|
-
create_table(:portals) do
|
|
6
|
-
String :discord_id, null: false
|
|
7
|
-
String :receiver, null: false, default: ""
|
|
8
|
-
String :mxid, unique: true
|
|
9
|
-
String :discord_guild_id
|
|
10
|
-
String :name
|
|
11
|
-
String :topic
|
|
12
|
-
String :avatar_hash
|
|
13
|
-
String :avatar_url
|
|
14
|
-
TrueClass :encrypted, default: false, null: false
|
|
15
|
-
Integer :channel_type, default: 0, null: false
|
|
16
|
-
String :relay_webhook_id
|
|
17
|
-
String :relay_webhook_secret
|
|
18
|
-
|
|
19
|
-
primary_key [:discord_id, :receiver]
|
|
20
|
-
foreign_key [:discord_guild_id], :guilds, key: :discord_id
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
Sequel.migration do
|
|
4
|
-
change do
|
|
5
|
-
create_table(:puppets) do
|
|
6
|
-
String :discord_id, primary_key: true
|
|
7
|
-
String :name
|
|
8
|
-
String :avatar_hash
|
|
9
|
-
String :avatar_url
|
|
10
|
-
String :username
|
|
11
|
-
String :global_name
|
|
12
|
-
TrueClass :is_bot, default: false, null: false
|
|
13
|
-
TrueClass :is_webhook, default: false, null: false
|
|
14
|
-
TrueClass :contact_info_set, default: false, null: false
|
|
15
|
-
String :custom_mxid
|
|
16
|
-
String :access_token
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|