del 0.1.16 → 0.1.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,51 +1,80 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Del
4
+ # This is used to contain all configuration.
2
5
  class Configuration
3
- SOCKET_FILE="/tmp/del.sock"
4
- attr_accessor :default_rooms
5
- attr_accessor :host
6
- attr_accessor :jid
7
- attr_accessor :jid
8
- attr_accessor :logger
9
- attr_accessor :muc_domain
10
- attr_accessor :name
11
- attr_accessor :password
6
+ SOCKET_FILE = '/tmp/del.sock'
12
7
  attr_accessor :router
13
8
  attr_accessor :users
14
- attr_accessor :socket_file
9
+ attr_writer :default_rooms
10
+ attr_writer :host
11
+ attr_writer :jid
12
+ attr_writer :logger
13
+ attr_writer :muc_domain
14
+ attr_writer :name
15
+ attr_writer :password
16
+ attr_writer :socket_file
15
17
 
16
18
  def initialize(settings = {})
17
- @default_rooms = settings.fetch(:rooms, [])
18
- @host = settings.fetch(:host, 'chat.hipchat.com')
19
- @jid = settings.fetch(:jid)
20
- @logger = Logger.new(STDOUT)
21
- @logger.level = settings.fetch(:log_level, Logger::INFO).to_i
22
- @muc_domain = settings.fetch(:muc_domain, "conf.hipchat.com")
23
- @name = settings.fetch(:full_name)
24
- @password = settings.fetch(:password)
19
+ @settings = settings
25
20
  @router = DefaultRouter.new
26
- @socket_file = settings.fetch(:socket_file, SOCKET_FILE)
27
- @users = Repository.new
21
+ @users = Repository.new(mapper: User)
22
+ end
23
+
24
+ def jid
25
+ @jid ||= settings.fetch(:jid)
26
+ end
27
+
28
+ def host
29
+ @host ||= settings.fetch(:host, 'chat.hipchat.com')
30
+ end
31
+
32
+ def muc_domain
33
+ @muc_domain ||= settings.fetch(:muc_domain, 'conf.hipchat.com')
34
+ end
35
+
36
+ def name
37
+ @name ||= settings.fetch(:full_name)
38
+ end
39
+
40
+ def password
41
+ @password ||= settings.fetch(:password)
42
+ end
43
+
44
+ def logger
45
+ @logger ||=
46
+ begin
47
+ x = Logger.new(STDOUT)
48
+ x.level = settings.fetch(:log_level, Logger::INFO).to_i
49
+ x
50
+ end
51
+ end
52
+
53
+ def socket_file
54
+ @socket_file ||= settings.fetch(:socket_file, SOCKET_FILE)
55
+ end
56
+
57
+ def default_rooms
58
+ @default_rooms ||= settings.fetch(:rooms, [])
28
59
  end
29
60
 
30
61
  def load(file)
31
62
  return if file.nil?
32
63
  return Kernel.load(file) if File.exist?(file)
33
-
34
- eval(remote_fetch(file), binding)
64
+ download(file)
35
65
  end
36
66
 
37
67
  private
38
68
 
39
- def remote_fetch(url)
40
- require 'uri'
41
- require 'net/http'
69
+ attr_reader :settings
70
+
71
+ def download(url)
72
+ Net::Hippie.logger = logger
73
+ content = Net::Hippie::Api.new(url).get
42
74
 
43
- uri = URI.parse(url)
44
- http = Net::HTTP.new(uri.host, uri.port)
45
- http.use_ssl = uri.is_a?(URI::HTTPS)
46
- response = http.request(Net::HTTP::Get.new(uri.request_uri))
47
- Del.logger.info("Loading...\n#{response.body}")
48
- response.body
75
+ path = Tempfile.new('del').path
76
+ IO.write(path, content)
77
+ load(path)
49
78
  end
50
79
  end
51
80
  end
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Del
4
+ # This class is the default router used
5
+ # to route chat messages to chat routes.
2
6
  class DefaultRouter
3
7
  def initialize(routes = [])
4
8
  @routes = routes
@@ -10,8 +14,11 @@ module Del
10
14
 
11
15
  def route(message)
12
16
  @routes.each do |route|
13
- if matches = route[:pattern].match(message.text)
17
+ next unless (matches = route[:pattern].match(message.text))
18
+ begin
14
19
  route[:command].call(message, matches)
20
+ rescue StandardError => error
21
+ Del.logger.error(error)
15
22
  end
16
23
  end
17
24
  end
@@ -1,17 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Del.configure do |x|
2
- puts "Registering custom routes."
4
+ puts 'Registering custom routes.'
3
5
 
4
6
  x.router.register(/.*/) do |message|
5
- Del.logger.info("Backwards!")
7
+ Del.logger.info('Backwards!')
6
8
  message.reply(message.text.reverse)
