meshchat 0.5.0

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