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.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +11 -0
- data/.rubocop.yml +41 -0
- data/.travis.yml +4 -1
- data/Gemfile +4 -2
- data/README.md +39 -5
- data/Rakefile +10 -3
- data/bin/cibuild +22 -0
- data/bin/console +6 -5
- data/bin/lint +6 -0
- data/bin/publish +6 -0
- data/bin/test +17 -0
- data/del.gemspec +23 -16
- data/exe/del +2 -1
- data/lib/del.rb +29 -20
- data/lib/del/cli.rb +91 -47
- data/lib/del/configuration.rb +60 -31
- data/lib/del/default_router.rb +8 -1
- data/lib/del/examples/routes.rb +7 -5
- data/lib/del/message.rb +9 -13
- data/lib/del/repository.rb +14 -7
- data/lib/del/robot.rb +22 -12
- data/lib/del/send_message.rb +21 -0
- data/lib/del/shell_command.rb +19 -0
- data/lib/del/socket_connection.rb +4 -2
- data/lib/del/socket_message.rb +37 -0
- data/lib/del/socket_server.rb +4 -0
- data/lib/del/source.rb +3 -0
- data/lib/del/tron.rb +58 -0
- data/lib/del/user.rb +8 -0
- data/lib/del/version.rb +3 -1
- data/lib/del/xmpp_connection.rb +126 -0
- metadata +73 -7
- data/lib/del/connection.rb +0 -98
data/lib/del/configuration.rb
CHANGED
@@ -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=
|
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
|
-
|
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
|
-
@
|
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
|
-
@
|
27
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
data/lib/del/default_router.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/del/examples/routes.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
Del.configure do |x|
|
2
|
-
puts
|
4
|
+
puts 'Registering custom routes.'
|
3
5
|
|
4
6
|
x.router.register(/.*/) do |message|
|
5
|
-
Del.logger.info(
|
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(
|
11
|
-
message.
|
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(
|
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 =
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
data/lib/del/repository.rb
CHANGED
@@ -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
|
-
|
13
|
+
find(id)
|
10
14
|
end
|
11
15
|
|
12
|
-
def
|
16
|
+
def find(id)
|
13
17
|
@lock.synchronize do
|
14
|
-
|
18
|
+
@mapper.map_from(@storage[id.to_s])
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
18
|
-
def
|
19
|
-
@lock.synchronize
|
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
|
-
|
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(
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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 ||=
|
56
|
+
@xmpp_connection ||= XMPPConnection.new(configuration: configuration)
|
48
57
|
end
|
49
58
|
|
50
59
|
def socket_server
|
51
|
-
@socket_server ||=
|
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.
|
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
|
data/lib/del/socket_server.rb
CHANGED
data/lib/del/source.rb
CHANGED
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
|