omnibot 0.0.21 → 0.0.22
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/Rakefile +1 -1
- data/examples/command_mail.rb +2 -2
- data/lib/omnibot.rb +32 -33
- data/lib/omnibot/amqpconsumer.rb +72 -72
- data/lib/omnibot/helpers.rb +64 -64
- data/lib/omnibot/jabberbot.rb +198 -205
- data/lib/omnibot/launcher.rb +58 -69
- data/lib/omnibot/loggedcommand.rb +14 -15
- data/lib/omnibot/mailchecker.rb +106 -114
- data/lib/omnibot/omnisend.rb +19 -21
- data/lib/omnibot/periodiccommand.rb +40 -45
- data/lib/omnibot/version.rb +1 -1
- metadata +20 -5
data/lib/omnibot/launcher.rb
CHANGED
@@ -1,83 +1,72 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module OmniBot
|
4
|
+
# entry point
|
5
|
+
class Launcher
|
6
|
+
def get_config_path(args)
|
7
|
+
return args[0] if args[0] && File.file?(args[0])
|
8
|
+
path = ENV['HOME'] + '/.local/share/omnibot/omnibot.yaml'
|
9
|
+
return path if File.file?(path)
|
10
|
+
raise 'No config file found, checked command line and ~/.local/share/omnibot/omnibot.yaml'
|
11
|
+
end
|
4
12
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
return path if ENV['HOME'] && File.file?(path)
|
11
|
-
raise 'No config file found, checked command line and ~/.omnibot.yaml'
|
12
|
-
end
|
13
|
+
def ensure_omnidir
|
14
|
+
path = ENV['HOME'] + '/.local/share/omnibot'
|
15
|
+
FileUtils.mkdir path unless File.directory? path
|
16
|
+
path
|
17
|
+
end
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
+
def log_path(config)
|
20
|
+
return config['logpath'] unless (config['logpath'] || '').empty?
|
21
|
+
ensure_omnidir + '/omnibot.log'
|
22
|
+
end
|
19
23
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
def create_db
|
25
|
+
db = SQLite3::Database.new(ensure_omnidir + '/omnibot.sqlite3')
|
26
|
+
if db.execute("select * from sqlite_master where type='table' and name='received_messages'").empty?
|
27
|
+
db.execute <<-SQL
|
28
|
+
create table received_messages (
|
29
|
+
account TEXT,
|
30
|
+
message TEXT,
|
31
|
+
date TEXT
|
32
|
+
);
|
33
|
+
SQL
|
34
|
+
end
|
35
|
+
db
|
36
|
+
end
|
24
37
|
|
25
|
-
|
26
|
-
|
27
|
-
if db.execute("select * from sqlite_master where type='table' and name='received_messages'").empty?
|
28
|
-
db.execute <<-SQL
|
29
|
-
create table received_messages (
|
30
|
-
account TEXT,
|
31
|
-
message TEXT,
|
32
|
-
date TEXT
|
33
|
-
);
|
34
|
-
SQL
|
35
|
-
end
|
36
|
-
db
|
37
|
-
end
|
38
|
+
def provide_handlers(config, db)
|
39
|
+
periodic_commands = [config['periodiccommands']].flatten.compact
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
used_mails = mails.select { |m| mail_triggers.find { |mt| m['user'] == mt['for'] } }
|
49
|
-
raise 'Sorry but multiple mail addresses is not supported yet' if used_mails.size > 1
|
41
|
+
mails = [config['mails']].flatten.compact
|
42
|
+
mail_triggers = [config['mailtriggers']].flatten.compact
|
43
|
+
mail_triggers.each do |mt|
|
44
|
+
raise 'No mail found for a trigger' unless mails.find { |m| m['user'] == mt['for'] }
|
45
|
+
raise 'Not supported action' unless mt['action'] == 'unpack'
|
46
|
+
end
|
47
|
+
used_mails = mails.select { |m| mail_triggers.find { |mt| m['user'] == mt['for'] } }
|
48
|
+
raise 'Sorry but multiple mail addresses is not supported yet' if used_mails.size > 1
|
50
49
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
mail_triggers.map do |trigger|
|
56
|
-
mail = mails.find { |m| m['user'] == trigger['for'] }
|
57
|
-
MailChecker.new mail, trigger, db
|
58
|
-
end
|
59
|
-
end
|
50
|
+
[] +
|
51
|
+
periodic_commands.map { |command| PeriodicCommand.new command } +
|
52
|
+
mail_triggers.map { |trigger| MailChecker.new(mails.find { |m| m['user'] == trigger['for'] }, trigger, db) }
|
53
|
+
end
|
60
54
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
55
|
+
def start(args)
|
56
|
+
config_path = get_config_path args
|
57
|
+
puts "Using config at #{config_path}"
|
58
|
+
config = YAML.load_file(config_path)['config']
|
65
59
|
|
66
|
-
|
67
|
-
|
68
|
-
OmniLog::log = Logger.new(log_path)
|
69
|
-
OmniLog::log.level = Logger::DEBUG
|
60
|
+
OmniLog::log = Logger.new(log_path(config))
|
61
|
+
OmniLog::log.level = Logger::DEBUG
|
70
62
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
63
|
+
db = create_db
|
64
|
+
consumer = AMQPConsumer.new config
|
65
|
+
consumer.db = db
|
66
|
+
consumer.handlers = provide_handlers(config, db)
|
67
|
+
consumer.start
|
76
68
|
|
77
|
-
|
78
|
-
|
79
|
-
|
69
|
+
OmniLog::log.close
|
70
|
+
end
|
71
|
+
end
|
80
72
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
@@ -1,20 +1,19 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module OmniBot
|
4
|
-
|
4
|
+
module LoggedCommand
|
5
|
+
def jabber_logged_command(banner, command)
|
6
|
+
body = `#{command}`
|
7
|
+
if $? != 0
|
8
|
+
@jabber_messenger.call "#{banner} command #{command} failed with an error #{$?}:\n" + body
|
9
|
+
end
|
10
|
+
if body.strip != ''
|
11
|
+
@jabber_messenger.call "#{banner} command #{command} succeeded with:\n" + body
|
12
|
+
end
|
13
|
+
end
|
5
14
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
-
if body.strip != ''
|
12
|
-
@jabber_messenger.call "#{banner} command #{command} succeeded with:\n" + body
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def set_jabber_messenger &block
|
17
|
-
@jabber_messenger = block
|
18
|
-
end
|
19
|
-
end
|
15
|
+
def jabber_messenger(&block)
|
16
|
+
@jabber_messenger = block
|
17
|
+
end
|
18
|
+
end
|
20
19
|
end
|
data/lib/omnibot/mailchecker.rb
CHANGED
@@ -1,118 +1,110 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module OmniBot
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
end
|
111
|
-
|
112
|
-
def start
|
113
|
-
@timer_provider.add_timer(@startup_pause) { on_first_timer }
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
4
|
+
class MailChecker
|
5
|
+
include LoggedCommand
|
6
|
+
|
7
|
+
def on_first_timer
|
8
|
+
on_periodic_timer
|
9
|
+
@timer_provider.add_periodic_timer(3600) { on_periodic_timer }
|
10
|
+
end
|
11
|
+
|
12
|
+
def match_condition(m, condition_name, mail_name = condition_name)
|
13
|
+
values = m.send(mail_name)
|
14
|
+
[values].flatten.any? do |value|
|
15
|
+
@conditions[condition_name] && Regexp.new(@conditions[condition_name]).match(value.to_s)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def match_conditions(m)
|
20
|
+
@conditions.empty? || %w( subject from to cc date).any? { |condition| match_condition m, condition }
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_periodic_timer
|
24
|
+
OmniLog::info "Checking mail #{@address}"
|
25
|
+
is_new_mail = false
|
26
|
+
Mail.all.each do |m|
|
27
|
+
rows = @db.execute 'select message from received_messages where account=? and message=?', @address, m.message_id
|
28
|
+
|
29
|
+
next unless rows.empty?
|
30
|
+
is_new_mail = true
|
31
|
+
OmniLog::info "New message from #{m.from} about #{m.subject}; id #{m.message_id}"
|
32
|
+
handle_message(m) if match_conditions(m)
|
33
|
+
@db.execute 'insert into received_messages values(?, ?, ?)', @address, m.message_id, m.date.to_s
|
34
|
+
end
|
35
|
+
OmniLog::info 'No new mail' unless is_new_mail
|
36
|
+
rescue => e
|
37
|
+
OmniLog::error "MailChecker error: #{e.message}\ntrace:\n#{Helpers::backtrace e}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def handle_message(m)
|
41
|
+
OmniLog::info 'Matched ' + m.inspect.to_s
|
42
|
+
attached = m.attachments.find { |a| a.mime_type =~ %r{application/(zip|x-zip|rar|x-rar).*} }
|
43
|
+
if attached
|
44
|
+
Dir.mktmpdir('omniatt') do |tmpdir|
|
45
|
+
filename = tmpdir + '/' + attached.filename
|
46
|
+
OmniLog::info "Writing attachment to #{filename}"
|
47
|
+
File.open(filename, 'w') { |f| f.write attached.read }
|
48
|
+
Dir.chdir(@unpack_to) do
|
49
|
+
if filename =~ /\.zip$/
|
50
|
+
system("unzip -oq '#{filename}'")
|
51
|
+
elsif filename =~ /\.rar$/
|
52
|
+
system("unrar x -y '#{filename}'")
|
53
|
+
else
|
54
|
+
raise 'Wrong filetype'
|
55
|
+
end
|
56
|
+
raise "Error extracting file #{filename} to #{@unpack_to}" if $? != 0
|
57
|
+
end
|
58
|
+
|
59
|
+
message_body = "Received an email '#{m.subject}' from '#{m.from.join(',')}' with "\
|
60
|
+
"an attachment #{attached.filename}. Successfully extracted an attachment to #{@unpack_to}."
|
61
|
+
@jabber_messenger.call message_body
|
62
|
+
|
63
|
+
jabber_logged_command 'Mail post-receive ', "#{@command_post} #{filename} #{@unpack_to}"
|
64
|
+
end
|
65
|
+
else
|
66
|
+
OmniLog::info 'No attachment found'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def yaml_to_mailhash(yaml_config)
|
71
|
+
{ address: yaml_config['host'],
|
72
|
+
port: yaml_config['port'],
|
73
|
+
user_name: yaml_config['user'],
|
74
|
+
password: yaml_config['password'],
|
75
|
+
enable_ssl: yaml_config['ssl']
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
public
|
80
|
+
|
81
|
+
attr_writer :timer_provider
|
82
|
+
attr_writer :startup_pause
|
83
|
+
|
84
|
+
def initialize(mail_config, trigger_config, db)
|
85
|
+
@startup_pause = 0
|
86
|
+
@mail_config = mail_config
|
87
|
+
@db = db
|
88
|
+
@conditions = (trigger_config['if'] || {})
|
89
|
+
@unpack_to = trigger_config['unpack_to']
|
90
|
+
@command_post = trigger_config['command_post']
|
91
|
+
@address = trigger_config['for']
|
92
|
+
|
93
|
+
mailhash = yaml_to_mailhash(mail_config)
|
94
|
+
Mail.defaults do
|
95
|
+
retriever_method :pop3, mailhash
|
96
|
+
end
|
97
|
+
|
98
|
+
raise 'Wrong command' if (@command_post || '') == ''
|
99
|
+
raise 'No dir to extract to' unless File.directory? @unpack_to
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_s
|
103
|
+
"Mail checker for #{@address}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def start
|
107
|
+
@timer_provider.add_timer(@startup_pause) { on_first_timer }
|
108
|
+
end
|
109
|
+
end
|
117
110
|
end
|
118
|
-
|
data/lib/omnibot/omnisend.rb
CHANGED
@@ -1,27 +1,25 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module OmniBot
|
4
|
+
class OmniSend
|
5
|
+
def start(args)
|
6
|
+
if args.empty?
|
7
|
+
message = STDIN.readlines.join
|
8
|
+
else
|
9
|
+
message = args.join(' ')
|
10
|
+
end
|
11
|
+
puts "Sending message #{message}"
|
12
|
+
data = Base64.encode64(Marshal.dump(message))
|
4
13
|
|
5
|
-
|
14
|
+
Signal.trap('INT') { AMQP.stop { EM.stop } }
|
6
15
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
Signal.trap('INT') { AMQP.stop{ EM.stop } }
|
17
|
-
|
18
|
-
AMQP.start do |connection|
|
19
|
-
mq = AMQP::Channel.new(connection)
|
20
|
-
exchange = mq.direct(Helpers::amqp_exchange_name)
|
21
|
-
exchange.publish(data, :routing_key => Helpers::amqp_routing_key)
|
22
|
-
EM.add_timer(2.0) { connection.close { puts 'sent' ; EM.stop } }
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
16
|
+
AMQP.start do |connection|
|
17
|
+
mq = AMQP::Channel.new(connection)
|
18
|
+
exchange = mq.direct(Helpers::amqp_exchange_name)
|
19
|
+
exchange.publish(data, routing_key: Helpers::amqp_routing_key)
|
20
|
+
EM.add_timer(2.0) { connection.close { EM.stop } }
|
21
|
+
end
|
22
|
+
puts 'Sent'
|
23
|
+
end
|
24
|
+
end
|
26
25
|
end
|
27
|
-
|
@@ -1,49 +1,44 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module OmniBot
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
@timer_provider.add_timer(next_report_time - now) { on_first_timer }
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
4
|
+
# Send to jabber user result of a daily command
|
5
|
+
class PeriodicCommand
|
6
|
+
include OmniBot::LoggedCommand
|
7
|
+
|
8
|
+
def on_first_timer
|
9
|
+
on_periodic_timer
|
10
|
+
@timer_provider.add_periodic_timer(24 * 3600) { on_periodic_timer }
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_periodic_timer
|
14
|
+
OmniLog::info "Reporting command #{@command}"
|
15
|
+
jabber_logged_command 'Periodic command', @command
|
16
|
+
rescue => e
|
17
|
+
OmniLog::error "PeriodicCommand error: #{e.message}\ntrace:\n#{Helpers::backtrace e}"
|
18
|
+
end
|
19
|
+
|
20
|
+
public
|
21
|
+
|
22
|
+
attr_writer :timer_provider
|
23
|
+
attr_writer :startup_pause
|
24
|
+
|
25
|
+
def initialize(command)
|
26
|
+
@command = command
|
27
|
+
@startup_pause = 0
|
28
|
+
|
29
|
+
raise 'Wrong command' if (@command || '') == ''
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"Periodic command '#{@command}'"
|
34
|
+
end
|
35
|
+
|
36
|
+
def start
|
37
|
+
now = Time.now
|
38
|
+
tomorrow = DateTime.now + 1
|
39
|
+
next_report_time = Time.local(tomorrow.year, tomorrow.month, tomorrow.day, 1, 0, 0)
|
40
|
+
next_report_time += @startup_pause
|
41
|
+
@timer_provider.add_timer(next_report_time - now) { on_first_timer }
|
42
|
+
end
|
43
|
+
end
|
48
44
|
end
|
49
|
-
|