rubot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,8 @@
1
+ module Rubot
2
+ module Core
3
+ smartload :Dispatcher
4
+ smartload :Command
5
+ smartload :Listener
6
+ smartload :Runner
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,13 @@
1
+ module Rubot
2
+ module Core
3
+ class Listener
4
+ def initialize(dispatcher)
5
+ @dispatcher = dispatcher
6
+ end
7
+
8
+ def execute(server, message)
9
+ puts "#{self} listener does not implement execute method"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Rubot
2
+ module Core
3
+ class BaseRunner
4
+
5
+ def initialize(dispatcher)
6
+ @dispatcher = dispatcher
7
+ end
8
+
9
+ def run(server)
10
+ end
11
+ end
12
+ end
13
+ end
data/lib/extensions.rb ADDED
@@ -0,0 +1,4 @@
1
+ dir = File.dirname(__FILE__)
2
+
3
+ require "#{dir}/extensions/string"
4
+ require "#{dir}/extensions/kernel"
@@ -0,0 +1,6 @@
1
+ module Kernel
2
+ def smartload(name)
3
+ # find a less hackish way to do this
4
+ require "#{File.dirname(__FILE__)}/../#{File.basename(self.to_s.underscore, '.rb')}/#{name.to_s.underscore}"
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ class String
2
+ def camelize
3
+ self.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
4
+ end
5
+
6
+ def underscore
7
+ self.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ begin
2
+ # Try to require the preresolved locked set of gems.
3
+ require File.expand_path('../.bundle/environment', __FILE__)
4
+ rescue LoadError
5
+ # Fall back on doing an unlocked resolve at runtime.
6
+ require "rubygems"
7
+ require "bundler"
8
+ Bundler.setup
9
+ end
data/lib/irc.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Rubot
2
+ module Irc
3
+ smartload :Constants
4
+ smartload :Message
5
+ smartload :Server
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module Rubot
2
+ module Irc
3
+ module Constants
4
+ MAX_MESSAGE_LENGTH = 400
5
+
6
+ WELLCOME = 1
7
+ ERR_NICK_IN_USE = 433
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ module Rubot
2
+ module Irc
3
+ class Message
4
+ attr_accessor :destination, :body
5
+ attr_reader :from
6
+
7
+ def initialize(from, destination, body)
8
+ @from = from
9
+ @destination = destination
10
+ @body = body
11
+ end
12
+ end
13
+ end
14
+ end
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
@@ -0,0 +1,12 @@
1
+ module Rubot
2
+
3
+ dir = File.dirname(__FILE__)
4
+
5
+ # run initializers
6
+ Dir["#{dir}/init/*.rb"].each {|file| require file}
7
+
8
+ # load rubot
9
+ require "#{dir}/extensions"
10
+ require "#{dir}/core"
11
+ require "#{dir}/irc"
12
+ end
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
+