urbit-api 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Urbit
4
+ class Group
5
+ attr_accessor :manager, :members, :tags
6
+ attr_reader :hidden, :path, :policy
7
+
8
+ def initialize(path:, members:, policy:, tags:, hidden:)
9
+ @hidden = hidden
10
+ @manager = nil
11
+ @members = Set.new(members)
12
+ @path = path
13
+ @policy = policy
14
+ @tags = self.parse_tags(tags)
15
+ end
16
+
17
+ def ==(another_group)
18
+ another_group.path == self.path
19
+ end
20
+
21
+ def <=>(another_group)
22
+ self.path <=> another_group.path
23
+ end
24
+
25
+ #
26
+ # This is the action labeled as "Archive" in the Landscape UI.
27
+ # As of now, you can only do this to groups on your own ship.
28
+ #
29
+ def delete
30
+ if (self.host == self.manager.ship.name)
31
+ spdr = self.manager.spider('group-delete', %Q({"remove": {"ship": "#{self.host}", "name": "#{self.key}"}}))
32
+ self.manager.remove(self) if 200 == spdr[:status]
33
+ return spdr
34
+ end
35
+ {status: 400, code: 'bad_request', body: 'Can only delete Groups on your own ship.'}
36
+ end
37
+
38
+ def eql?(another_group)
39
+ another_group.path == self.path
40
+ end
41
+
42
+ def host
43
+ self.path_tokens[0]
44
+ end
45
+
46
+ def invite(ship_names:, message:)
47
+ data = %Q({
48
+ "invite": {
49
+ "resource": {
50
+ "ship": "#{self.host}",
51
+ "name": "#{self.key}"
52
+ },
53
+ "ships": [
54
+ "#{ship_names.join(',')}"
55
+ ],
56
+ "description": "#{message}"
57
+ }
58
+ })
59
+ self.manager.spider('group-invite', data)
60
+ end
61
+
62
+ def key
63
+ self.path_tokens[1]
64
+ end
65
+
66
+ def leave
67
+ spdr = self.manager.spider('group-leave', %Q({"leave": {"ship": "#{self.host}", "name": "#{self.key}"}}))
68
+ self.manager.remove(self) if 200 == spdr[:status]
69
+ spdr
70
+ end
71
+
72
+ def path_tokens
73
+ self.path.split('/')
74
+ end
75
+
76
+ def pending_invites
77
+ if (i = @policy["invite"])
78
+ if (p = i["pending"])
79
+ return p.count
80
+ end
81
+ end
82
+ '?'
83
+ end
84
+
85
+ def to_h
86
+ {
87
+ host: self.host,
88
+ key: self.key,
89
+ member_count: self.members.count,
90
+ pending_invites: self.pending_invites,
91
+ hidden: self.hidden
92
+ }
93
+ end
94
+
95
+ def to_s
96
+ "a Group(#{self.to_h})"
97
+ end
98
+
99
+ private
100
+
101
+ def parse_tags(tags)
102
+ h = {}
103
+ return h if tags.empty?
104
+ tags.each {|k, v| h[k] = Set.new(v)}
105
+ tags.replace(h)
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Urbit
4
+ class GroupManager
5
+ attr_accessor :groups, :ship
6
+
7
+ def initialize(ship:)
8
+ @ship = ship
9
+ @groups = []
10
+ end
11
+
12
+ #
13
+ # Adds a Group to this Manager's groups collection
14
+ #
15
+ def add(a_group)
16
+ @groups << a_group
17
+ end
18
+
19
+ def add_members(group_path:, ships:)
20
+ if (group = self.find(path: group_path))
21
+ group.members += ships
22
+ end
23
+ end
24
+
25
+ def add_tag(group_path:, ships:, tag:)
26
+ if (group = self.find(path: group_path))
27
+ if (group.tags.include? tag)
28
+ group.tags[tag] += ships
29
+ end
30
+ end
31
+ end
32
+
33
+ def create(name:, title:, description:)
34
+ self.spider('group-create', %Q({"create": {"name": "#{name}", "title": "#{title}", "description": "#{description}", "policy": {"open": {"banRanks": [], "banned": []}}}}))
35
+ end
36
+
37
+ def empty?
38
+ self.groups.empty?
39
+ end
40
+
41
+ #
42
+ # Answers the Group uniquely keyed by path:, if it exists
43
+ #
44
+ def find(path:)
45
+ if (g = self.groups.select {|g| g.path == path}.first)
46
+ g.manager = self
47
+ g
48
+ end
49
+ end
50
+
51
+ def first
52
+ g = self.groups.first
53
+ g.manager = self
54
+ g
55
+ end
56
+
57
+ def join(host:, name:, share_contact: false, auto_join: false)
58
+ data = {join: {resource: {ship: "#{host}", name: "#{name}"}, ship: "#{host}", shareContact: share_contact, app: "groups", autojoin: auto_join}}
59
+ self.ship.poke(app: 'group-view', mark: 'group-view-action', message: data)
60
+ nil
61
+ end
62
+
63
+ def list
64
+ self.groups.map {|g| g.path}.join("\n")
65
+ end
66
+
67
+ def remove(group)
68
+ @groups = self.groups.filter {|g| g != group}
69
+ end
70
+
71
+ def remove_members(group_path:, ships:)
72
+ if (group = self.find(path: group_path))
73
+ group.members -= ships
74
+ end
75
+ end
76
+
77
+ def remove_tag(group_path:, ships:, tag:)
78
+ if (group = self.find(path: group_path))
79
+ if (group.tags.include? tag)
80
+ group.tags[tag] -= ships
81
+ end
82
+ end
83
+ end
84
+
85
+ def load
86
+ if self.ship.logged_in?
87
+ self.ship.subscribe(app: 'group-store', path: '/groups')
88
+ end
89
+ nil
90
+ end
91
+
92
+ def spider(thread, data)
93
+ self.ship.spider(mark_in: 'group-view-action', mark_out: 'json', thread: thread, data: data)
94
+ end
95
+
96
+ def to_s
97
+ self.groups.sort.each {|g| puts g}
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'urbit/parser'
4
+
5
+ module Urbit
6
+
7
+ class GroupParser < Parser
8
+ def resource
9
+ "~#{self.resource_hash["ship"]}/#{self.resource_hash["name"]}"
10
+ end
11
+
12
+ def resource_hash
13
+ @j["resource"]
14
+ end
15
+ end
16
+
17
+ class AddGroupParser < GroupParser
18
+ def group
19
+ Urbit::Group.new(path: self.resource,
20
+ members: [],
21
+ policy: @j["policy"],
22
+ tags: [],
23
+ hidden: @j["hidden"])
24
+ end
25
+ end
26
+
27
+ class ChangeTagParser < GroupParser
28
+ def ships
29
+ @j["ships"]
30
+ end
31
+
32
+ def tag
33
+ @j["tag"]["tag"]
34
+ end
35
+ end
36
+
37
+ class ChangeMemberParser < GroupParser
38
+ def ships
39
+ @j["ships"]
40
+ end
41
+ end
42
+
43
+ class InitialGroupParser < Parser
44
+ def groups
45
+ self.group_hashes.collect {|k, v| Group.new(path: k.sub('/ship/', ''),
46
+ members: v["members"],
47
+ policy: v["policy"],
48
+ tags: v["tags"],
49
+ hidden: v["hidden"])}
50
+ end
51
+
52
+ def group_hashes
53
+ @j
54
+ end
55
+ end
56
+
57
+ class InitialGroupGroupParser < GroupParser
58
+ def group
59
+ Urbit::Group.new(path: self.resource,
60
+ members: self.group_hash["members"],
61
+ policy: self.group_hash["policy"],
62
+ tags: self.group_hash["tags"],
63
+ hidden: self.group_hash["hidden"])
64
+ end
65
+
66
+ def group_hash
67
+ @j["group"]
68
+ end
69
+ end
70
+
71
+ end
data/lib/urbit/message.rb CHANGED
@@ -22,11 +22,11 @@ module Urbit
22
22
  end
