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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -0
  3. data/README.md +7 -0
  4. data/lib/meshchat.rb +73 -0
  5. data/lib/meshchat/cli.rb +164 -0
  6. data/lib/meshchat/cli/command.rb +63 -0
  7. data/lib/meshchat/cli/config.rb +30 -0
  8. data/lib/meshchat/cli/exit.rb +9 -0
  9. data/lib/meshchat/cli/identity.rb +9 -0
  10. data/lib/meshchat/cli/import.rb +37 -0
  11. data/lib/meshchat/cli/init.rb +34 -0
  12. data/lib/meshchat/cli/input.rb +60 -0
  13. data/lib/meshchat/cli/irb.rb +18 -0
  14. data/lib/meshchat/cli/listen.rb +9 -0
  15. data/lib/meshchat/cli/ping.rb +61 -0
  16. data/lib/meshchat/cli/ping_all.rb +11 -0
  17. data/lib/meshchat/cli/server.rb +16 -0
  18. data/lib/meshchat/cli/share.rb +9 -0
  19. data/lib/meshchat/cli/stop_listening.rb +9 -0
  20. data/lib/meshchat/cli/whisper.rb +34 -0
  21. data/lib/meshchat/cli/who.rb +9 -0
  22. data/lib/meshchat/config/hash_file.rb +75 -0
  23. data/lib/meshchat/config/settings.rb +112 -0
  24. data/lib/meshchat/database.rb +30 -0
  25. data/lib/meshchat/display.rb +32 -0
  26. data/lib/meshchat/display/base.rb +53 -0
  27. data/lib/meshchat/display/manager.rb +51 -0
  28. data/lib/meshchat/encryption.rb +27 -0
  29. data/lib/meshchat/encryption/aes_rsa.rb +65 -0
  30. data/lib/meshchat/encryption/passthrough.rb +17 -0
  31. data/lib/meshchat/instance.rb +34 -0
  32. data/lib/meshchat/message.rb +38 -0
  33. data/lib/meshchat/message/base.rb +93 -0
  34. data/lib/meshchat/message/chat.rb +14 -0
  35. data/lib/meshchat/message/disconnection.rb +13 -0
  36. data/lib/meshchat/message/node_list.rb +41 -0
  37. data/lib/meshchat/message/node_list_diff.rb +15 -0
  38. data/lib/meshchat/message/node_list_hash.rb +29 -0
  39. data/lib/meshchat/message/ping.rb +32 -0
  40. data/lib/meshchat/message/ping_reply.rb +9 -0
  41. data/lib/meshchat/message/relay.rb +43 -0
  42. data/lib/meshchat/message/whisper.rb +36 -0
  43. data/lib/meshchat/models/entry.rb +104 -0
  44. data/lib/meshchat/net/client.rb +60 -0
  45. data/lib/meshchat/net/listener/request.rb +48 -0
  46. data/lib/meshchat/net/listener/request_processor.rb +45 -0
  47. data/lib/meshchat/net/listener/server.rb +61 -0
  48. data/lib/meshchat/net/request.rb +29 -0
  49. data/lib/meshchat/notifier/base.rb +50 -0
  50. data/lib/meshchat/version.rb +3 -0
  51. 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,9 @@
1
+ module MeshChat
2
+ class CLI
3
+ class Listen < CLI::Command
4
+ def handle
5
+ CLI.start_server
6
+ end
7
+ end
8
+ end
9
+ 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,11 @@
1
+ module MeshChat
2
+ class CLI
3
+ class PingAll < CLI::Command
4
+ def handle
5
+ Node.all.each do |n|
6
+ Net::Client.send(node: n, message: Message::Ping.new)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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,9 @@
1
+ module MeshChat
2
+ class CLI
3
+ class Share < CLI::Command
4
+ def handle
5
+ Settings.share
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module MeshChat
2
+ class CLI
3
+ class StopListening < CLI::Command
4
+ def handle
5
+ CLI.close_server
6
+ end
7
+ end
8
+ end
9
+ 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,9 @@
1
+ module MeshChat
2
+ class CLI
3
+ class Who < CLI::Command
4
+ def handle
5
+ Display.info Node.online.map(&:as_info) || 'no one is online'
6
+ end
7
+ end
8
+ end
9
+ 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