del 0.1.16 → 0.1.17

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.
@@ -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