23
23
 
24
24
  def channel_url
25
- "#{self.ship.config.api_base_url}/~/channel/#{self.channel.key}"
25
+ "#{self.ship.url}/~/channel/#{self.channel.key}"
26
26
  end
27
27
 
28
28
  def request_body
29
- self.to_a.to_json
29
+ JSON.generate(self.to_a)
30
30
  end
31
31
 
32
32
  def ship
data/lib/urbit/node.rb CHANGED
@@ -11,8 +11,30 @@ module Urbit
11
11
  @index = nil
12
12
  end
13
13
 
14
+ #
15
+ # Given a bigint representing an urbit date, returns a unix timestamp.
16
+ #
17
+ def self.da_to_unix(da)
18
+ # ported from urbit lib.ts which in turn was ported from +time:enjs:format in hoon.hoon
19
+ da_second = 18446744073709551616
20
+ da_unix_epoch = 170141184475152167957503069145530368000
21
+ offset = da_second / 2000
22
+ epoch_adjusted = offset + (da - da_unix_epoch)
23
+ return (epoch_adjusted * 1000) / da_second
24
+ end
25
+
26
+ #
27
+ # Given a unix timestamp, returns a bigint representing an urbit date
28
+ #
29
+ def self.unix_to_da(unix)
30
+ da_second = 18446744073709551616
31
+ da_unix_epoch = 170141184475152167957503069145530368000
32
+ time_since_epoch = (unix * da_second) / 1000
33
+ return da_unix_epoch + time_since_epoch;
34
+ end
35
+
14
36
  def ==(another_node)