7
9
  end
8
10
 
9
11
  x.router.register(/^cowsay (.*)/) do |message, match_data|
10
- Del.logger.info("COWSAY!")
11
- message.reply("/code #{`cowsay #{match_data[1]}`}")
12
+ Del.logger.info('COWSAY!')
13
+ message.execute_shell(['cowsay', match_data[1]])
12
14
  end
13
15
 
14
16
  x.router.register(/^[Hh]ello/) do |message|
15
- message.reply("Hi!")
17
+ message.reply('Hi!')
16
18
  end
17
19
  end
data/lib/del/message.rb CHANGED
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Del
4
+ # An XMPP Message
2
5
  class Message
3
- PREFIX = "/code"
6
+ PREFIX = '/code'
4
7
  attr_reader :text, :robot, :source
5
8
 
6
9
  def initialize(text, robot:, source:)
@@ -14,21 +17,14 @@ module Del
14
17
  end
15
18
 
16
19
  def execute_shell(command)
17
- command = Array(command).flatten.join(' ')
18
- reply("Okay, I will run #{command}.")
19
- success = false
20
- Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
21
- stdout.each_line do |line|
22
- yield line if block_given?
23
- reply("#{PREFIX} #{line}")
24
- end
25
- stderr.each_line do |line|
26
- yield line if block_given?
20
+ reply("Okay, I'm on it!")
21
+ ShellCommand.new(command).run do |line|
22
+ if block_given?
23
+ yield line
24
+ else
27
25
  reply("#{PREFIX} #{line}")
28
26
  end
29
- success = wait_thr.value.success?
30
27
  end
31
- success
32
28
  end
33
29
 
34
30
  def to_s
@@ -1,26 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Del
4
+ # This class is a facade for backend data storage.
2
5
  class Repository
3
- def initialize(storage = {})
6
+ def initialize(storage: {}, mapper:)
4
7
  @storage = storage
8
+ @mapper = mapper
5
9
  @lock = Mutex.new
6
10
  end
7
11
 
8
12
  def [](id)
9
- find_by(id)
13
+ find(id)
10
14
  end
11
15
 
12
- def find_by(id)
16
+ def find(id)
13
17
  @lock.synchronize do
14
- Del::User.new(id, @storage[id.to_s])
18
+ @mapper.map_from(@storage[id.to_s])
15
19
  end
16
20
  end
17
21
 
18
- def find_all
19
- @lock.synchronize { @storage.keys }
22
+ def all
23
+ @lock.synchronize do
24
+ @storage.map do |(_, value)|
25
+ @mapper.map_from(value)
26
+ end
27
+ end
20
28
  end
21
29
 
22
30
  def upsert(id, attributes = {})
23
- Del.logger.debug([id, attributes].inspect)
24
31
  @lock.synchronize do
25
32
  @storage[id.to_s] = attributes
26
33
  end
data/lib/del/robot.rb CHANGED
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Del
4
+ # A funky robo-sapien.
2
5
  class Robot
3
6
  attr_reader :jid, :name
4
7
 
@@ -9,16 +12,21 @@ module Del
9
12
  end
10
13
 
11
14
  def get_funky!(start_server: true)
12
- Del.logger.info("🔥🔥🔥")
15
+ Del.logger.info('🔥🔥🔥')
13
16
  xmpp_connection.connect(self)
14
- socket_server.run(self) if start_server
17
+ if start_server
18
+ deltron = Del::Tron.new(self, configuration)
19
+ socket_server.run(deltron)
20
+ end
15
21
  rescue Interrupt
16
22
  xmpp_connection.disconnect
17
23
  end
18
24
 
19
25
  def receive(message, source:)
20
26
  return if source.from?(self)
21
- configuration.router.route(Message.new(message, robot: self, source: source))
27
+ configuration.router.route(
28
+ Message.new(message, robot: self, source: source)
29
+ )
22
30
  end
23
31
 
24
32
  def send_message(jid, message)
@@ -29,13 +37,14 @@ module Del
29
37
  end
30
38
  end
31
39
 
32
- def execute(request)
33
- case request['command']
34
- when 'send_message'
35
- send_message(request['jid'], request['message'])
36
- "Sent!"
37
- else
38
- "Unknown"
40
+ {
41
+ away!: :away,
42
+ busy!: :dnd,
43
+ offline!: :xa,
44
+ online!: :chat
45
+ }.each do |name, value|
46
+ define_method name do |message = nil|
47
+ xmpp_connection.update_status(value, message: message)
39
48
  end
40
49
  end
41
50
 
@@ -44,11 +53,12 @@ module Del
44
53
  attr_reader :configuration
45
54
 
46
55
  def xmpp_connection
47
- @xmpp_connection ||= Connection.new(configuration: configuration)
56
+ @xmpp_connection ||= XMPPConnection.new(configuration: configuration)
48
57
  end
