skyfall 0.7.0 → 0.7.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3e987d12df25109508c0f673894e9a334a81d60d02f57c118e699de5e68dc4d
4
- data.tar.gz: 152dfdced7c5fc6414f8cbf04d7cff6d184c592a0ed42463f0b156b373c8c8ac
3
+ metadata.gz: c52f2f3c63530b1e032666c7bfb71ba0b0279bfe0128c7432b8e267f1e2f9e95
4
+ data.tar.gz: 1c7e2e5cac7ec4f421be5c291a311dede53221150901a44418f01f7a6cdeae9e
5
5
  SHA512:
6
- metadata.gz: 59c9da3c3d60b953f3963c16a86c56bb9e75561696ea47d0b8ce5a3274c00a8d831529f22aacace27df0d1dba55b5dc7db58301f1baa7aa19528756d77eb7ccd
7
- data.tar.gz: 0575c825184297417f40a5f7248e2560fc24f19913631c23371b1f535feea3d3b38b62b76c0a4c610dafc2e52783ba58521eaf1f3407aa59699d26640fb0f94c
6
+ metadata.gz: 1eb9556cbc363f6a9c2ae42d27dc9a4471a24f8e08f0b1b8046c131fe320b63de73498d839367f0fd89c3841b27877c53bc0a937b9eeed41ea486f05b55a38c3
7
+ data.tar.gz: 9b61caf5cc63834efd8185622c435a9102b4a310f890a988fd029e9e691e80acf7527e0afc5e5fecde967c4f2de538559168d07a72a2af4dd9d5ef06cc16ab50
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
+ ## [0.7.1] - 2026-08-05
2
+
3
+ - extracted `CID` and `CarArchive` to a new gem "oxygene"
4
+ - added `CommitMessage#cid` as alias of `#commit`
5
+
1
6
  ## [0.7.0] - 2026-02-13
2
7
 