15
- another_node.raw_index == self.raw_index
37
+ another_node.index == self.index
16
38
  end
17
39
 
18
40
  def <=>(another_node)
@@ -20,11 +42,16 @@ module Urbit
20
42
  end
21
43
 
22
44
  def eql?(another_node)
23
- another_node.raw_index == self.raw_index
45
+ another_node.index == self.index
46
+ end
47
+
48
+ def deleted?
49
+ # This is a "deleted" node. Not sure what to do yet, but for now don't create a Node.
50
+ @post_h["index"].nil?
24
51
  end
25
52
 
26
53
  def hash
27
- self.raw_index.hash
54
+ self.index.hash
28
55
  end
29
56
 
30
57
  def author
@@ -47,6 +74,10 @@ module Urbit
47
74
  @post_h['contents']
48
75
  end
49
76
 
77
+ def datetime_sent
78
+ Time.at(self.time_sent / 1000).to_datetime
79
+ end
80
+
50
81
  def persistent?
51
82
  @persistent
52
83
  end
@@ -76,9 +107,13 @@ module Urbit
76
107
  end
77
108
 
78
109
  def raw_index
79
- @post_h["index"].delete_prefix('/')
110
+ return @post_h["index"].delete_prefix('/') unless self.deleted?
111
+ (Node.unix_to_da(Time.now.to_i)).to_s
80
112
  end
81
113
 
114
+ #
115
+ # This is the time sent as recorded by urbit in unix extended format.
116
+ #
82
117
  def time_sent
83
118
  @post_h['time-sent']
84
119
  end
@@ -87,13 +122,17 @@ module Urbit
87
122
  {
88
123
  index: self.index,
89
124
  author: self.author,
125
+ sent: self.datetime_sent,
90
126
  contents: self.contents,
91
- time_sent: self.time_sent,
92
127
  is_parent: !self.children.empty?,
93
128
  child_count: self.children.count
94
129
  }
95
130
  end
96
131
 
132
+ def to_pretty_array
133
+ self.to_h.each.map {|k, v| "#{k}#{(' ' * (12 - k.length))}#{v}"}
134
+ end
135
+
97
136
  def to_s
98
137
  "a Node(#{self.to_h})"
99
138
  end
@@ -108,5 +147,6 @@ module Urbit
108
147
  end
109
148
  subatoms.join('/')
110
149
  end
150
+
111
151
  end
112
152
  end
data/lib/urbit/parser.rb CHANGED
@@ -3,9 +3,15 @@ require 'urbit/node'
3
3
 
4
4
  module Urbit
5
5
  class Parser
6
+ def initialize(with_json:)
7
+ @j = with_json
8
+ end
9
+ end
10
+
11
+ class GraphParser < Parser
6
12
  def initialize(for_graph:, with_json:)
13
+ super(with_json: with_json)
7
14
  @g = for_graph
8
- @j = with_json
9
15
  end
10
16
 
11
17
  #
@@ -33,16 +39,30 @@ module Urbit
33
39
  end
34
40
  end
35
41
 
36
- class AddGraphParser < Parser
42
+ class AddGraphParser < GraphParser
37
43
  def nodes_hash
38
44
  @j["graph"]
39
45
  end
40
46
 
47
+ def resource_hash
48
+ @j["resource"]
49
+ end
41
50
  end
42
51
 
43
- class AddNodesParser < Parser
52
+ class AddNodesParser < GraphParser
44
53
  def nodes_hash
45
54
  @j["nodes"]
46
55
  end
47
56
  end
57
+
58
+ class RemoveGraphParser < GraphParser
59
+ def nodes_hash
60
+ nil
61
+ end
62
+
63
+ def resource_hash
64
+ @j["resource"]
65
+ end
66
+ end
67
+
48
68
  end
@@ -1,7 +1,7 @@
1
1
  module Urbit
2
2
  class PokeMessage < Message
3
- def initialize(channel:, app:, mark:, a_string:)
4
- super(channel: channel, app: app, mark: mark, contents: a_string)
3
+ def initialize(channel:, app:, mark:, a_message_hash:)
4
+ super(channel: channel, app: app, mark: mark, contents: a_message_hash)
5
5
  end
6
6
  end
7
7
  end
@@ -1,19 +1,19 @@
1
1
  require 'ld-eventsource'
2
+ require "logger"
2
3
 
3
4
  require 'urbit/ack_message'
