skyfall 0.6.1 → 0.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -10
- data/README.md +1 -4
- data/lib/skyfall/car_archive.rb +42 -6
- data/lib/skyfall/cid.rb +2 -0
- data/lib/skyfall/collection.rb +20 -0
- data/lib/skyfall/errors.rb +50 -5
- data/lib/skyfall/events.rb +19 -0
- data/lib/skyfall/extensions.rb +4 -0
- data/lib/skyfall/firehose/account_message.rb +32 -4
- data/lib/skyfall/firehose/commit_message.rb +45 -5
- data/lib/skyfall/firehose/identity_message.rb +30 -2
- data/lib/skyfall/firehose/info_message.rb +35 -1
- data/lib/skyfall/firehose/labels_message.rb +28 -10
- data/lib/skyfall/firehose/message.rb +133 -24
- data/lib/skyfall/firehose/operation.rb +57 -5
- data/lib/skyfall/firehose/sync_message.rb +31 -0
- data/lib/skyfall/firehose/unknown_message.rb +8 -0
- data/lib/skyfall/firehose.rb +103 -5
- data/lib/skyfall/jetstream/account_message.rb +21 -1
- data/lib/skyfall/jetstream/commit_message.rb +35 -2
- data/lib/skyfall/jetstream/identity_message.rb +22 -1
- data/lib/skyfall/jetstream/message.rb +94 -7
- data/lib/skyfall/jetstream/operation.rb +56 -4
- data/lib/skyfall/jetstream/unknown_message.rb +8 -0
- data/lib/skyfall/jetstream.rb +94 -7
- data/lib/skyfall/label.rb +33 -0
- data/lib/skyfall/stream.rb +264 -47
- data/lib/skyfall/version.rb +1 -1
- metadata +2 -3
- data/lib/skyfall/firehose/handle_message.rb +0 -14
- data/lib/skyfall/firehose/tombstone_message.rb +0 -11
|
@@ -1,22 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../errors'
|
|
2
4
|
require_relative '../jetstream'
|
|
3
5
|
|
|
4
6
|
require 'time'
|
|
5
7
|
|
|
6
8
|
module Skyfall
|
|
9
|
+
|
|
10
|
+
# @abstract
|
|
11
|
+
# Abstract base class representing a Jetstream message.
|
|
12
|
+
#
|
|
13
|
+
# Actual messages are returned as instances of one of the subclasses of this class,
|
|
14
|
+
# depending on the type of message, most commonly as {Skyfall::Jetstream::CommitMessage}.
|
|
15
|
+
#
|
|
16
|
+
# The {new} method is overridden here so that it can be called with a JSON message from
|
|
17
|
+
# the websocket, and it parses the type from the JSON and builds an instance of a matching
|
|
18
|
+
# subclass.
|
|
19
|
+
#
|
|
20
|
+
# You normally don't need to call this class directly, unless you're building a custom
|
|
21
|
+
# subclass of {Skyfall::Stream} or reading raw data packets from the websocket through
|
|
22
|
+
# the {Skyfall::Stream#on_raw_message} event handler.
|
|
23
|
+
|
|
7
24
|
class Jetstream::Message
|
|
8
|
-
require_relative 'account_message'
|
|
9
|
-
require_relative 'commit_message'
|
|
10
|
-
require_relative 'identity_message'
|
|
11
|
-
require_relative 'unknown_message'
|
|
12
25
|
|
|
13
|
-
|
|
26
|
+
# Type of the message (e.g. `:commit`, `:identity` etc.)
|
|
27
|
+
# @return [Symbol]
|
|
28
|
+
attr_reader :type
|
|
29
|
+
|
|
30
|
+
# DID of the account (repo) that the event is sent by
|
|
31
|
+
# @return [String]
|
|
32
|
+
attr_reader :did
|
|
33
|
+
|
|
34
|
+
# Server timestamp of the message (in Unix time microseconds), which serves as a cursor
|
|
35
|
+
# when reconnecting; an equivalent of {Skyfall::Firehose::Message#seq} in CBOR firehose
|
|
36
|
+
# messages.
|
|
37
|
+
# @return [Integer]
|
|
38
|
+
attr_reader :time_us
|
|
39
|
+
|
|
14
40
|
alias repo did
|
|
15
41
|
alias seq time_us
|
|
42
|
+
alias kind type
|
|
16
43
|
|
|
17
|
-
#
|
|
44
|
+
# The raw JSON of the message as parsed from the websocket packet.
|
|
18
45
|
attr_reader :json
|
|
19
46
|
|
|
47
|
+
#
|
|
48
|
+
# Parses the JSON data from a websocket message and returns an instance of an appropriate subclass.
|
|
49
|
+
#
|
|
50
|
+
# {Skyfall::Jetstream::UnknownMessage} is returned if the message type is not recognized.
|
|
51
|
+
#
|
|
52
|
+
# @param data [String] plain text payload of a Jetstream websocket message
|
|
53
|
+
# @return [Skyfall::Jetstream::Message]
|
|
54
|
+
# @raise [DecodeError] if the message doesn't include required data
|
|
55
|
+
#
|
|
20
56
|
def self.new(data)
|
|
21
57
|
json = JSON.parse(data)
|
|
22
58
|
|
|
@@ -27,28 +63,79 @@ module Skyfall
|
|
|
27
63
|
else Jetstream::UnknownMessage
|
|
28
64
|
end
|
|
29
65
|
|
|
66
|
+
if self != Jetstream::Message && self != message_class
|
|
67
|
+
expected_type = self.name.split('::').last.gsub(/Message$/, '').downcase
|
|
68
|
+
raise DecodeError, "Expected '#{expected_type}' message, got '#{json['kind']}'"
|
|
69
|
+
end
|
|
70
|
+
|
|
30
71
|
message = message_class.allocate
|
|
31
72
|
message.send(:initialize, json)
|
|
32
73
|
message
|
|
33
74
|
end
|
|
34
75
|
|
|
76
|
+
#
|
|
77
|
+
# @param json [Hash] message JSON decoded from the websocket message
|
|
78
|
+
# @raise [DecodeError] if the message doesn't include required data
|
|
79
|
+
#
|
|
35
80
|
def initialize(json)
|
|
81
|
+
%w(kind did time_us).each { |f| raise DecodeError.new("Missing event details (#{f})") if json[f].nil? }
|
|
82
|
+
|
|
36
83
|
@json = json
|
|
37
84
|
@type = @json['kind'].to_sym
|
|
38
85
|
@did = @json['did']
|
|
39
86
|
@time_us = @json['time_us']
|
|
40
87
|
end
|
|
41
88
|
|
|
89
|
+
#
|
|
90
|
+
# @return [Boolean] true if the message is {Jetstream::UnknownMessage} (of unrecognized type)
|
|
91
|
+
#
|
|
42
92
|
def unknown?
|
|
43
93
|
self.is_a?(Jetstream::UnknownMessage)
|
|
44
94
|
end
|
|
45
95
|
|
|
96
|
+
# Returns a record operation included in the message. Only `:commit` messages include
|
|
97
|
+
# operations, but for convenience the method is declared here and returns nil in other messages.
|
|
98
|
+
#
|
|
99
|
+
# @return [nil]
|
|
100
|
+
#
|
|
101
|
+
def operation
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
alias op operation
|
|
106
|
+
|
|
107
|
+
# List of operations on records included in the message. Only `:commit` messages include
|
|
108
|
+
# operations, but for convenience the method is declared here and returns an empty array
|
|
109
|
+
# in other messages.
|
|
110
|
+
#
|
|
111
|
+
# @return [Array<Jetstream::Operation>]
|
|
112
|
+
#
|
|
46
113
|
def operations
|
|
47
114
|
[]
|
|
48
115
|
end
|
|
49
116
|
|
|
117
|
+
#
|
|
118
|
+
# Timestamp decoded from the message.
|
|
119
|
+
#
|
|
120
|
+
# Note: the time is read from the {#time_us} field, which stores the event time as an integer in
|
|
121
|
+
# Unix time microseconds, and which is used as an equivalent of {Skyfall::Firehose::Message#seq}
|
|
122
|
+
# in CBOR firehose messages. This timestamp represents the time when the message was received
|
|
123
|
+
# and stored by Jetstream, which might differ a lot from the `created_at` time saved in the
|
|
124
|
+
# record data, e.g. if user's local time is set incorrectly or if an archive of existing posts
|
|
125
|
+
# was imported from another platform. It will also differ (usually only slightly) from the
|
|
126
|
+
# timestamp of the original CBOR message emitted from the PDS and passed through the relay.
|
|
127
|
+
#
|
|
128
|
+
# @return [Time]
|
|
129
|
+
#
|
|
50
130
|
def time
|
|
51
|
-
@time ||=
|
|
131
|
+
@time ||= Time.at(@time_us / 1_000_000.0)
|
|
52
132
|
end
|
|
53
133
|
end
|
|
54
134
|
end
|
|
135
|
+
|
|
136
|
+
# need to be at the end because of a circular dependency
|
|
137
|
+
|
|
138
|
+
require_relative 'account_message'
|
|
139
|
+
require_relative 'commit_message'
|
|
140
|
+
require_relative 'identity_message'
|
|
141
|
+
require_relative 'unknown_message'
|
|
@@ -1,58 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../collection'
|
|
2
4
|
require_relative '../jetstream'
|
|
3
5
|
|
|
4
6
|
module Skyfall
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# A single record operation from a Jetstream commit event. An operation is a new record being
|
|
10
|
+
# created, or an existing record modified or deleted. It includes the URI and other details of
|
|
11
|
+
# the record in question, type of the action taken, and record data for "created" and "update"
|
|
12
|
+
# actions.
|
|
13
|
+
#
|
|
14
|
+
# Note: when a record is deleted, the previous record data is *not* included in the commit, only
|
|
15
|
+
# its URI. This means that if you're tracking records which are referencing other records, e.g.
|
|
16
|
+
# follow, block, or like records, you need to store information about this referencing record
|
|
17
|
+
# including an URI or rkey, because in case of a delete, you will not get information about which
|
|
18
|
+
# post was unliked or which account was unfollowed, only which like/follow record was deleted.
|
|
19
|
+
#
|
|
20
|
+
# At the moment, Skyfall doesn't parse the record data into any rich models specific for a given
|
|
21
|
+
# record type with a convenient API, but simply returns them as `Hash` objects (see {#raw_record}).
|
|
22
|
+
# In the future, a second `#record` method might be added which returns a parsed record model.
|
|
23
|
+
#
|
|
24
|
+
|
|
5
25
|
class Jetstream::Operation
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# @param message [Skyfall::Jetstream::Message] commit message the operation is parsed from
|
|
29
|
+
# @param json [Hash] operation data
|
|
30
|
+
#
|
|
6
31
|
def initialize(message, json)
|
|
7
32
|
@message = message
|
|
8
33
|
@json = json
|
|
9
34
|
end
|
|
10
35
|
|
|
36
|
+
# @return [String] DID of the account/repository in which the operation happened
|
|
11
37
|
def repo
|
|
12
38
|
@message.repo
|
|
13
39
|
end
|
|
14
40
|
|
|
15
41
|
alias did repo
|
|
16
42
|
|
|
43
|
+
# @return [String] path part of the record URI (collection + rkey)
|
|
44
|
+
# @deprecated Use {#collection} + {#rkey}
|
|
17
45
|
def path
|
|
46
|
+
@@path_warning_printed ||= false
|
|
47
|
+
|
|
48
|
+
unless @@path_warning_printed
|
|
49
|
+
$stderr.puts "Warning: Skyfall::Jetstream::Operation#path is deprecated - use #collection + #rkey"
|
|
50
|
+
@@path_warning_printed = true
|
|
51
|
+
end
|
|
52
|
+
|
|
18
53
|
@json['collection'] + '/' + @json['rkey']
|
|
19
54
|
end
|
|
20
55
|
|
|
56
|
+
# @return [Symbol] type of the operation (`:create`, `:update` or `:delete`)
|
|
21
57
|
def action
|
|
22
58
|
@json['operation'].to_sym
|
|
23
59
|
end
|
|
24
60
|
|
|
61
|
+
# @return [String] record collection NSID
|
|
25
62
|
def collection
|
|
26
63
|
@json['collection']
|
|
27
64
|
end
|
|
28
65
|
|
|
66
|
+
# @return [String] record rkey
|
|
29
67
|
def rkey
|
|
30
68
|
@json['rkey']
|
|
31
69
|
end
|
|
32
70
|
|
|
71
|
+
# @return [String] full AT URI of the record
|
|
33
72
|
def uri
|
|
34
73
|
"at://#{repo}/#{collection}/#{rkey}"
|
|
35
74
|
end
|
|
36
75
|
|
|
76
|
+
# @return [CID, nil] CID (Content Identifier) of the record (nil for delete operations)
|
|
37
77
|
def cid
|
|
38
78
|
@cid ||= @json['cid'] && CID.from_json(@json['cid'])
|
|
39
79
|
end
|
|
40
80
|
|
|
81
|
+
# @return [Hash, nil] record data as a plain Ruby Hash (nil for delete operations)
|
|
41
82
|
def raw_record
|
|
42
83
|
@json['record']
|
|
43
84
|
end
|
|
44
85
|
|
|
86
|
+
# Symbol short code of the collection, like `:bsky_post`. If the collection NSID is not
|
|
87
|
+
# recognized, the type is `:unknown`. The full NSID is always available through the
|
|
88
|
+
# `#collection` property.
|
|
89
|
+
#
|
|
90
|
+
# @return [Symbol]
|
|
91
|
+
# @see Skyfall::Collection
|
|
92
|
+
#
|
|
45
93
|
def type
|
|
46
94
|
Collection.short_code(collection)
|
|
47
95
|
end
|
|
48
96
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
end
|
|
52
|
-
|
|
97
|
+
# Returns a string with a representation of the object for debugging purposes.
|
|
98
|
+
# @return [String]
|
|
53
99
|
def inspect
|
|
54
100
|
vars = inspectable_variables.map { |v| "#{v}=#{instance_variable_get(v).inspect}" }.join(", ")
|
|
55
101
|
"#<#{self.class}:0x#{object_id} #{vars}>"
|
|
56
102
|
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def inspectable_variables
|
|
107
|
+
instance_variables - [:@message]
|
|
108
|
+
end
|
|
57
109
|
end
|
|
58
110
|
end
|
data/lib/skyfall/jetstream.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'stream'
|
|
2
4
|
|
|
3
5
|
require 'json'
|
|
@@ -5,9 +7,78 @@ require 'time'
|
|
|
5
7
|
require 'uri'
|
|
6
8
|
|
|
7
9
|
module Skyfall
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# Client of a Jetstream service (JSON-based firehose).
|
|
13
|
+
#
|
|
14
|
+
# This is an equivalent of {Skyfall::Firehose} for Jetstream sources, mirroring its API.
|
|
15
|
+
# It returns messages as instances of subclasses of {Skyfall::Jetstream::Message}, which
|
|
16
|
+
# are generally equivalent to the respective {Skyfall::Firehose::Message} variants as much
|
|
17
|
+
# as possible.
|
|
18
|
+
#
|
|
19
|
+
# To connect to a Jetstream websocket, you need to:
|
|
20
|
+
#
|
|
21
|
+
# * create an instance of Jetstream, passing it the hostname/URL of the server, and optionally
|
|
22
|
+
# parameters such as cursor or collection/DID filters
|
|
23
|
+
# * set up callbacks to be run when connecting, disconnecting, when a message is received etc.
|
|
24
|
+
# (you need to set at least a message handler)
|
|
25
|
+
# * call {#connect} to start the connection
|
|
26
|
+
# * handle the received messages
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
# client = Skyfall::Jetstream.new('jetstream2.us-east.bsky.network', {
|
|
30
|
+
# wanted_collections: 'app.bsky.feed.post',
|
|
31
|
+
# wanted_dids: @dids
|
|
32
|
+
# })
|
|
33
|
+
#
|
|
34
|
+
# client.on_message do |msg|
|
|
35
|
+
# next unless msg.type == :commit
|
|
36
|
+
#
|
|
37
|
+
# op = msg.operation
|
|
38
|
+
#
|
|
39
|
+
# if op.type == :bsky_post && op.action == :create
|
|
40
|
+
# puts "[#{msg.time}] #{msg.repo}: #{op.raw_record['text']}"
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# client.connect
|
|
45
|
+
#
|
|
46
|
+
# # You might also want to set some or all of these lifecycle callback handlers:
|
|
47
|
+
#
|
|
48
|
+
# client.on_connecting { |url| puts "Connecting to #{url}..." }
|
|
49
|
+
# client.on_connect { puts "Connected" }
|
|
50
|
+
# client.on_disconnect { puts "Disconnected" }
|
|
51
|
+
# client.on_reconnect { puts "Connection lost, trying to reconnect..." }
|
|
52
|
+
# client.on_timeout { puts "Connection stalled, triggering a reconnect..." }
|
|
53
|
+
# client.on_error { |e| puts "ERROR: #{e}" }
|
|
54
|
+
#
|
|
55
|
+
# @note Most of the methods of this class that you might want to use are defined in {Skyfall::Stream}.
|
|
56
|
+
#
|
|
57
|
+
|
|
8
58
|
class Jetstream < Stream
|
|
59
|
+
|
|
60
|
+
# Current cursor (time of the last seen message)
|
|
61
|
+
# @return [Integer, nil]
|
|
9
62
|
attr_accessor :cursor
|
|
10
63
|
|
|
64
|
+
#
|
|
65
|
+
# @param server [String] Address of the server to connect to.
|
|
66
|
+
# Expects a string with either just a hostname, or a ws:// or wss:// URL with no path.
|
|
67
|
+
# @param params [Hash] options, see below:
|
|
68
|
+
#
|
|
69
|
+
# @option params [Integer] :cursor
|
|
70
|
+
# cursor from which to resume
|
|
71
|
+
#
|
|
72
|
+
# @option params [Array<String>] :wanted_dids
|
|
73
|
+
# DID filter to pass to the server (`:wantedDids` is also accepted);
|
|
74
|
+
# value should be a DID string or an array of those
|
|
75
|
+
#
|
|
76
|
+
# @option params [Array<String, Symbol>] :wanted_collections
|
|
77
|
+
# collection filter to pass to the server (`:wantedCollections` is also accepted);
|
|
78
|
+
# value should be an NSID string or a symbol shorthand, or an array of those
|
|
79
|
+
#
|
|
80
|
+
# @raise [ArgumentError] if the server parameter or the options are invalid
|
|
81
|
+
#
|
|
11
82
|
def initialize(server, params = {})
|
|
12
83
|
require_relative 'jetstream/message'
|
|
13
84
|
super(server)
|
|
@@ -17,6 +88,28 @@ module Skyfall
|
|
|
17
88
|
@root_url = ensure_empty_path(@root_url)
|
|
18
89
|
end
|
|
19
90
|
|
|
91
|
+
|
|
92
|
+
protected
|
|
93
|
+
|
|
94
|
+
# Returns the full URL of the websocket endpoint to connect to.
|
|
95
|
+
# @return [String]
|
|
96
|
+
|
|
97
|
+
def build_websocket_url
|
|
98
|
+
params = @cursor ? @params.merge(cursor: @cursor) : @params
|
|
99
|
+
query = URI.encode_www_form(params)
|
|
100
|
+
|
|
101
|
+
@root_url + "/subscribe" + (query.length > 0 ? "?#{query}" : '')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Processes a single message received from the websocket. Passes the received data to the
|
|
105
|
+
# {#on_raw_message} handler, builds a {Skyfall::Jetstream::Message} object, and passes it to
|
|
106
|
+
# the {#on_message} handler (if defined). Also updates the {#cursor} to this message's
|
|
107
|
+
# microsecond timestamp (note: this is skipped if {#on_message} is not set).
|
|
108
|
+
#
|
|
109
|
+
# @param msg
|
|
110
|
+
# {https://rubydoc.info/gems/faye-websocket/Faye/WebSocket/API/MessageEvent Faye::WebSocket::API::MessageEvent}
|
|
111
|
+
# @return [nil]
|
|
112
|
+
|
|
20
113
|
def handle_message(msg)
|
|
21
114
|
data = msg.data
|
|
22
115
|
@handlers[:raw_message]&.call(data)
|
|
@@ -30,14 +123,8 @@ module Skyfall
|
|
|
30
123
|
end
|
|
31
124
|
end
|
|
32
125
|
|
|
33
|
-
private
|
|
34
126
|
|
|
35
|
-
|
|
36
|
-
params = @cursor ? @params.merge(cursor: @cursor) : @params
|
|
37
|
-
query = URI.encode_www_form(params)
|
|
38
|
-
|
|
39
|
-
@root_url + "/subscribe" + (query.length > 0 ? "?#{query}" : '')
|
|
40
|
-
end
|
|
127
|
+
private
|
|
41
128
|
|
|
42
129
|
def check_params(params)
|
|
43
130
|
params ||= {}
|
data/lib/skyfall/label.rb
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'errors'
|
|
2
4
|
require 'time'
|
|
3
5
|
|
|
4
6
|
module Skyfall
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# A single label emitted from the "subscribeLabels" firehose of a labeller service.
|
|
10
|
+
#
|
|
11
|
+
# The label assigns some specific value - from a list of available values defined by this
|
|
12
|
+
# labeller - to a specific target (at:// URI or a DID). In general, this will usually be either
|
|
13
|
+
# a "badge" that a user requested to be assigned to themselves from a fun/informative labeller,
|
|
14
|
+
# or some kind of (likely negative) label assigned to a user or post by a moderation labeller.
|
|
15
|
+
#
|
|
16
|
+
# You generally don't need to create instances of this class manually, but will receive them
|
|
17
|
+
# from {Skyfall::Firehose} that's connected to `:subscribe_labels` in the {Stream#on_message}
|
|
18
|
+
# callback handler (wrapped in a {Skyfall::Firehose::LabelsMessage}).
|
|
19
|
+
#
|
|
20
|
+
|
|
5
21
|
class Label
|
|
22
|
+
|
|
23
|
+
# @return [Hash] the label's JSON data
|
|
6
24
|
attr_reader :data
|
|
7
25
|
|
|
26
|
+
#
|
|
27
|
+
# @param data [Hash] raw label JSON
|
|
28
|
+
# @raise [Skyfall::DecodeError] if the data has an invalid format
|
|
29
|
+
# @raise [Skyfall::UnsupportedError] if the label is in an unsupported future version
|
|
30
|
+
#
|
|
8
31
|
def initialize(data)
|
|
9
32
|
@data = data
|
|
10
33
|
|
|
@@ -20,34 +43,44 @@ module Skyfall
|
|
|
20
43
|
raise DecodeError.new("Invalid uri: #{uri}") unless uri.start_with?('at://') || uri.start_with?('did:')
|
|
21
44
|
end
|
|
22
45
|
|
|
46
|
+
# @return [Integer] label format version number
|
|
23
47
|
def version
|
|
24
48
|
@data['ver']
|
|
25
49
|
end
|
|
26
50
|
|
|
51
|
+
# DID of the labelling authority (the labeller service).
|
|
52
|
+
# @return [String]
|
|
27
53
|
def authority
|
|
28
54
|
@data['src']
|
|
29
55
|
end
|
|
30
56
|
|
|
57
|
+
# AT URI or DID of the labelled subject (e.g. a user or post).
|
|
58
|
+
# @return [String]
|
|
31
59
|
def subject
|
|
32
60
|
@data['uri']
|
|
33
61
|
end
|
|
34
62
|
|
|
63
|
+
# @return [CID, nil] CID of the specific version of the subject that this label applies to
|
|
35
64
|
def cid
|
|
36
65
|
@cid ||= @data['cid'] && CID.from_json(@data['cid'])
|
|
37
66
|
end
|
|
38
67
|
|
|
68
|
+
# @return [String] label value
|
|
39
69
|
def value
|
|
40
70
|
@data['val']
|
|
41
71
|
end
|
|
42
72
|
|
|
73
|
+
# @return [Boolean] if true, then this is a negation (delete) of an existing label
|
|
43
74
|
def negation?
|
|
44
75
|
!!@data['neg']
|
|
45
76
|
end
|
|
46
77
|
|
|
78
|
+
# @return [Time] timestamp when the label was created
|
|
47
79
|
def created_at
|
|
48
80
|
@created_at ||= Time.parse(@data['cts'])
|
|
49
81
|
end
|
|
50
82
|
|
|
83
|
+
# @return [Time, nil] optional timestamp when the label expires
|
|
51
84
|
def expires_at
|
|
52
85
|
@expires_at ||= @data['exp'] && Time.parse(@data['exp'])
|
|
53
86
|
end
|