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