rubot 0.0.1
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.
- data/README +1 -0
- data/Rakefile +12 -0
- data/lib/core.rb +8 -0
- data/lib/core/command.rb +70 -0
- data/lib/core/dispatcher.rb +84 -0
- data/lib/core/listener.rb +13 -0
- data/lib/core/runner.rb +13 -0
- data/lib/extensions.rb +4 -0
- data/lib/extensions/kernel.rb +6 -0
- data/lib/extensions/string.rb +9 -0
- data/lib/init/bundler.rb +9 -0
- data/lib/irc.rb +7 -0
- data/lib/irc/constants.rb +10 -0
- data/lib/irc/message.rb +14 -0
- data/lib/irc/server.rb +149 -0
- data/lib/rubot.rb +12 -0
- metadata +83 -0
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# todo Add documentation here
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('rubot', '0.0.1') do |p|
|
6
|
+
p.description = "A Ruby Bot framwork for IRC"
|
7
|
+
p.url = "http://github.com/tombombadil/hello_world"
|
8
|
+
p.author = "Chris Thorn"
|
9
|
+
p.email = "thorncp @nospam@ gmail.com"
|
10
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
11
|
+
p.development_dependencies = []
|
12
|
+
end
|
data/lib/core.rb
ADDED
data/lib/core/command.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require "shellwords"
|
2
|
+
module Rubot
|
3
|
+
module Core
|
4
|
+
class Command
|
5
|
+
attr_reader :protected
|
6
|
+
|
7
|
+
def initialize(dispatcher)
|
8
|
+
@dispatcher = dispatcher
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(server, message)
|
12
|
+
args = Shellwords.shellwords(message.body.gsub(/(')/n, "\\\\\'"))
|
13
|
+
options = parse(args)
|
14
|
+
|
15
|
+
if options.help
|
16
|
+
# this to_s.lines.to_a business is a hack to not display the -h, --help line
|
17
|
+
# there's got to be an easier/better way to do this. but it'll work for now
|
18
|
+
server.msg(message.destination, options.help.to_s.lines.to_a[0..-2])
|
19
|
+
else
|
20
|
+
message.body = args.join(" ")
|
21
|
+
execute(server, message, options)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def is_protected?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# commands override this
|
32
|
+
def execute(server, message, options)
|
33
|
+
server.msg(message.destincation, "unimplemented")
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.acts_as_protected
|
37
|
+
define_method(:is_protected?) do
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.aliases(*aliases)
|
43
|
+
raise ArgumentError, 'only symbols allowed' unless aliases.all? {|a| a.is_a? Symbol}
|
44
|
+
define_method(:aliases) do
|
45
|
+
aliases
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse(args)
|
50
|
+
options = OpenStruct.new
|
51
|
+
@parser = OptionParser.new do |parser|
|
52
|
+
parser.banner = "Usage: !#{self.class.to_s.downcase} [options]"
|
53
|
+
|
54
|
+
options(parser, options)
|
55
|
+
|
56
|
+
parser.on_tail("-h", "--help", "Show this message") do
|
57
|
+
options.help = parser
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
@parser.parse!(args)
|
62
|
+
options
|
63
|
+
end
|
64
|
+
|
65
|
+
# override this to add more options in a command class
|
66
|
+
def options(parser, options)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
module Rubot
|
4
|
+
module Core
|
5
|
+
class Dispatcher
|
6
|
+
|
7
|
+
attr_reader :commands, :listeners, :function_character, :config, :resource_lock
|
8
|
+
|
9
|
+
def initialize(config)
|
10
|
+
@config = config
|
11
|
+
@function_character = @config["function_character"]
|
12
|
+
|
13
|
+
@auth_list = config["auth_list"].split(',')
|
14
|
+
|
15
|
+
@resource_lock = Mutex.new
|
16
|
+
reload
|
17
|
+
# runners are only run on server connection, so there's no need them to be in reload
|
18
|
+
load_dir "runners", @runners = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def connected(server)
|
22
|
+
run_runners(server)
|
23
|
+
end
|
24
|
+
|
25
|
+
def reload
|
26
|
+
load_dir "commands", @commands = {}
|
27
|
+
load_dir "listeners", @listeners = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def handle_message(server, message)
|
31
|
+
if message.body =~ /^#{@function_character}([a-z_]+)( .+)?$/i
|
32
|
+
message.body = $2.nil? ? "" : $2.strip # remove the function name from the message
|
33
|
+
command = @commands[$1.underscore.to_sym]
|
34
|
+
|
35
|
+
if command.nil?
|
36
|
+
puts "#{$1} does not yield a command"
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
40
|
+
#if command is protected and user is not authenticated, return
|
41
|
+
if command.is_protected? && !@auth_list.include?(message.from)
|
42
|
+
server.msg(message.destination, "unauthorized")
|
43
|
+
return
|
44
|
+
end
|
45
|
+
|
46
|
+
command.run(server, message)
|
47
|
+
elsif message.from != server.nick
|
48
|
+
@listeners.each_value do |listener|
|
49
|
+
listener.execute(server, message)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_auth(nick)
|
55
|
+
unless @auth_list.include? nick
|
56
|
+
@auth_list.push nick
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def remove_auth(nick)
|
61
|
+
@auth_list.delete nick
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def run_runners(server)
|
67
|
+
@runners.each_value {|r| r.run(server)}
|
68
|
+
end
|
69
|
+
|
70
|
+
def load_dir(dir, set = nil)
|
71
|
+
Dir["#{dir}/*.rb"].each do |file|
|
72
|
+
load file
|
73
|
+
|
74
|
+
next unless set
|
75
|
+
|
76
|
+
name = File.basename(file, ".rb")
|
77
|
+
clazz = eval(name.camelize).new(self)
|
78
|
+
set[name.to_sym] = clazz
|
79
|
+
clazz.aliases.each{|a| set[a] = clazz} if clazz.respond_to? :aliases
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/core/runner.rb
ADDED
data/lib/extensions.rb
ADDED
data/lib/init/bundler.rb
ADDED
data/lib/irc.rb
ADDED
data/lib/irc/message.rb
ADDED
data/lib/irc/server.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
module Rubot
|
4
|
+
module Irc
|
5
|
+
class Server
|
6
|
+
include Rubot::Irc::Constants
|
7
|
+
attr_reader :nick, :connected_at
|
8
|
+
|
9
|
+
def initialize(dispatcher)
|
10
|
+
dispatcher.config["server"].each_pair do |key, value|
|
11
|
+
instance_variable_set("@#{key}".to_sym, value)
|
12
|
+
end
|
13
|
+
@channels = @channels.split(",").collect(&:strip)
|
14
|
+
@dispatcher = dispatcher
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect
|
18
|
+
return if @is_connected
|
19
|
+
|
20
|
+
@conn = TCPSocket.open(@host, @port, @vhost)
|
21
|
+
raw "USER #{@nick} #{@nick} #{@nick} :#{@real_name}"
|
22
|
+
change_nick @nick
|
23
|
+
join @channels
|
24
|
+
|
25
|
+
begin
|
26
|
+
main_loop()
|
27
|
+
rescue Interrupt
|
28
|
+
rescue Exception => detail
|
29
|
+
puts detail.message()
|
30
|
+
print detail.backtrace.join("\n")
|
31
|
+
retry
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def quit
|
36
|
+
raw "QUIT :#{@quit_message}"
|
37
|
+
@conn.close
|
38
|
+
end
|
39
|
+
|
40
|
+
def change_nick(new_nick)
|
41
|
+
raw "NICK #{new_nick}"
|
42
|
+
@nick = new_nick
|
43
|
+
end
|
44
|
+
|
45
|
+
def join(channels)
|
46
|
+
channels = channels.split(',') if channels.is_a? String
|
47
|
+
@channels.concat(channels).uniq!
|
48
|
+
bulk_command("JOIN %s", channels)
|
49
|
+
end
|
50
|
+
|
51
|
+
def part(channels)
|
52
|
+
channels = channels.split(',') if channels.is_a? String
|
53
|
+
@channels.reject! { |channel| channels.include?(channel) }
|
54
|
+
bulk_command("PART %s :#{@quit_message}", channels)
|
55
|
+
end
|
56
|
+
|
57
|
+
def msg(destination, message)
|
58
|
+
message = message.to_s.split("\n") unless message.is_a? Array
|
59
|
+
build_message_array(message).each do |l|
|
60
|
+
raw "PRIVMSG #{destination} :#{l}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def action(destination, message)
|
65
|
+
msg(destination, "\001ACTION #{message}\001")
|
66
|
+
end
|
67
|
+
|
68
|
+
def raw(message)
|
69
|
+
puts "--> #{message}"
|
70
|
+
@conn.puts "#{message}\n"
|
71
|
+
end
|
72
|
+
|
73
|
+
def names(channel)
|
74
|
+
raw "NAMES #{channel}"
|
75
|
+
@conn.gets.split(":")[2].split(" ")
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def main_loop
|
81
|
+
while true
|
82
|
+
ready = select([@conn])
|
83
|
+
next unless ready
|
84
|
+
|
85
|
+
return if @conn.eof
|
86
|
+
s = @conn.gets
|
87
|
+
handle_server_input(s)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def handle_server_input(s)
|
92
|
+
puts s
|
93
|
+
|
94
|
+
case s.strip
|
95
|
+
when /^PING :(.+)$/i
|
96
|
+
raw "PONG :#{$1}"
|
97
|
+
when /^:([-.0-9a-z]+)\s([0-9]+)\s(.+)\s(.*)$/i
|
98
|
+
handle_meta($1, $2.to_i, $4)
|
99
|
+
when /^:(.+?)!(.+?)@(.+?)\sPRIVMSG\s(.+)\s:(.+)$/i
|
100
|
+
message = Rubot::Irc::Message.new($1, $4 == @nick ? $1 : $4, $5)
|
101
|
+
# TODO add ability to pass events other than privmsg to dispatcher. ie, nick changes, parts, joins, quits, bans, etc, etc
|
102
|
+
@dispatcher.handle_message(self, message)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# performs the same command on each element in the given collection, separated by comma
|
107
|
+
def bulk_command(formatted_string, elements)
|
108
|
+
if elements.is_a? String
|
109
|
+
elements = elements.split(',')
|
110
|
+
end
|
111
|
+
|
112
|
+
elements.each do |e|
|
113
|
+
raw sprintf(formatted_string, e.to_s.strip)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def handle_meta(server, code, message)
|
118
|
+
case code
|
119
|
+
when ERR_NICK_IN_USE
|
120
|
+
if @nick == @alt_nick
|
121
|
+
puts "all nicks used, don't know how to name myself."
|
122
|
+
quit
|
123
|
+
exit!
|
124
|
+
end
|
125
|
+
change_nick @alt_nick
|
126
|
+
join @channels
|
127
|
+
when WELLCOME
|
128
|
+
@dispatcher.connected(self)
|
129
|
+
@is_connected = true
|
130
|
+
@connected_at = Time.now
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def string_to_irc_lines(str)
|
135
|
+
str.split(" ").inject([""]) do |arr, word|
|
136
|
+
arr.push("") if arr.last.size > MAX_MESSAGE_LENGTH
|
137
|
+
arr.last << "#{word} "
|
138
|
+
arr
|
139
|
+
end.map(&:strip)
|
140
|
+
end
|
141
|
+
|
142
|
+
def build_message_array(arr)
|
143
|
+
arr.each_with_index.map do |message, index|
|
144
|
+
message.size > MAX_MESSAGE_LENGTH ? string_to_irc_lines(message) : arr[index]
|
145
|
+
end.flatten
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/rubot.rb
ADDED
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Chris Thorn
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-04 00:00:00 -09:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: A Ruby Bot framwork for IRC featuring reloadable commands and listeners.
|
22
|
+
email: thorncp@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README
|
29
|
+
files:
|
30
|
+
- lib/core/command.rb
|
31
|
+
- lib/core/dispatcher.rb
|
32
|
+
- lib/core/listener.rb
|
33
|
+
- lib/core/runner.rb
|
34
|
+
- lib/core.rb
|
35
|
+
- lib/extensions/kernel.rb
|
36
|
+
- lib/extensions/string.rb
|
37
|
+
- lib/extensions.rb
|
38
|
+
- lib/init/bundler.rb
|
39
|
+
- lib/irc/constants.rb
|
40
|
+
- lib/irc/message.rb
|
41
|
+
- lib/irc/server.rb
|
42
|
+
- lib/irc.rb
|
43
|
+
- lib/rubot.rb
|
44
|
+
- README
|
45
|
+
- Rakefile
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://github.com/thorncp/rubot
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --line-numbers
|
53
|
+
- --inline-source
|
54
|
+
- --title
|
55
|
+
- NiftyGenerators
|
56
|
+
- --main
|
57
|
+
- README.rdoc
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
segments:
|
72
|
+
- 1
|
73
|
+
- 2
|
74
|
+
version: "1.2"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.3.6
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: A Ruby Bot framwork for IRC
|
82
|
+
test_files: []
|
83
|
+
|