4
5
  require 'urbit/fact'
5
6
 
6
7
  module Urbit
7
8
  class Receiver < SSE::Client
8
- attr_accessor :facts
9
+ attr_accessor :errors, :facts
9
10
 
10
11
  def initialize(channel:)
11
- @facts = []
12
- super(channel.url, {headers: self.headers(channel)}) do |rec|
12
+ super(channel.url, headers: self.headers(channel), logger: self.default_logger) do |rec|
13
13
  # We are now listening on a socket for SSE::Events. This block will be called for each one.
14
14
  rec.on_event do |event|
15
15
  # Wrap the returned event in a Fact.
16
- @facts << (f = Fact.new(channel: channel, event: event))
16
+ @facts << (f = Fact.collect(channel: channel, event: event))
17
17
 
18
18
  # We need to acknowlege each message or urbit will eventually disconnect us.
19
19
  # We record the ack with the Fact itself.
@@ -22,12 +22,22 @@ module Urbit
22
22
  end
23
23
 
24
24
  rec.on_error do |error|
25
- self.facts += ["I received an error fact: #{error.class}"]
25
+ self.errors << ["I received an error fact: #{error.class}"]
26
26
  end
27
27
  end
28
+
29
+ @errors = []
30
+ @facts = []
28
31
  @is_open = true
29
32
  end
30
33
 
34
+ def default_logger
35
+ log = ::Logger.new($stdout)
36
+ log.level = ::Logger::WARN
37
+ log.progname = 'ld-eventsource'
38
+ log
39
+ end
40
+
31
41
  def open?
32
42
  @is_open
33
43
  end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'urbit/bucket'
4
+
5
+ module Urbit
6
+ class Setting
7
+ attr_accessor :buckets
8
+ attr_reader :desk, :ship
9
+
10
+ def initialize(ship:, desk:, buckets:)
11
+ @ship = ship
12
+ @desk = desk
13
+ @buckets = Set.new
14
+ buckets.each {|k, v| @buckets << Bucket.new(setting: self, name: k, entries: v)}
15
+ end
16
+
17
+ def ==(another_group)
18
+ another_setting.desk == self.desk
19
+ end
20
+
21
+ def <=>(another_group)
22
+ self.desk <=> another_group.desk
23
+ end
24
+
25
+ def [](bucket:)
26
+ self.buckets.select {|b| bucket == b.name}.first
27
+ end
28
+
29
+ def add_bucket(name:, entries:)
30
+ msg = {
31
+ "put-bucket": {
32
+ "bucket-key": "#{name}",
33
+ "desk": "#{self.desk}",
34
+ "bucket": entries
35
+ }
36
+ }
37
+ self.ship.poke(app: 'settings-store', mark: 'settings-event', message: msg)
38
+ nil
39
+ end
40
+
41
+ def entries(bucket:)
42
+ self[bucket: bucket].entries
43
+ end
44
+
45
+ def remove_bucket(name:)
46
+ msg = {
47
+ "del-bucket": {
48
+ "bucket-key": "#{name}",
49
+ "desk": "#{self.desk}"
50
+ }
51
+ }
52
+ self.ship.poke(app: 'settings-store', mark: 'settings-event', message: msg)
53
+ nil
54
+ end
55
+
56
+ def to_h
57
+ {
58
+ desk: @desk,
59
+ buckets: self.buckets,
60
+ }
61
+ end
62
+
63
+ def to_s
64
+ "a Setting(#{self.to_h})"
65
+ end
66
+
67
+ def to_string
68
+ "desk: #{self.desk}\n buckets: #{self.buckets.collect {|b| b.to_string}}"
69
+ end
70
+ end
71
+
72
+ class Settings < Set
73
+ class << self
74
+ def load(ship:)
75
+ ship.subscribe(app: 'settings-store', path: '/all')
76
+ scry = ship.scry(app: "settings-store", path: "/all", mark: "json")
77
+ # scry = self.scry(app: "settings-store", path: "/desk/#{desk}", mark: "json")
78
+ s = Settings.new
79
+ if scry[:body]
80
+ body = JSON.parse scry[:body]
81
+ body["all"].each do |k, v| # At this level the keys are the desks and the values are the buckets
82
+ s << Setting.new(ship: ship, desk: k, buckets: v)
83
+ end
84
+ end
85
+ s
86
+ end
87
+ end
88
+
89
+ def initialize
90
+ @hash = {}
91
+ end
92
+
93
+ def [](desk:)
94
+ self.select {|s| desk == s.desk}.first
95
+ end
96
+
97
+ def list
98
+ self.each {|s| puts s.to_string}
99
+ nil
100
+ end
101
+ end
102
+
103
+ end