-
Notifications
You must be signed in to change notification settings - Fork 0
/
fancy_bot.rb
322 lines (278 loc) · 8.75 KB
/
fancy_bot.rb
1
2
3
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
require 'open-uri'
require 'cinch'
require "date"
require "timeout"
require "open3"
require "net/http"
require "uri"
FANCY_DIR = ARGV[0]
FANCY_CMD = "#{FANCY_DIR}/bin/fancy -I #{FANCY_DIR}"
LOGDIR = ARGV[1] ? ARGV[1] : "."
API_DOC_DESTDIR = ARGV[2] ? ARGV[2] : nil
class Cinch::Bot
attr_reader :plugins # hack to allow access to plugins from outside
end
class FancyLogger
include Cinch::Plugin
listen_to :channel
def log_message(msg)
time = Time.now
if msg.user.nick
logfile.puts "[#{time}] #{msg.user.nick}: #{msg.message}"
else
logfile.puts "[#{time}]: #{msg.message}"
end
logfile.flush
end
def logfile
@current_date ||= Date.today
@logfile ||= File.open("#{LOGDIR}/#fancy_#{Date.today}.txt", "a")
if @current_date != Date.today
@logfile.close
@current_date = Date.today
@logfile = File.open("#{LOGDIR}/#fancy_#{Date.today}.txt", "a")
end
@logfile
end
def listen(m)
log_message m
end
def shutdown
@logfile.close
end
end
class Seen < Struct.new(:who, :where, :what, :time)
def to_s
"[#{time.asctime}] #{who} was seen in #{where} saying #{what}"
end
end
#######################
# The actual irc bot: #
#######################
bot = Cinch::Bot.new do
configure do |c|
c.server = "irc.freenode.org"
c.channels = ["#fancy"]
c.nick = "fancy_bot"
c.plugins.plugins = [FancyLogger]
@seen_users = {}
@start_time = Time.now
end
helpers do
def shorten(url)
url = open("http://tinyurl.com/api-create.php?url=#{URI.escape(url)}").read
url == "Error" ? nil : url
rescue OpenURI::HTTPError
nil
end
def get_revision
`cd #{FANCY_DIR} && git show master`.split[1]
end
# paste some text to gist.github.com
def paste_text(text, title = "rake test")
uri = URI.parse('http://gist.github.com/api/v1/xml/new')
req = Net::HTTP::Post.new(uri.path)
req.set_form_data({ "files[Fancy: #{title} @ #{get_revision}]" => text })
res = Net::HTTP.new(uri.host, uri.port).start {|http| http.request(req) }
if(res.code == '200')
'http://gist.github.com/' + res.body.match(/repo>(\d+)</)[1]
else
false
end
end
# fetch latest revision from github
def fetch_latest_revision(m)
Open3.popen3("cd #{FANCY_DIR} && git pull origin master") do |stdin, stdout, stderr|
err_lines = stderr.readlines
if err_lines.size > 0
# only print error lines if we're not dealing with an
# already-up-to-date-message.
unless err_lines.all?{|l| l !~ /Already up-to-date./}
m.reply "Got error while trying to update from repository:"
err_lines.each do |l|
m.reply l.chomp
end
m.reply "Won't/Can't build or run tests."
return false # done since error
end
end
end
return true
end
def non_error_line?(line)
line =~ /make(.+)?:/ ||
line =~ /warning|warnung|In function/i ||
line =~ /parser.(y|c): (konflikte|conflicts):/i ||
line =~ /lexer.(lex|c):/i ||
line =~ /^rm -f/ ||
line =~ /^make -C/ ||
line =~ /^rbx/ ||
line =~ /^flex/ ||
line =~ /^bison/ ||
line =~ /^rm / ||
line =~ /^mv / ||
line =~ /^In file included from/
end
# sends error messages to channel, ignoring any warnings etc that
# aren't real error messages
def send_errors(m, errors, cmd)
# ignore warnings and rake output lines
errors.reject!{|e| non_error_line?(e) }
size = errors.size
if size > 0
gist_url = paste_text(errors.join, cmd)
m.reply "Got #{size} errors during '#{cmd}'. See: #{gist_url}"
end
return size
end
# runs a given comand in FANCY_DIR and outputs any resulting error
# messages
def do_cmd(m, cmd)
Open3.popen3("cd #{FANCY_DIR} && #{cmd}") do |stdin, stdout, stderr|
errors = stderr.readlines
if errors.size > 0
real_errors = send_errors(m, errors, cmd)
yield if real_errors > 0
end
end
end
def do_rake(m)
rake_log_file = "/tmp/rake_err_output.log"
system("cd #{FANCY_DIR} && rake > /tmp/rake_stdout_output.log 2> #{rake_log_file}")
lines =[]
File.open(rake_log_file, "r") do |f|
lines = f.readlines
end
err_lines = lines.reject{|l| non_error_line?(l) }
if err_lines.size > 0
gist_url = paste_text(err_lines.join, "rake")
m.reply "Got #{err_lines.size} errors while compiling. See: #{gist_url}"
yield if block_given?
end
end
# try to build fancy source
def try_build(m)
do_cmd(m, "rake clean"){ return false }
do_rake(m){ return false }
return true
end
# try to run FancySpecs
def run_tests(m)
IO.popen("cd #{FANCY_DIR} && rake test", "r") do |o|
lines = o.readlines
failed = lines.select{|l| l =~ /FAILED:/}
amount = failed.size
if amount > 0
failed << "=> #{amount} failed tests!"
gist_url = paste_text(failed.join, "rake test")
end
m.reply "=> #{amount} failed tests! See: #{gist_url}"
end
end
def gen_docs(m)
do_cmd(m, "cp doc/api/* #{API_DOC_DESTDIR}")
if API_DOC_DESTDIR
do_cmd(m, "bin/fdoc -o #{API_DOC_DESTDIR}")
else
do_cmd(m, "bin/fdoc")
end
end
def do_update_build_test(m)
m.reply "Getting latest changes & trying to run tests."
return unless fetch_latest_revision m
return unless try_build m
gen_docs m
run_tests m
end
end
# Message handlers
# Only log channel messages for !seen
on :channel do |m|
@seen_users[m.user.nick] = Seen.new(m.user.nick, m.channel, m.message, Time.new)
end
# Display !seen user info
on :channel, /^!seen (.+)/ do |m, nick|
if nick == bot.nick
m.reply "That's me!"
elsif nick == m.user.nick
m.reply "That's you!"
elsif @seen_users.key?(nick)
m.reply @seen_users[nick].to_s
else
m.reply "Sorry, I haven't seen #{nick}"
end
end
# Display shortened URLs (via tinyurl.com)
on :channel, /^!shorten (.+)$/ do |m, url|
urls = URI.extract(url, "http")
unless urls.empty?
short_urls = urls.map {|url| shorten(url) }.compact
unless short_urls.empty?
m.reply short_urls.join(", ")
end
end
end
# Display uptime of bot in channel
on :message, "!uptime" do |m|
m.reply "I'm running since #{@start_time}, which is #{Time.at(Time.now - @start_time).gmtime.strftime('%R:%S')}"
end
on :message, /^!(info|help) (.+)$/ do |m, foo, command_name|
case command_name
when "!seen"
m.reply "!seen <nickname> : Displays information on when <nickname> was last seen."
when "!uptime"
m.reply "!uptime : Displays uptime information for FancyBot."
when "!shorten"
m.reply "!shorten <url> [<urls>] : Displays a shorted version of any given amount of urls (using tinyurl.com)."
when /!(info|help)/
m.reply "!info/!help [<command>]: Displays help text for <command>. If <command> is ommitted, displays general help text."
when "!"
m.reply "! <code> : Evaluates the <code> given (expects it to be Fancy code) and displays any output from evaluation."
m.reply "! <code> : Maximum timeout for any computation is 5 seconds and only up to 5 lines will be displayed here (seperated by ';' instead of a newline)."
else
m.reply "Unknown command: #{command_name}."
end
end
on :message, /^!(info|help)$/ do |m|
m.reply "This is FancyBot v0.2 running @ irc.fancy-lang.org"
m.reply "Possible commands are: !seen <nick>, !uptime, !shorten <url> [<urls>], !info, !help, ! <code>"
end
on :message, /^! (.+)$/ do |m, cmd|
m.reply "=> Sorry, evaling fancy code is not possible at the moment. Working on it."
# begin
# Timeout::timeout(5) do
# disable = ["Kernel", "File", "Directory", "System", "Dir", "IO"]
# disable_str = disable.map{|o| "#{o} = nil; "}.join
# IO.popen("#{FANCY_CMD} -e \"#{disable_str} #{cmd.gsub(/\"/, "\\\"")}\"", "r") do |o|
# lines = o.readlines
# if lines.size <= 5
# m.reply "=> #{lines.map(&:chomp).join("; ")}"
# else
# m.reply "=> #{lines[0..4].map(&:chomp).join("; ")} [...]"
# end
# end
# end
# rescue Timeout::Error
# m.reply "=> Your computation took to long! Timeout is set to 5 seconds."
# end
end
on :message, /^fancy:(.+)http/ do |m|
if m.user.nick == "fancy_gh"
do_update_build_test m
end
end
end
def bot_shutdown
puts "Bot is quitting"
bot.plugins.each do |p|
p.shutdown
end
exit
end
trap("INT") do
bot_shutdown
end
trap("KILL") do
bot_shutdown
end
bot.start