meshchat 0.5.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 +7 -0
- data/CHANGELOG.md +1 -0
- data/README.md +7 -0
- data/lib/meshchat.rb +73 -0
- data/lib/meshchat/cli.rb +164 -0
- data/lib/meshchat/cli/command.rb +63 -0
- data/lib/meshchat/cli/config.rb +30 -0
- data/lib/meshchat/cli/exit.rb +9 -0
- data/lib/meshchat/cli/identity.rb +9 -0
- data/lib/meshchat/cli/import.rb +37 -0
- data/lib/meshchat/cli/init.rb +34 -0
- data/lib/meshchat/cli/input.rb +60 -0
- data/lib/meshchat/cli/irb.rb +18 -0
- data/lib/meshchat/cli/listen.rb +9 -0
- data/lib/meshchat/cli/ping.rb +61 -0
- data/lib/meshchat/cli/ping_all.rb +11 -0
- data/lib/meshchat/cli/server.rb +16 -0
- data/lib/meshchat/cli/share.rb +9 -0
- data/lib/meshchat/cli/stop_listening.rb +9 -0
- data/lib/meshchat/cli/whisper.rb +34 -0
- data/lib/meshchat/cli/who.rb +9 -0
- data/lib/meshchat/config/hash_file.rb +75 -0
- data/lib/meshchat/config/settings.rb +112 -0
- data/lib/meshchat/database.rb +30 -0
- data/lib/meshchat/display.rb +32 -0
- data/lib/meshchat/display/base.rb +53 -0
- data/lib/meshchat/display/manager.rb +51 -0
- data/lib/meshchat/encryption.rb +27 -0
- data/lib/meshchat/encryption/aes_rsa.rb +65 -0
- data/lib/meshchat/encryption/passthrough.rb +17 -0
- data/lib/meshchat/instance.rb +34 -0
- data/lib/meshchat/message.rb +38 -0
- data/lib/meshchat/message/base.rb +93 -0
- data/lib/meshchat/message/chat.rb +14 -0
- data/lib/meshchat/message/disconnection.rb +13 -0
- data/lib/meshchat/message/node_list.rb +41 -0
- data/lib/meshchat/message/node_list_diff.rb +15 -0
- data/lib/meshchat/message/node_list_hash.rb +29 -0
- data/lib/meshchat/message/ping.rb +32 -0
- data/lib/meshchat/message/ping_reply.rb +9 -0
- data/lib/meshchat/message/relay.rb +43 -0
- data/lib/meshchat/message/whisper.rb +36 -0
- data/lib/meshchat/models/entry.rb +104 -0
- data/lib/meshchat/net/client.rb +60 -0
- data/lib/meshchat/net/listener/request.rb +48 -0
- data/lib/meshchat/net/listener/request_processor.rb +45 -0
- data/lib/meshchat/net/listener/server.rb +61 -0
- data/lib/meshchat/net/request.rb +29 -0
- data/lib/meshchat/notifier/base.rb +50 -0
- data/lib/meshchat/version.rb +3 -0
- metadata +288 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
module MeshChat
|
2
|
+
class CLI
|
3
|
+
class Input
|
4
|
+
WHISPER = '@'
|
5
|
+
COMMAND = '/'
|
6
|
+
attr_accessor :_input
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def create(input)
|
10
|
+
klass =
|
11
|
+
if is_command(input)
|
12
|
+
CLI::Command
|
13
|
+
elsif is_whisper?(input)
|
14
|
+
CLI::Whisper
|
15
|
+
else
|
16
|
+
# TODO: maybe change this to a chat command?
|
17
|
+
CLI::Input
|
18
|
+
end
|
19
|
+
|
20
|
+
klass.new(input)
|
21
|
+
end
|
22
|
+
|
23
|
+
def is_command(input)
|
24
|
+
input[0, 1] == COMMAND
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_whisper?(input)
|
28
|
+
input[0, 1] == WHISPER
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(input)
|
33
|
+
self._input = input.chomp
|
34
|
+
end
|
35
|
+
|
36
|
+
def handle
|
37
|
+
return if _input.empty?
|
38
|
+
|
39
|
+
servers = Node.online
|
40
|
+
if !servers.empty?
|
41
|
+
m = Message::Chat.new(
|
42
|
+
message: _input
|
43
|
+
)
|
44
|
+
|
45
|
+
Display.chat m.display
|
46
|
+
|
47
|
+
# if sending to all, iterate thorugh list of
|
48
|
+
# servers, and send to each one
|
49
|
+
# TODO: do this async so that one server doesn't block
|
50
|
+
# the rest of the servers from receiving the messages
|
51
|
+
servers.each do |entry|
|
52
|
+
Net::Client.send(node: entry, message: m)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
Display.warning 'you have no servers'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module MeshChat
|
2
|
+
class CLI
|
3
|
+
# TODO: only include this and awesome_print when booted with
|
4
|
+
# debug=true in the config
|
5
|
+
class IRB < CLI::Command
|
6
|
+
def handle
|
7
|
+
begin
|
8
|
+
code = command_args[1..command_args.length].join(' ')
|
9
|
+
ap eval(code)
|
10
|
+
''
|
11
|
+
rescue => e
|
12
|
+
ap e.message
|
13
|
+
ap e.backtrace
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module MeshChat
|
2
|
+
class CLI
|
3
|
+
class Ping < CLI::Command
|
4
|
+
def handle
|
5
|
+
if command_valid?
|
6
|
+
msg = Message::Ping.new
|
7
|
+
|
8
|
+
field, value = parse_ping_command
|
9
|
+
|
10
|
+
node =
|
11
|
+
if field == 'location'
|
12
|
+
Node.find_by_location(lookup_value)
|
13
|
+
else
|
14
|
+
Node.find_by_alias_name(lookup_value)
|
15
|
+
end
|
16
|
+
|
17
|
+
location = node.try(:location)
|
18
|
+
|
19
|
+
unless location
|
20
|
+
return Display.alert "#{lookup_value} could not be found"
|
21
|
+
end
|
22
|
+
|
23
|
+
Net::Client.send(
|
24
|
+
node: node,
|
25
|
+
message: msg
|
26
|
+
)
|
27
|
+
else
|
28
|
+
Display.alert usage
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def usage
|
33
|
+
'Usage: /ping {field} {value} e.g.: /ping alias neurotek or /ping location 10.10.10.10:8080'
|
34
|
+
end
|
35
|
+
|
36
|
+
def lookup_field
|
37
|
+
sub_command
|
38
|
+
end
|
39
|
+
|
40
|
+
def lookup_value
|
41
|
+
value = command_args.last
|
42
|
+
value if value != sub_command
|
43
|
+
end
|
44
|
+
|
45
|
+
def command_valid?
|
46
|
+
parse_ping_command.compact.length == 2
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_ping_command
|
50
|
+
@parsed_args ||=
|
51
|
+
if lookup_field == 'location' || lookup_field == 'alias'
|
52
|
+
[lookup_field, lookup_value]
|
53
|
+
elsif lookup_field =~ /(?:[0-9]{1,3}\.){3}[0-9]{1,3}/
|
54
|
+
['location', lookup_field]
|
55
|
+
else
|
56
|
+
['alias', lookup_field]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module MeshChat
|
2
|
+
class CLI
|
3
|
+
class Server < CLI::Command
|
4
|
+
ONLINE = 'online'
|
5
|
+
|
6
|
+
def handle
|
7
|
+
case sub_command
|
8
|
+
when ONLINE
|
9
|
+
Display.info Node.online.map(&:as_info).join(', ') || 'no one is online'
|
10
|
+
else
|
11
|
+
Display.info Node.all.map(&:as_info).join(', ') || 'there are no nodes'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module MeshChat
|
2
|
+
class CLI
|
3
|
+
class Whisper < CLI::Command
|
4
|
+
def target
|
5
|
+
# get first arg
|
6
|
+
command
|
7
|
+
end
|
8
|
+
|
9
|
+
def message
|
10
|
+
command_args[1..command_args.length].try(:join, ' ')
|
11
|
+
end
|
12
|
+
|
13
|
+
def handle
|
14
|
+
node = Node.find_by_alias_name(target)
|
15
|
+
|
16
|
+
if node
|
17
|
+
m = Message::Whisper.new(
|
18
|
+
message: message,
|
19
|
+
to: target
|
20
|
+
)
|
21
|
+
|
22
|
+
Display.whisper m.display
|
23
|
+
|
24
|
+
Net::Client.send(
|
25
|
+
node: node,
|
26
|
+
message: m
|
27
|
+
)
|
28
|
+
else
|
29
|
+
Display.alert "node for #{target} not found or is not online"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module MeshChat
|
2
|
+
module Config
|
3
|
+
class HashFile
|
4
|
+
attr_accessor :_hash
|
5
|
+
|
6
|
+
DEFAULT_SETTINGS = {}
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
self._hash = default_settings
|
10
|
+
exists? ? load : save
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
_hash[key.to_s]
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, value)
|
18
|
+
_hash[key.to_s] = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def load
|
22
|
+
f = read_file
|
23
|
+
begin
|
24
|
+
self._hash = JSON.parse(f)
|
25
|
+
rescue Exception => e
|
26
|
+
Display.alert e.message
|
27
|
+
self._hash = default_settings
|
28
|
+
Display.warning 'writing defaults...'
|
29
|
+
save
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def read_file
|
34
|
+
File.read(filename)
|
35
|
+
end
|
36
|
+
|
37
|
+
def display
|
38
|
+
_hash.inspect
|
39
|
+
end
|
40
|
+
|
41
|
+
def as_hash
|
42
|
+
_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def save
|
46
|
+
# backup
|
47
|
+
exists = exists?
|
48
|
+
File.rename(filename, filename + '.bak') if exists
|
49
|
+
# write
|
50
|
+
File.open(filename, 'w') { |f| f.syswrite(_hash.to_json) }
|
51
|
+
# remove backup
|
52
|
+
File.delete(filename + '.bak') if exists
|
53
|
+
end
|
54
|
+
|
55
|
+
def set(key, with: value)
|
56
|
+
self[key] = with
|
57
|
+
save
|
58
|
+
"#{key} set to #{with}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def exists?
|
62
|
+
File.exist?(filename)
|
63
|
+
end
|
64
|
+
|
65
|
+
def filename
|
66
|
+
return @filename if @filename
|
67
|
+
fail 'filename must be set'
|
68
|
+
end
|
69
|
+
|
70
|
+
def default_settings
|
71
|
+
@default_settings ? @default_settings : DEFAULT_SETTINGS
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module MeshChat
|
2
|
+
module Config
|
3
|
+
class Settings < HashFile
|
4
|
+
FILENAME = 'settings.json'
|
5
|
+
|
6
|
+
DEFAULT_SETTINGS = {
|
7
|
+
'alias' => 'alias',
|
8
|
+
'port' => '2008',
|
9
|
+
'ip' => 'localhost',
|
10
|
+
'uid' => '1',
|
11
|
+
'publicKey' => 'replace this'
|
12
|
+
}
|
13
|
+
|
14
|
+
class << self
|
15
|
+
delegate :valid?, :errors,
|
16
|
+
:[], :[]=, :display, :as_hash, :save, :set,
|
17
|
+
:location, :identity, :keys_exist?, :public_key,
|
18
|
+
:private_key, :generate_keys, :share, :key_pair,
|
19
|
+
:uid_exists?, :generate_uid, :debug?, :identity_as_json,
|
20
|
+
to: :instance
|
21
|
+
|
22
|
+
def instance
|
23
|
+
@instance ||= new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def debug?
|
28
|
+
['true', '1', 'yes', 'y', 't'].include?(self['debug'].try(:downcase))
|
29
|
+
end
|
30
|
+
|
31
|
+
def identity_as_json
|
32
|
+
{
|
33
|
+
'alias' => self['alias'],
|
34
|
+
'location' => location,
|
35
|
+
'uid' => self['uid'],
|
36
|
+
'publicKey' => public_key
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def share
|
41
|
+
data = identity_as_json.to_json
|
42
|
+
|
43
|
+
filename = "#{identity_as_json['alias']}.json"
|
44
|
+
File.open(filename, 'w'){ |f| f.syswrite(data) }
|
45
|
+
Display.info "#{filename} written..."
|
46
|
+
end
|
47
|
+
|
48
|
+
def keys_exist?
|
49
|
+
public_key.present? && private_key.present?
|
50
|
+
end
|
51
|
+
|
52
|
+
def uid_exists?
|
53
|
+
self['uid'].present?
|
54
|
+
end
|
55
|
+
|
56
|
+
def public_key
|
57
|
+
self['publicKey']
|
58
|
+
end
|
59
|
+
|
60
|
+
def private_key
|
61
|
+
self['privatKey']
|
62
|
+
end
|
63
|
+
|
64
|
+
# generates 32 bytes
|
65
|
+
def generate_uid
|
66
|
+
self['uid'] = SecureRandom.hex(32)
|
67
|
+
Display.success 'new uid set'
|
68
|
+
end
|
69
|
+
|
70
|
+
def generate_keys
|
71
|
+
@key_pair = OpenSSL::PKey::RSA.new(2048)
|
72
|
+
self['publicKey'] = @key_pair.public_key.to_pem
|
73
|
+
self['privateKey'] = @key_pair.to_pem
|
74
|
+
Display.success 'new keys generated'
|
75
|
+
end
|
76
|
+
|
77
|
+
def key_pair
|
78
|
+
if !@key_pair && keys_exist?
|
79
|
+
@key_pair = OpenSSL::PKey::RSA.new(self['privateKey'] + self['publicKey'])
|
80
|
+
end
|
81
|
+
@key_pair
|
82
|
+
end
|
83
|
+
|
84
|
+
def identity
|
85
|
+
"#{self['alias']}@#{location}##{self['uid']}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def location
|
89
|
+
"#{self['ip']}:#{self['port']}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def initialize
|
93
|
+
@default_settings = DEFAULT_SETTINGS
|
94
|
+
@filename = FILENAME
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
def valid?
|
99
|
+
errors.empty?
|
100
|
+
end
|
101
|
+
|
102
|
+
def errors
|
103
|
+
messages = []
|
104
|
+
messages << 'must have an alias' if !self['alias']
|
105
|
+
messages << 'must have ip set' if !self['ip']
|
106
|
+
messages << 'must have port set' if !self['port']
|
107
|
+
messages << 'must have uid set' if !self['uid']
|
108
|
+
messages
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module MeshChat
|
2
|
+
module Database
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# Upon initial startup, instantiated the database
|
6
|
+
# this is used for storing the information of every node
|
7
|
+
# on the network
|
8
|
+
def setup_storage
|
9
|
+
ActiveRecord::Base.establish_connection(
|
10
|
+
adapter: "sqlite3",
|
11
|
+
database: "meshchat.sqlite3",
|
12
|
+
pool: 128
|
13
|
+
)
|
14
|
+
|
15
|
+
ActiveRecord::Migration.suppress_messages do
|
16
|
+
ActiveRecord::Schema.define do
|
17
|
+
unless table_exists? :entries
|
18
|
+
create_table :entries do |table|
|
19
|
+
table.column :alias_name, :string
|
20
|
+
table.column :location, :string
|
21
|
+
table.column :uid, :string
|
22
|
+
table.column :public_key, :string
|
23
|
+
table.column :online, :boolean, default: true, null: false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|