49
58
 
50
59
  def socket_server
51
- @socket_server ||= SocketServer.new(socket_file: configuration.socket_file)
60
+ @socket_server ||=
61
+ SocketServer.new(socket_file: configuration.socket_file)
52
62
  end
53
63
 
54
64
  def user?(jid)
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Del
4
+ class SendMessage
5
+ def initialize(shell, socket_file:)
6
+ @shell = shell
7
+ @socket = SocketMessage.new(@shell, socket_file: socket_file)
8
+ end
9
+
10
+ def run(jid, message)
11
+ @socket.deliver(
12
+ command: :send_message,
13
+ jid: jid,
14
+ message: message
15
+ )
16
+ @shell.say(@socket.listen, :green)
17
+ ensure
18
+ @socket.close
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Del
4
+ # Executes shell commands and pipes the
5
+ # results back to the caller.
6
+ class ShellCommand
7
+ def initialize(command)
8
+ @command = Array(command).flatten.join(' ')
9
+ end
10
+
11
+ def run
12
+ Open3.popen3(@command) do |_stdin, stdout, stderr, wait_thr|
13
+ stdout.each_line { |line| yield line }
14
+ stderr.each_line { |line| yield line }
15
+ wait_thr.value.success?
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Del
2
4
  class SocketConnection
3
5
  def initialize(path:)
4
- File.unlink(path) if File.exists?(path)
6
+ File.unlink(path) if File.exist?(path)
5
7
  @server = UNIXServer.new(path)
6
8
  end
7
9
 
8
10
  def on_receive
9
11
  socket = @server.accept
10
12
  yield socket
11
- rescue => error
13
+ rescue StandardError => error
12
14
  Del.logger.error(error)
13
15
  ensure
14
16
  socket&.close
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Del
4
+ class SocketMessage
5
+ def initialize(shell, socket_file:)
6
+ @shell = shell
7
+ @socket_file = socket_file
8
+ end
9
+
10
+ def deliver(payload)
11
+ socket.puts(message_for(payload))
12
+ rescue EOFError => error
13
+ @shell.say error.message, :red
14
+ rescue Errno::ECONNREFUSED => error
15
+ @shell.say error.message, :red
16
+ @shell.say 'You must start the del server first.', :yellow
17
+ end
18
+
19
+ def listen
20
+ socket.readline
21
+ end
22
+
23
+ def close
24
+ socket&.close
25
+ end
26
+
27
+ private
28
+
29
+ def message_for(payload)
30
+ JSON.generate(payload)
31
+ end
32
+
33
+ def socket
34
+ @socket ||= UNIXSocket.new(@socket_file)
35
+ end
36
+ end
37
+ end
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Del
4
+ # A Socket server for client/server communication
5
+ # overs a UNIX socket.
2
6
  class SocketServer
3
7
  def initialize(socket_file:)
4
8
  @connection = SocketConnection.new(path: socket_file)
data/lib/del/source.rb CHANGED
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Del
4
+ # This represents the source of a chat message.
2
5
  class Source
3
6
  attr_reader :user, :room
4
7
 
data/lib/del/tron.rb ADDED
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Del
4
+ class Tron
5
+ attr_reader :robot, :configuration
6
+
7
+ def initialize(robot, configuration)
8
+ @robot = robot
9
+ @configuration = configuration
10
+ end
11
+
12
+ def execute(request)
13
+ command_for(request)&.call(request) || 'Unknown'
14
+ rescue StandardError => error
15
+ error.message
16
+ end
17
+
18
+ private
19
+
20
+ def commands
21
+ {
22
+ change_status: ->(request) { change_status(request) },
23
+ send_message: ->(request) { send_message(request) },
24
+ users: ->(request) { users(request) },
25
+ whoami: ->(request) { whoami(request) },
26
+ whois: ->(request) { JSON.generate(whois(request['q'])) }
27
+ }
28
+ end
29
+
30
+ def command_for(request)
31
+ commands[request['command'].to_sym]
32
+ end
33
+
34
+ def whois(jid)
35
+ configuration.users[jid]&.attributes || {}
36
+ end
37
+
38
+ def send_message(request)
39
+ robot.send_message(request['jid'], request['message'])
40
+ 'Sent!'
41
+ end
42
+
43
+ def users(_request)
44
+ JSON.generate(configuration.users.all.map(&:attributes))
45
+ end
46
+
47
+ def whoami(_request)
48
+ JSON.generate(whois(robot.jid))
49
+ end
50
+
51
+ def change_status(request)
52
+ robot.public_send("#{request['status'].downcase}!", request['message'])
53
+ 'Done!'
54
+ rescue NoMethodError
55
+ 'Error: Invalid status'
56
+ end
57
+ end
58
+ end