3
- The main change in this version is that inline YARD documentation has been added. This was also a good opportunity to review some APIs and tweak some things in order to get Skyfall a bit closer to 1.0.
8
+ The main change in this version is that inline [YARD documentation](https://rubydoc.info/gems/skyfall/) has been added. This was also a good opportunity to review some APIs and tweak some things in order to get Skyfall a bit closer to 1.0.
4
9
 
5
10
  New APIs:
6
11
 
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Skyfall
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/skyfall.svg?icon=si%3Arubygems&icon_color=%23ff6251)](https://rubygems.org/gems/skyfall) [![YARD Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://rubydoc.info/gems/skyfall)
4
+
3
5
  A Ruby gem for streaming data from the Bluesky/ATProto firehose 🦋
4
6
 
5
7
  > [!NOTE]
@@ -15,7 +17,7 @@ Since version 0.5, Skyfall also supports connecting to [Jetstream](https://githu
15
17
 
16
18
  ## Installation
17
19
 
18
- To use Skyfall, you need a reasonably new version of Ruby – it should run on Ruby 2.6 and above, although it's recommended to use a version that's still getting maintainance updates, i.e. currently 3.2+. A compatible version should be preinstalled on macOS Big Sur and above and on many Linux systems. Otherwise, you can install one using tools such as [RVM](https://rvm.io), [asdf](https://asdf-vm.com), [ruby-install](https://github.com/postmodern/ruby-install) or [ruby-build](https://github.com/rbenv/ruby-build), or `rpm` or `apt-get` on Linux (see more installation options on [ruby-lang.org](https://www.ruby-lang.org/en/downloads/)).
20
+ To use Skyfall, you need a reasonably new version of Ruby – it should run on Ruby 2.6 and above, although it's recommended to use a version that's still getting maintainance updates, i.e. currently 3.3+. A compatible version should be preinstalled on macOS Big Sur and above and on many Linux systems. Otherwise, you can install one using tools such as [RVM](https://rvm.io), [asdf](https://asdf-vm.com), [ruby-install](https://github.com/postmodern/ruby-install) or [ruby-build](https://github.com/rbenv/ruby-build), or `rpm` or `apt-get` on Linux (see more installation options on [ruby-lang.org](https://www.ruby-lang.org/en/downloads/)).
19
21
 
20
22
  To install the gem, run the command:
21
23
 
@@ -38,7 +40,7 @@ require 'skyfall'
38
40
  sky = Skyfall::Firehose.new('bsky.network', :subscribe_repos)
39
41
  ```
40
42
 
41
- The server name can be just a hostname, or a full URL with a `ws:` or `wss:` scheme, which is useful if you want to use a non-encrypted websocket connection, e.g. `"ws://localhost:8000"`. The endpoint can be either a full NSID string like `"com.atproto.sync.subscribeRepos"`, or one of the defined symbol shortcuts - you will almost always want to pass `:subscribe_repos` here.
43
+ The server name can be just a hostname, or a full URL with a `ws:` or `wss:` scheme, which is useful if you want to use a non-encrypted websocket connection, e.g. `"ws://localhost:8000"`. The endpoint can be either a full NSID string like `"com.atproto.sync.subscribeRepos"`, or one of the defined symbol shortcuts you will almost always want to pass `:subscribe_repos` here.
42
44
 
43
45
  Next, set up event listeners to handle incoming messages and get notified of errors. Here are all the available listeners (you will need at least either `on_message` or `on_raw_message`):
44
46
 
@@ -60,7 +62,7 @@ sky.on_timeout { puts "Connection stalled, triggering a reconnect..." }
60
62
  sky.on_error { |e| puts "ERROR: #{e}" }
61
63
  ```
62
64
 
63
- You can also call these as setters accepting a `Proc` - e.g. to disable default error handling, you can do:
65
+ You can also call these as setters accepting a `Proc` e.g. to disable default error handling, you can do:
64
66
 
65
67
  ```rb
66
68
  sky.on_error = nil
@@ -94,7 +96,7 @@ sky.connect
94
96
 
95
97
  ### Cursors
96
98
 
97
- ATProto websocket endpoints implement a "*cursor*" feature to help you make sure that you don't miss anything if your connection is down for a bit (because of a network issue, server restart, deploy etc.). Each message includes a `seq` field, which is the sequence number of the event. You can keep track of the last seq you've seen, and when you reconnect, you pass that number as a cursor parameter - the server will then "replay" all events you might have missed since that last one. (The `bsky.network` Relay firehose currently has a buffer of about 72 hours, though that's not something required by specification.)
99
+ ATProto websocket endpoints implement a "*cursor*" feature to help you make sure that you don't miss anything if your connection is down for a bit (because of a network issue, server restart, deploy etc.). Each message includes a `seq` field, which is the sequence number of the event. You can keep track of the last seq you've seen, and when you reconnect, you pass that number as a cursor parameter the server will then "replay" all events you might have missed since that last one. (The `bsky.network` Relay firehose currently has a buffer of about 72 hours, though that's not something required by specification.)
98
100
 
99
101
  To use a cursor when connecting to the firehose, pass it as the third parameter to `Skyfall::Firehose`. You should then regularly save the `seq` of the last event to some permanent storage, and then load it from there when reconnecting.
100
102
 
@@ -127,57 +129,57 @@ end
127
129
 
128
130
  Each message passed to `on_message` is an instance of a subclass of either `Skyfall::Firehose::Message` or `Skyfall::Jetstream::Message`, depending on the selected source. The supported message types are:
129
131
 
130
- - `CommitMessage` (`#commit`) - represents a change in a user's repo; most messages are of this type
131
- - `IdentityMessage` (`#identity`) - notifies about a change in user's DID document, e.g. a handle change or a migration to a new PDS
132
- - `AccountMessage` (`#account`) - notifies about a change of an account's status (de/activation, suspension, deletion)
133
- - `SyncMessage` (`#sync`) - updates repository state, can be used to trigger account resynchronization
134
- - `LabelsMessage` (`#labels`) - only used in `subscribe_labels` endpoint
135
- - `InfoMessage` (`#info`) - a protocol error message, e.g. about an invalid cursor parameter
132
+ - `CommitMessage` (`#commit`) represents a change in a user's repo; most messages are of this type
133
+ - `IdentityMessage` (`#identity`) notifies about a change in user's DID document, e.g. a handle change or a migration to a new PDS
134
+ - `AccountMessage` (`#account`) notifies about a change of an account's status (de/activation, suspension, deletion)
135
+ - `SyncMessage` (`#sync`) updates repository state, can be used to trigger account resynchronization
136
+ - `LabelsMessage` (`#labels`) only used in `subscribe_labels` endpoint
137
+ - `InfoMessage` (`#info`) a protocol error message, e.g. about an invalid cursor parameter
136
138
  - `UnknownMessage` is used for other unrecognized message types
137
139
 
138
140
  `Skyfall::Firehose::Message` and `Skyfall::Jetstream::Message` variants of message classes should have more or less the same interface, except when a given field is not included in one of the formats.
139
141
 
140
142
  All message objects have the following shared properties:
141
143
 
142
- - `type` (symbol) - the message type identifier, e.g. `:commit`
143
- - `seq` (integer) - a sequential index of the message; Jetstream messages instead have a `time_us` value, which is a Unix timestamp in microseconds (also aliased as `seq` for compatibility)
144
- - `repo` or `did` (string) - DID of the repository (user account)
145
- - `time` (Time) - timestamp of the described action
144
+ - `type` (symbol) the message type identifier, e.g. `:commit`
145
+ - `seq` (integer) a sequential index of the message; Jetstream messages instead have a `time_us` value, which is a Unix timestamp in microseconds (also aliased as `seq` for compatibility)
146
+ - `repo` or `did` (string) DID of the repository (user account)
147
+ - `time` (Time) timestamp of the described action
146
148
 
147
149
  All properties except `type` may be nil for some message types that aren't related to a specific user, like `#info`.
148
150
 
149
151
  Commit messages additionally have:
150
152
 
151
- - `commit` - CID of the commit
152
- - `operations` - list of operations (usually one)
153
+ - `commit` CID of the commit
154
+ - `operations` list of operations (usually one)
153
155
 
154
156
  Handle and Identity messages additionally have:
155
157
 
156
- - `handle` - the new handle assigned to the DID
158
+ - `handle` the new handle assigned to the DID
157
159
 
158
160
  Account messages additionally have:
159
161
 
160
- - `active?` - whether the account is active, or inactive for any reason
161
- - `status` - if not active, shows the status of the account (`:deactivated`, `:deleted`, `:takendown`)
162
+ - `active?` whether the account is active, or inactive for any reason
163
+ - `status` if not active, shows the status of the account (`:deactivated`, `:deleted`, `:takendown`)
162
164
 
163
165
  Info messages additionally have:
164
166
 
165
- - `name` - identifier of the message/error
166
- - `message` - a human-readable description
167
+ - `name` identifier of the message/error
168
+ - `message` a human-readable description
167
169
 
168
170
 
169
171
  ### Commit operations
170
172
 
171
173
  Operations are objects of type `Skyfall::Firehose::Operation` or `Skyfall::Jetstream::Operation` and have such properties:
172
174
 
173
- - `repo` or `did` (string) - DID of the repository (user account)
174
- - `collection` (string) - name of the relevant collection in the repository, e.g. `app.bsky.feed.post` for posts
175
- - `type` (symbol) - short name of the collection, e.g. `:bsky_post`
176
- - `rkey` (string) - identifier of a record in a collection
177
- - `path` (string) - the path part of the at:// URI - collection name + ID (rkey) of the item
178
- - `uri` (string) - the complete at:// URI
179
- - `action` (symbol) - `:create`, `:update` or `:delete`
180
- - `cid` (CID) - CID of the operation/record (`nil` for delete operations)
175
+ - `repo` or `did` (string) DID of the repository (user account)
176
+ - `collection` (string) name of the relevant collection in the repository, e.g. `app.bsky.feed.post` for posts
177
+ - `type` (symbol) short name of the collection, e.g. `:bsky_post`
178
+ - `rkey` (string) identifier of a record in a collection
179
+ - `path` (string) the path part of the at:// URI collection name + ID (rkey) of the item
180
+ - `uri` (string) the complete at:// URI
181
+ - `action` (symbol) `:create`, `:update` or `:delete`
182
+ - `cid` (CID) CID of the operation/record (`nil` for delete operations)
181
183
 
182
184
  Create and update operations will also have an attached record (JSON object) with details of the post, like etc. The record data is currently available as a Ruby hash via `raw_record` property (custom types will be added in future).
183
185
 
@@ -206,7 +208,7 @@ Note that the `Operation` objects have two properties that tell you the kind of
206
208
 
207
209
  When Skyfall receives a message about a record type that's not on the list, whether in the `app.bsky` namespace or not, the operation `type` will be `:unknown`, while the `collection` will be the original string. So if an app like e.g. "Skygram" appears with a `zz.skygram.*` namespace that lets you share photos on ATProto, the operations will have a type `:unknown` and collection names like `zz.skygram.feed.photo`, and you can check the `collection` field for record types known to you and process them in some appropriate way, even if Skyfall doesn't recognize the record type.
208
210
 
209
- Do not however check if such operations have a `type` equal to `:unknown` first - just ignore the type and only check the `collection` string. The reason is that some next version of Skyfall might start recognizing those records and add a new `type` value for them like e.g. `:skygram_photo`, and then they won't match your condition anymore.
211
+ Do not however check if such operations have a `type` equal to `:unknown` first just ignore the type and only check the `collection` string. The reason is that some next version of Skyfall might start recognizing those records and add a new `type` value for them like e.g. `:skygram_photo`, and then they won't match your condition anymore.
210
212
 
211
213
 
212
214
  ## Reconnection logic
@@ -260,7 +262,7 @@ sky.on_message do |msg|
260
262
  end
261
263
  ```
262
264
 
263
- See [ATProto label docs](https://atproto.com/specs/label) for info on what fields are included with each label - `Skyfall::Label` includes properties with these original names, and also more friendly aliases for each (e.g. `value` instead of `val`).
265
+ See [ATProto label docs](https://atproto.com/specs/label) for info on what fields are included with each label `Skyfall::Label` includes properties with these original names, and also more friendly aliases for each (e.g. `value` instead of `val`).
264
266
 
265
267
 
266
268
  ## Other configuration
@@ -302,6 +304,13 @@ See [Jetstream docs](https://github.com/bluesky-social/jetstream?tab=readme-ov-f
302
304
  > The `compress` and `requireHello` options (and zstd compression) are not available at the moment. Also the "subscriber sourced messages" aren't implemented yet.
303
305
 
304
306
 
307
+ ## Other resources
308
+
309
+ - [YARD API documentation](https://rubydoc.info/gems/skyfall) at rubydoc.info
310
+ - [ruby.sdk.blue](https://ruby.sdk.blue)
311
+ - [Example scripts](https://ruby.sdk.blue/examples/)
312
+ - [bluesky-feeds-rb](https://tangled.org/mackuba.eu/bluesky-feeds-rb) – feed generator template project
313
+
305
314
  ## Credits
306
315
 
307
316
  Copyright © 2026 Kuba Suder ([@mackuba.eu](https://bsky.app/profile/did:plc:oio4hkxaop4ao4wz2pp3f4cr)).
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../car_archive'
4
- require_relative '../cid'
5
3
  require_relative '../firehose'
6
4
  require_relative 'message'
7
5
  require_relative 'operation'
6
+ require 'oxygene'
8
7
 
9
8
  module Skyfall
10
9
 
@@ -39,19 +38,21 @@ module Skyfall
39
38
  @data_object['since']
40
39
  end
41
40
 
42
- # @return [CID, nil] CID (Content Identifier) of data of the previous commit in the repo
41
+ # @return [Oxygene::CID, nil] CID (Content Identifier) of data of the previous commit in the repo
43
42
  def prev_data
44
- @prev_data ||= CID.from_cbor_tag(@data_object['prevData'])
43
+ @prev_data ||= Oxygene::CID.from_cbor_tag(@data_object['prevData'])
45
44
  end
46
45
 
47
- # @return [CID] CID (Content Identifier) of the commit
46
+ # @return [Oxygene::CID] CID (Content Identifier) of the commit
48
47
  def commit
49
- @commit ||= CID.from_cbor_tag(@data_object['commit'])
48
+ @commit ||= Oxygene::CID.from_cbor_tag(@data_object['commit'])
50
49
  end
51
50
 
52
- # @return [Skyfall::CarArchive] commit data in the form of a parsed CAR archive
51
+ alias cid commit
52
+
53
+ # @return [Oxygene::CARArchive] commit data in the form of a parsed CAR archive
53
54
  def blocks
54
- @blocks ||= CarArchive.new(@data_object['blocks'])
55
+ @blocks ||= Oxygene::CARArchive.new(@data_object['blocks'])
55
56
  end
56
57
 
57
58
  # @return [Array<Firehose::Operation>] record operations (usually one) included in the commit
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../errors'
4
- require_relative '../extensions'
5
4
  require_relative '../firehose'
6
5
 
7
6
  require 'cbor'
7
+ require 'oxygene'
8
8
  require 'time'
9
9
 
10
10
  module Skyfall
@@ -24,7 +24,7 @@ module Skyfall
24
24
  # the {Skyfall::Stream#on_raw_message} event handler.
25
25
 
26
26
  class Firehose::Message
27
- using Skyfall::Extensions
27
+ using Oxygene::Extensions
28
28
 
29
29
  # Type of the message (e.g. `:commit`, `:identity` etc.)
30
30
  # @return [Symbol]
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative '../collection'
4
4
  require_relative '../firehose'
5
+ require 'oxygene'
5
6
 
6
7
  module Skyfall
7
8
 
@@ -73,9 +74,9 @@ module Skyfall
73
74
  "at://#{repo}/#{@json['path']}"
74
75
  end
75
76
 
76
- # @return [CID, nil] CID (Content Identifier) of the record (nil for delete operations)
77
+ # @return [Oxygene::CID, nil] CID (Content Identifier) of the record (nil for delete operations)
77
78
  def cid
78
- @cid ||= @json['cid'] && CID.from_cbor_tag(@json['cid'])
79
+ @cid ||= @json['cid'] && Oxygene::CID.from_cbor_tag(@json['cid'])
79
80
  end
80
81
 
81
82
  # @return [Hash, nil] record data as a plain Ruby Hash (nil for delete operations)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../car_archive'
4
3
  require_relative '../firehose'
5
4
  require_relative 'message'
5
+ require 'oxygene'
6
6
 
7
7
  module Skyfall
8
8
 
@@ -32,9 +32,9 @@ module Skyfall
32
32
  @rev ||= @data_object['rev']
33
33
  end
34
34
 
35
- # @return [Skyfall::CarArchive] commit data in the form of a parsed CAR archive
35
+ # @return [Oxygene::CARArchive] commit data in the form of a parsed CAR archive
36
36
  def blocks
37
- @blocks ||= CarArchive.new(@data_object['blocks'])
37
+ @blocks ||= Oxygene::CARArchive.new(@data_object['blocks'])
38
38
  end
39
39
  end
40
40
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative '../collection'
4
4
  require_relative '../jetstream'
5
+ require 'oxygene'
5
6
 
6
7
  module Skyfall
7
8
 
@@ -73,9 +74,9 @@ module Skyfall
73
74
  "at://#{repo}/#{collection}/#{rkey}"
74
75
  end
75
76
 
76
- # @return [CID, nil] CID (Content Identifier) of the record (nil for delete operations)
77
+ # @return [Oxygene::CID, nil] CID (Content Identifier) of the record (nil for delete operations)
77
78
  def cid
78
- @cid ||= @json['cid'] && CID.from_json(@json['cid'])
79
+ @cid ||= @json['cid'] && Oxygene::CID.from_json(@json['cid'])
79
80
  end
80
81
 
81
82
  # @return [Hash, nil] record data as a plain Ruby Hash (nil for delete operations)
data/lib/skyfall/label.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'errors'
4
+ require 'oxygene'
4
5
  require 'time'
5
6
 
6
7
  module Skyfall
@@ -60,9 +61,9 @@ module Skyfall
60
61
  @data['uri']
61
62
  end
62
63
 
63
- # @return [CID, nil] CID of the specific version of the subject that this label applies to
64
+ # @return [Oxygene::CID, nil] CID of the specific version of the subject that this label applies to
64
65
  def cid
65
- @cid ||= @data['cid'] && CID.from_json(@data['cid'])
66
+ @cid ||= @data['cid'] && Oxygene::CID.from_json(@data['cid'])
66
67
  end
67
68
 
68
69
  # @return [String] label value
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Skyfall
4
- VERSION = "0.7.0"
4
+ VERSION = "0.7.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skyfall
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kuba Suder
@@ -10,99 +10,60 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: base32
13
+ name: eventmachine
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.3'
18
+ version: '1.2'
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: 0.3.4
21
+ version: 1.2.7
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - "~>"
27
27
  - !ruby/object:Gem::Version
28
- version: '0.3'
28
+ version: '1.2'
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
- version: 0.3.4
32
- - !ruby/object:Gem::Dependency
33
- name: base64
34
- requirement: !ruby/object:Gem::Requirement
35
- requirements:
36
- - - "~>"
37
- - !ruby/object:Gem::Version
38
- version: '0.1'
39
- type: :runtime
40
- prerelease: false
41
- version_requirements: !ruby/object:Gem::Requirement
42
- requirements:
43
- - - "~>"
44
- - !ruby/object:Gem::Version
45
- version: '0.1'
31
+ version: 1.2.7
46
32
  - !ruby/object:Gem::Dependency
47
- name: cbor
33
+ name: faye-websocket
48
34
  requirement: !ruby/object:Gem::Requirement
49
35
  requirements:
50
36
  - - "~>"
51
37
  - !ruby/object:Gem::Version
52
- version: '0.5'
53
- - - ">="
54
- - !ruby/object:Gem::Version
55
- version: 0.5.9.6
38
+ version: '0.12'
56
39
  type: :runtime
57
40
  prerelease: false
58
41
  version_requirements: !ruby/object:Gem::Requirement
59
42
  requirements:
60
43
  - - "~>"
61
44
  - !ruby/object:Gem::Version
62
- version: '0.5'
63
- - - ">="
64
- - !ruby/object:Gem::Version
65
- version: 0.5.9.6
45
+ version: '0.12'
66
46
  - !ruby/object:Gem::Dependency
67
- name: eventmachine
47
+ name: oxygene
68
48
  requirement: !ruby/object:Gem::Requirement
69
49
  requirements:
70
- - - "~>"
71
- - !ruby/object:Gem::Version
72
- version: '1.2'
73
50
  - - ">="
74
51
  - !ruby/object:Gem::Version
75
- version: 1.2.7
52
+ version: 0.0.1
76
53
  type: :runtime
77
54
  prerelease: false
78
55
  version_requirements: !ruby/object:Gem::Requirement
79
56
  requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '1.2'
83
57
  - - ">="
84
58
  - !ruby/object:Gem::Version
85
- version: 1.2.7
86
- - !ruby/object:Gem::Dependency
87
- name: faye-websocket
88
- requirement: !ruby/object:Gem::Requirement
89
- requirements:
90
- - - "~>"
91
- - !ruby/object:Gem::Version
92
- version: '0.12'
93
- type: :runtime
94
- prerelease: false
95
- version_requirements: !ruby/object:Gem::Requirement
96
- requirements:
97
- - - "~>"
98
- - !ruby/object:Gem::Version
99
- version: '0.12'
100
- description: "\n Skyfall is a Ruby library for connecting to the \"firehose\" of
101
- the Bluesky social network, i.e. a websocket which\n streams all new posts and
102
- everything else happening on the Bluesky network in real time. The code connects
103
- to the\n websocket endpoint, decodes the messages which are encoded in some binary
104
- formats, and returns the data as Ruby\n objects, which you can filter and save
105
- to some kind of database (e.g. in order to create a custom feed).\n "
59
+ version: 0.0.1
60
+ description: "\n Skyfall is a Ruby gem for connecting to the \"firehose\" of the
61
+ AT Protocol network that Bluesky is built on,\n i.e. a websocket which streams
62
+ all new posts and everything else happening on the network in real time. The code\n
63
+ \ connects to the websocket endpoint, decodes the messages which are encoded in
64
+ some binary formats, and returns\n the data as Ruby objects and hashes, which
65
+ you can filter and save to some kind of database (e.g. in order\n to create a
66
+ custom feed).\n "
106
67
  email:
107
68
  - jakub.suder@gmail.com
108
69
  executables: []
@@ -113,12 +74,9 @@ files:
113
74
  - LICENSE.txt
114
75
  - README.md
115
76
  - lib/skyfall.rb
116
- - lib/skyfall/car_archive.rb
117
- - lib/skyfall/cid.rb
118
77
  - lib/skyfall/collection.rb
119
78
  - lib/skyfall/errors.rb
120
79
  - lib/skyfall/events.rb
121
- - lib/skyfall/extensions.rb
122
80
  - lib/skyfall/firehose.rb
123
81
  - lib/skyfall/firehose/account_message.rb
124
82
  - lib/skyfall/firehose/commit_message.rb
@@ -161,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
119
  - !ruby/object:Gem::Version
162
120
  version: '0'
163
121
  requirements: []
164
- rubygems_version: 4.0.3
122
+ rubygems_version: 3.6.9
165
123
  specification_version: 4
166
124
  summary: A Ruby gem for streaming data from the Bluesky/ATProto firehose
167
125
  test_files: []
@@ -1,158 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'cid'
4
- require_relative 'errors'
5
- require_relative 'extensions'
6
-
7
- require 'base64'
8
- require 'cbor'
9
- require 'stringio'
10
-
11
- # CAR v1: https://ipld.io/specs/transport/car/carv1/
12
- # multicodec codes: https://github.com/multiformats/multicodec/blob/master/table.csv
13
-
14
- module Skyfall
15
- class CarSection
16
- attr_reader :cid
17
-
18
- def initialize(cid, body_data)
19
- @cid = cid
20
- @body_data = body_data
21
- end
22
-
23
- def body
24
- @body ||= CarArchive.convert_data(CBOR.decode(@body_data))
25
- end
26
- end
27
-
28
- class CarArchive
29
- using Skyfall::Extensions
30
-
31
- attr_reader :roots, :sections
32
-
33
- def initialize(data)
34
- @sections = []
35
- @buffer = StringIO.new(data)
36
-
37
- read_header(@buffer)
38
- end
39
-
40
- def section_with_cid(cid)
41
- if section = @sections.detect { |s| s.cid == cid }
42
- return section.body
43
- end
44
-
45
- if @buffer
46
- while !@buffer.eof?
47
- section = read_section(@buffer)
48
- return section.body if section.cid == cid
49
- end
50
- end
51
-
52
- @buffer = nil
53
- nil
54
- end
55
-
56
- def sections
57
- if @buffer
58
- read_section(@buffer) while !@buffer.eof?
59
- @buffer = nil
60
- end
61
-
62
- @sections
63
- end
64
-
65
- def self.convert_data(object)
66
- if object.is_a?(Hash)
67
- object.each do |k, v|
68
- if v.is_a?(Hash) || v.is_a?(Array)
69
- convert_data(v)
70
- elsif v.is_a?(CBOR::Tagged)
71
- object[k] = make_cid_link(v)
72
- elsif v.is_a?(String) && v.encoding == Encoding::ASCII_8BIT
73
- object[k] = make_bytes(v)
74
- end
75
- end
76
- elsif object.is_a?(Array)
77
- object.each_with_index do |v, i|
78
- if v.is_a?(Hash) || v.is_a?(Array)
79
- convert_data(v)
80
- elsif v.is_a?(CBOR::Tagged)
81
- object[i] = make_cid_link(v)
82
- elsif v.is_a?(String) && v.encoding == Encoding::ASCII_8BIT
83
- object[i] = make_bytes(v)
84
- end
85
- end
86
- else
87
- raise DecodeError, "Unexpected value type in record: #{object}"
88
- end
89
- end
90
-
91
- def self.make_cid_link(cid)
92
- { '$link' => CID.from_cbor_tag(cid) }
93
- end
94
-
95
- def self.make_bytes(data)
96
- { '$bytes' => Base64.encode64(data).chomp.gsub(/=+$/, '') }
97
- end
98
-
99
- def inspect
100
- vars = instance_variables.map { |v|
101
- if v == :@sections && @buffer
102
- "#{v}=[...]"
103
- else
104
- "#{v}=#{instance_variable_get(v).inspect}"
105
- end
106
- }
107
-
108
- "#<#{self.class}:0x#{object_id} #{vars.join(", ")}>"
109
- end
110
-
111
- private
112
-
113
- def read_header(buffer)
114
- len = buffer.read_varint
115
-
116
- header_data = buffer.read(len)
117
- raise DecodeError.new("Header too short: #{header_data}") unless header_data.length == len
118
-
119
- header = CBOR.decode(header_data)
120
- raise UnsupportedError.new("Unexpected CAR version: #{header['version']}") unless header['version'] == 1
121
- @roots = header['roots'].map { |x| CID.from_cbor_tag(x) }
122
- end
123
-
124
- def read_section(buffer)
125
- len = buffer.read_varint
126
-
127
- section_data = buffer.read(len)
128
- raise DecodeError.new("Section too short: #{section_data}") unless section_data.length == len
129
-
130
- sbuffer = StringIO.new(section_data)
131
-
132
- version = sbuffer.read_varint
133
- raise UnsupportedError.new("Unexpected CID version: #{version}") unless version == 1
134
-
135
- codec = sbuffer.read_varint
136
- raise UnsupportedError.new("Unexpected CID codec: #{codec}") unless codec == 0x71 # dag-cbor
137
-
138
- hash = sbuffer.read_varint
139
- raise UnsupportedError.new("Unexpected CID hash: #{hash}") unless hash == 0x12 # sha2-256
140
-
141
- clen = sbuffer.read_varint
142
- raise UnsupportedError.new("Unexpected CID length: #{clen}") unless clen == 32
143
-
144
- prefix = section_data[0...sbuffer.pos]
145
-
146
- cid_data = sbuffer.read(clen)
147
- raise DecodeError.new("CID too short: #{cid_data}") unless cid_data.length == clen
148
-
149
- cid = CID.new(prefix + cid_data)
150
-
151
- body_data = sbuffer.read
152
- new_section = CarSection.new(cid, body_data)
153
-
154
- @sections << new_section
155
- new_section
156
- end
157
- end
158
- end
data/lib/skyfall/cid.rb DELETED
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'errors'
4
-
5
- require 'base32'
6
-
7
- # CIDs in DAG-CBOR: https://ipld.io/specs/codecs/dag-cbor/spec/
8
- # CIDs in JSON: https://ipld.io/specs/codecs/dag-json/spec/
9
- # multibase: https://github.com/multiformats/multibase
10
-
11
- module Skyfall
12
- class CID
13
- attr_reader :data
14
-
15
- def self.from_cbor_tag(tag)
16
- data = tag.value
17
- raise DecodeError.new("Unexpected first byte of CID: #{data[0]}") unless data[0] == "\x00"
18
- CID.new(data[1..-1])
19
- end
20
-
21
- def self.from_json(string)
22
- raise DecodeError.new("Unexpected CID length") unless string.length == 59
23
- raise DecodeError.new("Unexpected CID prefix") unless string[0] == 'b'
24
-
25
- data = Base32.decode(string[1..-1].upcase)
26
- CID.new(data)
27
- end
28
-
29
- def initialize(data)
30
- @data = data
31
- end
32
-
33
- def to_s
34
- 'b' + Base32.encode(@data).downcase.gsub(/=+$/, '')
35
- end
36
-
37
- def inspect
38
- "CID(\"#{to_s}\")"
39
- end
40
-
41
- def ==(other)
42
- other.is_a?(CID) && @data == other.data
43
- end
44
- end
45
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cbor'
4
- require 'stringio'
5
-
6
- module Skyfall
7
-
8
- # @private
9
- module Extensions
10
-
11
- refine StringIO do
12
- # https://en.wikipedia.org/wiki/LEB128
13
- def read_varint
14
- shift = 1
15
- value = 0
16
-
17
- loop do
18
- byte = self.readbyte
19
- value += byte % 128 * shift
20
- break if byte < 128
21
- shift *= 128
22
- end
23
-
24
- value
25
- end
26
- end
27
-
28
- refine CBOR.singleton_class do
29
- def decode_sequence(data)
30
- unpacker = CBOR::Unpacker.new(StringIO.new(data))
31
- unpacker.each.to_a
32
- end
33
- end
34
-
35
- end
36
- end