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,138 +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
|
-
require "sequel/extensions/migration"
|
|
8
|
-
|
|
9
|
-
# The Discord bridge's database layer. Requiring this file loads the whole
|
|
10
|
-
# subsystem: the Sequel placeholder (schema), the connection helper, and every
|
|
11
|
-
# model class, so DB.connect can bind them all.
|
|
12
|
-
require_relative "db/schema"
|
|
13
|
-
require_relative "db/connection"
|
|
14
|
-
require_relative "db/user"
|
|
15
|
-
require_relative "db/guild"
|
|
16
|
-
require_relative "db/portal"
|
|
17
|
-
require_relative "db/puppet"
|
|
18
|
-
require_relative "db/message"
|
|
19
|
-
require_relative "db/reaction"
|
|
20
|
-
require_relative "db/file"
|
|
21
|
-
|
|
22
|
-
module Async
|
|
23
|
-
module Matrix
|
|
24
|
-
module Bridge
|
|
25
|
-
module Discord
|
|
26
|
-
# Database module for the Discord bridge.
|
|
27
|
-
#
|
|
28
|
-
# Establishes a connection using the bridge config's database section,
|
|
29
|
-
# runs Sequel migrations, and wires up all model classes to the database.
|
|
30
|
-
#
|
|
31
|
-
# Supports both SQLite (with foreign keys + WAL) and PostgreSQL.
|
|
32
|
-
#
|
|
33
|
-
# db = Async::Matrix::Bridge::Discord::DB.connect(config)
|
|
34
|
-
# # All models are now ready:
|
|
35
|
-
# DB::User.create(mxid: "@alice:example.com")
|
|
36
|
-
# DB::Portal.where(discord_guild_id: "123").all
|
|
37
|
-
#
|
|
38
|
-
module DB
|
|
39
|
-
MIGRATIONS_PATH = File.join(__dir__, "db", "migrations").freeze
|
|
40
|
-
|
|
41
|
-
# Connect to the database, run migrations, and bind all models.
|
|
42
|
-
#
|
|
43
|
-
# @param config [Async::Matrix::ApplicationService::Config] the bridge config
|
|
44
|
-
# @return [Sequel::Database]
|
|
45
|
-
def self.connect(config)
|
|
46
|
-
db = Connection.establish(config.database)
|
|
47
|
-
migrate!(db)
|
|
48
|
-
bind_models(db)
|
|
49
|
-
db
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Run pending migrations.
|
|
53
|
-
#
|
|
54
|
-
# @param db [Sequel::Database]
|
|
55
|
-
def self.migrate!(db)
|
|
56
|
-
Sequel::Migrator.run(db, MIGRATIONS_PATH)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Bind all model classes to the given database.
|
|
60
|
-
#
|
|
61
|
-
# @param db [Sequel::Database]
|
|
62
|
-
def self.bind_models(db)
|
|
63
|
-
Async::Matrix::Bridge::Discord::DB::User.dataset = db[:users]
|
|
64
|
-
Async::Matrix::Bridge::Discord::DB::Guild.dataset = db[:guilds]
|
|
65
|
-
Async::Matrix::Bridge::Discord::DB::Portal.dataset = db[:portals]
|
|
66
|
-
Async::Matrix::Bridge::Discord::DB::Puppet.dataset = db[:puppets]
|
|
67
|
-
Async::Matrix::Bridge::Discord::DB::Message.dataset = db[:messages]
|
|
68
|
-
Async::Matrix::Bridge::Discord::DB::Reaction.dataset = db[:reactions]
|
|
69
|
-
Async::Matrix::Bridge::Discord::DB::CachedFile.dataset = db[:files]
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
__END__
|
|
78
|
-
describe "Async::Matrix::Bridge::Discord::DB" do
|
|
79
|
-
it "connects, migrates, and binds models from config" do
|
|
80
|
-
database_config = Object.new
|
|
81
|
-
database_config.define_singleton_method(:type) { "sqlite3-fk-wal" }
|
|
82
|
-
database_config.define_singleton_method(:uri) { "sqlite:/" }
|
|
83
|
-
database_config.define_singleton_method(:max_open_conns) { 1 }
|
|
84
|
-
|
|
85
|
-
config = Object.new
|
|
86
|
-
config.define_singleton_method(:database) { database_config }
|
|
87
|
-
|
|
88
|
-
db = Async::Matrix::Bridge::Discord::DB.connect(config)
|
|
89
|
-
db.should.be.kind_of Sequel::Database
|
|
90
|
-
|
|
91
|
-
# All tables should exist
|
|
92
|
-
db.tables.should.include :users
|
|
93
|
-
db.tables.should.include :guilds
|
|
94
|
-
db.tables.should.include :portals
|
|
95
|
-
db.tables.should.include :puppets
|
|
96
|
-
db.tables.should.include :messages
|
|
97
|
-
db.tables.should.include :reactions
|
|
98
|
-
db.tables.should.include :files
|
|
99
|
-
|
|
100
|
-
# Models should be bound and functional
|
|
101
|
-
db[:users].delete
|
|
102
|
-
db[:guilds].delete
|
|
103
|
-
db[:portals].delete
|
|
104
|
-
db[:puppets].delete
|
|
105
|
-
|
|
106
|
-
Async::Matrix::Bridge::Discord::DB::User.create(mxid: "@db_int:x")
|
|
107
|
-
Async::Matrix::Bridge::Discord::DB::User["@db_int:x"].should.not.be.nil
|
|
108
|
-
|
|
109
|
-
Async::Matrix::Bridge::Discord::DB::Guild.create(discord_id: "g_int")
|
|
110
|
-
Async::Matrix::Bridge::Discord::DB::Guild["g_int"].should.not.be.nil
|
|
111
|
-
|
|
112
|
-
Async::Matrix::Bridge::Discord::DB::Portal.create(discord_id: "p_int", receiver: "")
|
|
113
|
-
Async::Matrix::Bridge::Discord::DB::Portal["p_int", ""].should.not.be.nil
|
|
114
|
-
|
|
115
|
-
Async::Matrix::Bridge::Discord::DB::Puppet.create(discord_id: "d_int")
|
|
116
|
-
Async::Matrix::Bridge::Discord::DB::Puppet["d_int"].should.not.be.nil
|
|
117
|
-
|
|
118
|
-
db.disconnect
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
it "exposes MIGRATIONS_PATH" do
|
|
122
|
-
File.directory?(Async::Matrix::Bridge::Discord::DB::MIGRATIONS_PATH).should == true
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
it "is idempotent — running migrations twice does not raise" do
|
|
126
|
-
database_config = Object.new
|
|
127
|
-
database_config.define_singleton_method(:type) { "sqlite3-fk-wal" }
|
|
128
|
-
database_config.define_singleton_method(:uri) { "sqlite:/" }
|
|
129
|
-
database_config.define_singleton_method(:max_open_conns) { 1 }
|
|
130
|
-
|
|
131
|
-
config = Object.new
|
|
132
|
-
config.define_singleton_method(:database) { database_config }
|
|
133
|
-
|
|
134
|
-
db = Async::Matrix::Bridge::Discord::DB.connect(config)
|
|
135
|
-
lambda { Async::Matrix::Bridge::Discord::DB.migrate!(db) }.should.not.raise
|
|
136
|
-
db.disconnect
|
|
137
|
-
end
|
|
138
|
-
end
|
|
@@ -1,354 +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 "yaml"
|
|
7
|
-
require "json"
|
|
8
|
-
require "json_schemer"
|
|
9
|
-
require "pathname"
|
|
10
|
-
|
|
11
|
-
module Async
|
|
12
|
-
module Matrix
|
|
13
|
-
module Schema
|
|
14
|
-
# Loads and indexes Matrix event schemas from the official matrix-org/matrix-spec
|
|
15
|
-
# YAML files bundled in data/matrix-spec/event-schemas/schema/.
|
|
16
|
-
#
|
|
17
|
-
# Schemas are loaded lazily on first access and cached for the lifetime of the
|
|
18
|
-
# process. Each schema is a JSONSchemer::Schema instance that resolves relative
|
|
19
|
-
# $ref paths (e.g. core-event-schema/room_event.yaml) via a file-based resolver.
|
|
20
|
-
#
|
|
21
|
-
# registry = Async::Matrix::Schema::Registry.instance
|
|
22
|
-
# registry["m.room.message"] # => JSONSchemer::Schema
|
|
23
|
-
# registry.variant("m.room.message", "m.text") # => JSONSchemer::Schema
|
|
24
|
-
# registry.event_types # => ["m.accepted_terms", ...]
|
|
25
|
-
#
|
|
26
|
-
class Registry
|
|
27
|
-
# Custom format validators for Matrix-specific format hints.
|
|
28
|
-
MATRIX_FORMATS = {
|
|
29
|
-
"mx-user-id" => ->(value, _schema) { value.is_a?(String) && value.match?(/\A@[^:]+:.+\z/) },
|
|
30
|
-
"mx-room-id" => ->(value, _schema) { value.is_a?(String) && value.match?(/\A![^:]+:.+\z/) },
|
|
31
|
-
"mx-event-id" => ->(value, _schema) { value.is_a?(String) && value.match?(/\A\$/) },
|
|
32
|
-
"mx-room-alias" => ->(value, _schema) { value.is_a?(String) && value.match?(/\A#[^:]+:.+\z/) },
|
|
33
|
-
"mx-server-name" => ->(value, _schema) { value.is_a?(String) && !value.empty? },
|
|
34
|
-
"mx-unpadded-base64" => ->(value, _schema) { value.is_a?(String) && value.match?(/\A[A-Za-z0-9+\/]*\z/) },
|
|
35
|
-
}.freeze
|
|
36
|
-
|
|
37
|
-
GEM_ROOT = File.expand_path("../../../..", __dir__)
|
|
38
|
-
SCHEMA_DIR = File.join(GEM_ROOT, "data", "matrix-spec", "event-schemas", "schema")
|
|
39
|
-
|
|
40
|
-
@instance = nil
|
|
41
|
-
|
|
42
|
-
class << self
|
|
43
|
-
# Returns the singleton Registry instance, creating it on first access.
|
|
44
|
-
def instance
|
|
45
|
-
@instance ||= new
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Reset the singleton (useful for testing).
|
|
49
|
-
def reset!
|
|
50
|
-
@instance = nil
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def initialize
|
|
55
|
-
@schemas = {}
|
|
56
|
-
@variants = {}
|
|
57
|
-
@loaded = false
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Look up a schema by Matrix event type (e.g. "m.room.message").
|
|
61
|
-
# Returns a JSONSchemer::Schema or nil if no schema exists for that type.
|
|
62
|
-
def [](event_type)
|
|
63
|
-
load_schemas! unless @loaded
|
|
64
|
-
@schemas[event_type]
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Look up a variant schema (e.g. variant("m.room.message", "m.text")).
|
|
68
|
-
# Returns a JSONSchemer::Schema or nil.
|
|
69
|
-
def variant(event_type, subtype)
|
|
70
|
-
load_schemas! unless @loaded
|
|
71
|
-
@variants[[event_type, subtype]]
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# All known base event types (excludes variant subtypes).
|
|
75
|
-
# @return [Array<String>] sorted event type strings
|
|
76
|
-
def event_types
|
|
77
|
-
load_schemas! unless @loaded
|
|
78
|
-
@schemas.keys.sort
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# All known variant keys as [event_type, subtype] pairs.
|
|
82
|
-
# @return [Array<Array(String, String)>]
|
|
83
|
-
def variant_types
|
|
84
|
-
load_schemas! unless @loaded
|
|
85
|
-
@variants.keys.sort
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Total number of schemas loaded (base + variants).
|
|
89
|
-
def size
|
|
90
|
-
load_schemas! unless @loaded
|
|
91
|
-
@schemas.size + @variants.size
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Validate an event hash against its schema.
|
|
95
|
-
# Returns an array of error hashes (empty if valid).
|
|
96
|
-
# If no schema exists for the event type, returns empty (lenient).
|
|
97
|
-
def validate(event_hash)
|
|
98
|
-
event_type = event_hash["type"]
|
|
99
|
-
return [] unless event_type
|
|
100
|
-
|
|
101
|
-
base = self[event_type]
|
|
102
|
-
return [] unless base
|
|
103
|
-
|
|
104
|
-
errors = base.validate(event_hash).to_a
|
|
105
|
-
|
|
106
|
-
# Also validate against variant if applicable
|
|
107
|
-
variant_key = detect_variant_key(event_type, event_hash)
|
|
108
|
-
if variant_key
|
|
109
|
-
variant_schema = variant(event_type, variant_key)
|
|
110
|
-
if variant_schema
|
|
111
|
-
errors.concat(variant_schema.validate(event_hash).to_a)
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
errors
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# Boolean validation.
|
|
119
|
-
def valid?(event_hash)
|
|
120
|
-
validate(event_hash).empty?
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Content properties defined by the schema for a given event type.
|
|
124
|
-
# Returns an array of property name strings, or empty array if unknown.
|
|
125
|
-
def content_properties(event_type)
|
|
126
|
-
schema = self[event_type]
|
|
127
|
-
return [] unless schema
|
|
128
|
-
|
|
129
|
-
extract_content_properties(schema.value)
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
private
|
|
133
|
-
|
|
134
|
-
# Ref resolver that loads YAML/JSON files from disk.
|
|
135
|
-
# json_schemer calls this with a URI for each $ref it encounters.
|
|
136
|
-
def ref_resolver
|
|
137
|
-
@ref_resolver ||= proc do |uri|
|
|
138
|
-
path = uri.path
|
|
139
|
-
if path && File.exist?(path)
|
|
140
|
-
content = File.read(path)
|
|
141
|
-
if path.end_with?(".yaml", ".yml")
|
|
142
|
-
YAML.safe_load(content, permitted_classes: [Symbol])
|
|
143
|
-
else
|
|
144
|
-
JSON.parse(content)
|
|
145
|
-
end
|
|
146
|
-
else
|
|
147
|
-
raise JSONSchemer::UnknownRef, uri.to_s
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Build a JSONSchemer::Schema from a parsed YAML hash, anchored at the
|
|
153
|
-
# given file path so relative $ref paths resolve correctly.
|
|
154
|
-
def build_schemer(parsed, file_path)
|
|
155
|
-
uri = URI("file://#{File.expand_path(file_path)}")
|
|
156
|
-
JSONSchemer.schema(
|
|
157
|
-
parsed,
|
|
158
|
-
base_uri: uri,
|
|
159
|
-
ref_resolver: ref_resolver,
|
|
160
|
-
insert_property_defaults: false,
|
|
161
|
-
formats: MATRIX_FORMATS
|
|
162
|
-
)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Scan the schema directory and load all event schemas.
|
|
166
|
-
def load_schemas!
|
|
167
|
-
return if @loaded
|
|
168
|
-
|
|
169
|
-
Dir.glob(File.join(SCHEMA_DIR, "*.yaml")).each do |path|
|
|
170
|
-
load_schema_file(path)
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# Also load JSON schema files (a few older schemas are JSON)
|
|
174
|
-
Dir.glob(File.join(SCHEMA_DIR, "*.json")).each do |path|
|
|
175
|
-
load_schema_file(path)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
@loaded = true
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def load_schema_file(path)
|
|
182
|
-
filename = File.basename(path, File.extname(path))
|
|
183
|
-
content = File.read(path)
|
|
184
|
-
parsed = path.end_with?(".json") ? JSON.parse(content) : YAML.safe_load(content, permitted_classes: [Symbol])
|
|
185
|
-
|
|
186
|
-
schemer = build_schemer(parsed, path)
|
|
187
|
-
|
|
188
|
-
# Detect event type from schema: properties.type.enum[0]
|
|
189
|
-
declared_type = parsed.dig("properties", "type", "enum")&.first
|
|
190
|
-
|
|
191
|
-
if filename.include?("$")
|
|
192
|
-
# Variant schema: "m.room.message$m.text" -> base="m.room.message", sub="m.text"
|
|
193
|
-
parts = filename.split("$", 2)
|
|
194
|
-
base_type = declared_type || parts[0]
|
|
195
|
-
subtype = parts[1]
|
|
196
|
-
@variants[[base_type, subtype]] = schemer
|
|
197
|
-
elsif declared_type
|
|
198
|
-
@schemas[declared_type] = schemer
|
|
199
|
-
else
|
|
200
|
-
# Schemas without a type enum (like core-event-schema/) are skipped
|
|
201
|
-
# from the index -- they're only used as $ref targets.
|
|
202
|
-
end
|
|
203
|
-
rescue => e
|
|
204
|
-
# Don't let a single broken schema prevent loading the rest
|
|
205
|
-
warn "async-matrix: failed to load schema #{path}: #{e.message}"
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
# Detect the variant subtype key from an event hash.
|
|
209
|
-
# For m.room.message, the variant is the msgtype.
|
|
210
|
-
# For m.room.encrypted, the variant is the algorithm.
|
|
211
|
-
# For m.key.verification.start, the variant is the method.
|
|
212
|
-
def detect_variant_key(event_type, event_hash)
|
|
213
|
-
content = event_hash["content"]
|
|
214
|
-
return nil unless content.is_a?(Hash)
|
|
215
|
-
|
|
216
|
-
case event_type
|
|
217
|
-
when "m.room.message"
|
|
218
|
-
content["msgtype"]
|
|
219
|
-
when "m.room.encrypted"
|
|
220
|
-
content["algorithm"]
|
|
221
|
-
when "m.key.verification.start"
|
|
222
|
-
content["method"]
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
# Extract content property names from a schema's parsed YAML.
|
|
227
|
-
def extract_content_properties(schema_hash)
|
|
228
|
-
props = schema_hash.dig("properties", "content", "properties")
|
|
229
|
-
return [] unless props.is_a?(Hash)
|
|
230
|
-
props.keys
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
__END__
|
|
238
|
-
describe "Async::Matrix::Schema::Registry" do
|
|
239
|
-
it "loads schemas from disk" do
|
|
240
|
-
Async::Matrix::Schema::Registry.instance.size.should.be > 0
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
it "has known event types" do
|
|
244
|
-
types = Async::Matrix::Schema::Registry.instance.event_types
|
|
245
|
-
types.should.include "m.room.message"
|
|
246
|
-
types.should.include "m.room.member"
|
|
247
|
-
types.should.include "m.room.create"
|
|
248
|
-
types.should.include "m.reaction"
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
it "returns a JSONSchemer::Schema for a known type" do
|
|
252
|
-
schema = Async::Matrix::Schema::Registry.instance["m.room.message"]
|
|
253
|
-
schema.should.not.be.nil
|
|
254
|
-
schema.should.be.kind_of JSONSchemer::Schema
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
it "returns nil for an unknown type" do
|
|
258
|
-
Async::Matrix::Schema::Registry.instance["m.fake.event"].should.be.nil
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
it "has variant schemas" do
|
|
262
|
-
Async::Matrix::Schema::Registry.instance.variant_types.should.not.be.empty
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
it "resolves variant schemas" do
|
|
266
|
-
schema = Async::Matrix::Schema::Registry.instance.variant("m.room.message", "m.text")
|
|
267
|
-
schema.should.not.be.nil
|
|
268
|
-
schema.should.be.kind_of JSONSchemer::Schema
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
it "validates a correct m.room.message event" do
|
|
272
|
-
event = {
|
|
273
|
-
"type" => "m.room.message",
|
|
274
|
-
"content" => {"msgtype" => "m.text", "body" => "hello"},
|
|
275
|
-
"event_id" => "$abc123",
|
|
276
|
-
"sender" => "@alice:example.org",
|
|
277
|
-
"origin_server_ts" => 1234567890,
|
|
278
|
-
"room_id" => "!room:example.org"
|
|
279
|
-
}
|
|
280
|
-
Async::Matrix::Schema::Registry.instance.valid?(event).should == true
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
it "rejects m.room.message missing body" do
|
|
284
|
-
event = {
|
|
285
|
-
"type" => "m.room.message",
|
|
286
|
-
"content" => {"msgtype" => "m.text"},
|
|
287
|
-
"event_id" => "$abc123",
|
|
288
|
-
"sender" => "@alice:example.org",
|
|
289
|
-
"origin_server_ts" => 1234567890,
|
|
290
|
-
"room_id" => "!room:example.org"
|
|
291
|
-
}
|
|
292
|
-
Async::Matrix::Schema::Registry.instance.valid?(event).should == false
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
it "is lenient with unknown event types" do
|
|
296
|
-
event = {
|
|
297
|
-
"type" => "m.room.other",
|
|
298
|
-
"content" => {"msgtype" => "m.text", "body" => "hi"},
|
|
299
|
-
"event_id" => "$abc123",
|
|
300
|
-
"sender" => "@alice:example.org",
|
|
301
|
-
"origin_server_ts" => 1234567890,
|
|
302
|
-
"room_id" => "!room:example.org"
|
|
303
|
-
}
|
|
304
|
-
Async::Matrix::Schema::Registry.instance.valid?(event).should == true
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
it "validates m.room.member with correct membership" do
|
|
308
|
-
event = {
|
|
309
|
-
"type" => "m.room.member",
|
|
310
|
-
"content" => {"membership" => "join"},
|
|
311
|
-
"state_key" => "@alice:example.org",
|
|
312
|
-
"event_id" => "$abc123",
|
|
313
|
-
"sender" => "@alice:example.org",
|
|
314
|
-
"origin_server_ts" => 1234567890,
|
|
315
|
-
"room_id" => "!room:example.org"
|
|
316
|
-
}
|
|
317
|
-
Async::Matrix::Schema::Registry.instance.valid?(event).should == true
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
it "rejects m.room.member with invalid membership" do
|
|
321
|
-
event = {
|
|
322
|
-
"type" => "m.room.member",
|
|
323
|
-
"content" => {"membership" => "invalid_state"},
|
|
324
|
-
"state_key" => "@alice:example.org",
|
|
325
|
-
"event_id" => "$abc123",
|
|
326
|
-
"sender" => "@alice:example.org",
|
|
327
|
-
"origin_server_ts" => 1234567890,
|
|
328
|
-
"room_id" => "!room:example.org"
|
|
329
|
-
}
|
|
330
|
-
Async::Matrix::Schema::Registry.instance.valid?(event).should == false
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
it "returns content properties for known types" do
|
|
334
|
-
props = Async::Matrix::Schema::Registry.instance.content_properties("m.room.message")
|
|
335
|
-
props.should.include "msgtype"
|
|
336
|
-
props.should.include "body"
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
it "returns empty content properties for unknown types" do
|
|
340
|
-
Async::Matrix::Schema::Registry.instance.content_properties("m.fake.type").should == []
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
it "performs variant validation for m.room.message with msgtype" do
|
|
344
|
-
event = {
|
|
345
|
-
"type" => "m.room.message",
|
|
346
|
-
"content" => {"msgtype" => "m.text", "body" => "hello"},
|
|
347
|
-
"event_id" => "$abc123",
|
|
348
|
-
"sender" => "@alice:example.org",
|
|
349
|
-
"origin_server_ts" => 1234567890,
|
|
350
|
-
"room_id" => "!room:example.org"
|
|
351
|
-
}
|
|
352
|
-
Async::Matrix::Schema::Registry.instance.validate(event).should == []
|
|
353
|
-
end
|
|
354
|
-
end
|