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 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
+