Skip to content

Instantly share code, notes, and snippets.

@jangler
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jangler/9165383 to your computer and use it in GitHub Desktop.
Save jangler/9165383 to your computer and use it in GitHub Desktop.
extensible IRC bot (requires suckless.org sic)
#!/usr/bin/env ruby
# vim:sw=2
# IRC bot (requires suckless.org sic).
# Each executable file in the current directory is treated as a command
# plugin for the bot, and is executed when a command corresponding to its
# filename is called. For example, a plugin with filename "greet" would be
# invoked via the command "!greet".
# The plugin is passed the channel, nick, and message of its invocation as
# 3 positional arguments. Any line printed to standard output by the plugin
# is forwarded to the channel of the invoking message.
require 'optparse'
require 'shellwords'
OPTIONS, CHANNELS = begin
options, channels = [], []
OptionParser.new do |opts|
opts.on('-s', '--server <server>',
'Overrides the default server') { |serv| options << '-h' << serv }
opts.on('-p', '--port <port>', Integer,
'Overrides the default port') { |port| options << '-p' << port }
opts.on('-n', '--nick <nick>',
'Override the default nick') { |nick| options << '-n' << nick }
opts.on('-k', '--keyword <keyword>',
'Specifies the keyword to authenticate your',
'nick on the host') { |keyword| options << '-k' << keyword }
opts.on('-c', '--channels <channels>',
'Comma-separated list of channels to join') do |list|
channels = list.split(',')
end
end.parse!
[options.freeze, channels.freeze]
end
def join_channels(subprocess)
subprocess.each_line do |line|
if line.include? ' >< MODE ('
CHANNELS.each { |chan| subprocess.write(":j ##{chan}\n") }
subprocess.flush
break
end
end
end
def run_plugin(subprocess, plugin, channel, nick, msg)
args = [plugin, channel, nick, msg].map { |arg| arg.shellescape }
IO.popen(args.join(' ')) do |plugin|
plugin.each_line do |line|
target = channel[0] == '#' ? channel : nick
subprocess.write(":m #{target} #{line}")
subprocess.flush
end
end
end
def loop_input(subprocess)
subprocess.each_line do |line|
begin
line_match = /(\S+).+<(.+?)> (.+)/.match(line)
rescue ArgumentError
next
end
if line_match
channel, nick, msg = line_match[1, 3]
cmd, args = msg.split(' ', 2)
args = '' if not args
/^\W(.+)/.match(cmd) do |cmd_match|
files = Dir.glob(cmd_match[1]).select do |file|
File.executable? file and not File.directory? file
end
files.each do |plugin|
Thread.new do
run_plugin(subprocess, './' + plugin, channel, nick, args)
end
end
end
end
end
end
if fork
exit!
end
IO.popen("sic #{OPTIONS.join(' ')}", 'w+') do |subprocess|
join_channels(subprocess)
loop_input(subprocess)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment