urbit-api 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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