ircbot 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +43 -21
- data/Rakefile +9 -6
- data/bin/ircbot +3 -27
- data/lib/ircbot.rb +12 -15
- data/lib/ircbot/client.rb +32 -0
- data/lib/ircbot/client/commands.rb +34 -0
- data/lib/ircbot/client/config.rb +55 -0
- data/lib/ircbot/client/config/channels.rb +40 -0
- data/lib/ircbot/client/config/plugins.rb +16 -0
- data/lib/ircbot/client/encoding.rb +17 -0
- data/lib/ircbot/client/plugins.rb +64 -0
- data/lib/ircbot/core_ext/delegation.rb +135 -0
- data/lib/ircbot/core_ext/extending.rb +80 -0
- data/lib/ircbot/core_ext/message.rb +18 -0
- data/lib/ircbot/framework.rb +22 -40
- data/lib/ircbot/plugin.rb +63 -0
- data/lib/ircbot/plugins.rb +141 -0
- data/lib/ircbot/version.rb +4 -0
- data/plugins/echo.rb +14 -0
- data/plugins/irc.rb +19 -0
- data/plugins/plugins.rb +59 -0
- data/spec/config_spec.rb +83 -0
- data/spec/fixtures/sama-zu.yml +8 -0
- data/spec/framework_spec.rb +18 -0
- data/spec/its_helper.rb +15 -0
- data/spec/plugin_spec.rb +70 -0
- data/spec/plugins_spec.rb +30 -0
- data/spec/provide_helper.rb +36 -0
- data/spec/spec_helper.rb +16 -0
- metadata +40 -19
- data/lib/irc/agent.rb +0 -177
- data/lib/irc/client.rb +0 -476
- data/lib/irc/const.rb +0 -242
- data/lib/irc/irc.rb +0 -324
- data/lib/irc/localize.rb +0 -260
- data/lib/ircbot/agent_manager.rb +0 -89
- data/lib/ircbot/config_client.rb +0 -369
- data/lib/ircbot/core_ext/digest.rb +0 -14
- data/lib/ircbot/core_ext/irc.rb +0 -61
- data/lib/ircbot/core_ext/rand-polimorphism.rb +0 -23
- data/lib/ircbot/core_ext/writefile.rb +0 -45
- data/lib/ircbot/ordered_hash.rb +0 -91
- data/lib/ircbot/reply_client.rb +0 -328
@@ -0,0 +1,135 @@
|
|
1
|
+
class Module
|
2
|
+
# Provides a delegate class method to easily expose contained objects' methods
|
3
|
+
# as your own. Pass one or more methods (specified as symbols or strings)
|
4
|
+
# and the name of the target object as the final <tt>:to</tt> option (also a symbol
|
5
|
+
# or string). At least one method and the <tt>:to</tt> option are required.
|
6
|
+
#
|
7
|
+
# Delegation is particularly useful with Active Record associations:
|
8
|
+
#
|
9
|
+
# class Greeter < ActiveRecord::Base
|
10
|
+
# def hello() "hello" end
|
11
|
+
# def goodbye() "goodbye" end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class Foo < ActiveRecord::Base
|
15
|
+
# belongs_to :greeter
|
16
|
+
# delegate :hello, :to => :greeter
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# Foo.new.hello # => "hello"
|
20
|
+
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
|
21
|
+
#
|
22
|
+
# Multiple delegates to the same target are allowed:
|
23
|
+
#
|
24
|
+
# class Foo < ActiveRecord::Base
|
25
|
+
# belongs_to :greeter
|
26
|
+
# delegate :hello, :goodbye, :to => :greeter
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Foo.new.goodbye # => "goodbye"
|
30
|
+
#
|
31
|
+
# Methods can be delegated to instance variables, class variables, or constants
|
32
|
+
# by providing them as a symbols:
|
33
|
+
#
|
34
|
+
# class Foo
|
35
|
+
# CONSTANT_ARRAY = [0,1,2,3]
|
36
|
+
# @@class_array = [4,5,6,7]
|
37
|
+
#
|
38
|
+
# def initialize
|
39
|
+
# @instance_array = [8,9,10,11]
|
40
|
+
# end
|
41
|
+
# delegate :sum, :to => :CONSTANT_ARRAY
|
42
|
+
# delegate :min, :to => :@@class_array
|
43
|
+
# delegate :max, :to => :@instance_array
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# Foo.new.sum # => 6
|
47
|
+
# Foo.new.min # => 4
|
48
|
+
# Foo.new.max # => 11
|
49
|
+
#
|
50
|
+
# Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
|
51
|
+
# is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
|
52
|
+
# delegated to.
|
53
|
+
#
|
54
|
+
# Person = Struct.new(:name, :address)
|
55
|
+
#
|
56
|
+
# class Invoice < Struct.new(:client)
|
57
|
+
# delegate :name, :address, :to => :client, :prefix => true
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# john_doe = Person.new("John Doe", "Vimmersvej 13")
|
61
|
+
# invoice = Invoice.new(john_doe)
|
62
|
+
# invoice.client_name # => "John Doe"
|
63
|
+
# invoice.client_address # => "Vimmersvej 13"
|
64
|
+
#
|
65
|
+
# It is also possible to supply a custom prefix.
|
66
|
+
#
|
67
|
+
# class Invoice < Struct.new(:client)
|
68
|
+
# delegate :name, :address, :to => :client, :prefix => :customer
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# invoice = Invoice.new(john_doe)
|
72
|
+
# invoice.customer_name # => "John Doe"
|
73
|
+
# invoice.customer_address # => "Vimmersvej 13"
|
74
|
+
#
|
75
|
+
# If the object to which you delegate can be nil, you may want to use the
|
76
|
+
# :allow_nil option. In that case, it returns nil instead of raising a
|
77
|
+
# NoMethodError exception:
|
78
|
+
#
|
79
|
+
# class Foo
|
80
|
+
# attr_accessor :bar
|
81
|
+
# def initialize(bar = nil)
|
82
|
+
# @bar = bar
|
83
|
+
# end
|
84
|
+
# delegate :zoo, :to => :bar
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
|
88
|
+
#
|
89
|
+
# class Foo
|
90
|
+
# attr_accessor :bar
|
91
|
+
# def initialize(bar = nil)
|
92
|
+
# @bar = bar
|
93
|
+
# end
|
94
|
+
# delegate :zoo, :to => :bar, :allow_nil => true
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# Foo.new.zoo # returns nil
|
98
|
+
#
|
99
|
+
def delegate(*methods)
|
100
|
+
options = methods.pop
|
101
|
+
unless options.is_a?(Hash) && to = options[:to]
|
102
|
+
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
|
103
|
+
end
|
104
|
+
|
105
|
+
if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/
|
106
|
+
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
|
107
|
+
end
|
108
|
+
|
109
|
+
prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_"
|
110
|
+
|
111
|
+
file, line = caller.first.split(':', 2)
|
112
|
+
line = line.to_i
|
113
|
+
|
114
|
+
methods.each do |method|
|
115
|
+
on_nil =
|
116
|
+
if options[:allow_nil]
|
117
|
+
'return'
|
118
|
+
else
|
119
|
+
%(raise "#{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
|
120
|
+
end
|
121
|
+
|
122
|
+
module_eval(<<-EOS, file, line)
|
123
|
+
def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
|
124
|
+
#{to}.__send__(#{method.inspect}, *args, &block) # client.__send__(:name, *args, &block)
|
125
|
+
rescue NoMethodError # rescue NoMethodError
|
126
|
+
if #{to}.nil? # if client.nil?
|
127
|
+
#{on_nil}
|
128
|
+
else # else
|
129
|
+
raise # raise
|
130
|
+
end # end
|
131
|
+
end # end
|
132
|
+
EOS
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class Object
|
2
|
+
def remove_subclasses_of(*superclasses) #:nodoc:
|
3
|
+
Class.remove_class(*subclasses_of(*superclasses))
|
4
|
+
end
|
5
|
+
|
6
|
+
begin
|
7
|
+
ObjectSpace.each_object(Class.new) {}
|
8
|
+
|
9
|
+
# Exclude this class unless it's a subclass of our supers and is defined.
|
10
|
+
# We check defined? in case we find a removed class that has yet to be
|
11
|
+
# garbage collected. This also fails for anonymous classes -- please
|
12
|
+
# submit a patch if you have a workaround.
|
13
|
+
def subclasses_of(*superclasses) #:nodoc:
|
14
|
+
subclasses = []
|
15
|
+
|
16
|
+
superclasses.each do |sup|
|
17
|
+
ObjectSpace.each_object(class << sup; self; end) do |k|
|
18
|
+
if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
|
19
|
+
subclasses << k
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
subclasses
|
25
|
+
end
|
26
|
+
rescue RuntimeError
|
27
|
+
# JRuby and any implementations which cannot handle the objectspace traversal
|
28
|
+
# above fall back to this implementation
|
29
|
+
def subclasses_of(*superclasses) #:nodoc:
|
30
|
+
subclasses = []
|
31
|
+
|
32
|
+
superclasses.each do |sup|
|
33
|
+
ObjectSpace.each_object(Class) do |k|
|
34
|
+
if superclasses.any? { |superclass| k < superclass } &&
|
35
|
+
(k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
|
36
|
+
subclasses << k
|
37
|
+
end
|
38
|
+
end
|
39
|
+
subclasses.uniq!
|
40
|
+
end
|
41
|
+
subclasses
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def extended_by #:nodoc:
|
46
|
+
ancestors = class << self; ancestors end
|
47
|
+
ancestors.select { |mod| mod.class == Module } - [ Object, Kernel ]
|
48
|
+
end
|
49
|
+
|
50
|
+
def extend_with_included_modules_from(object) #:nodoc:
|
51
|
+
object.extended_by.each { |mod| extend mod }
|
52
|
+
end
|
53
|
+
|
54
|
+
unless defined? instance_exec # 1.9
|
55
|
+
module InstanceExecMethods #:nodoc:
|
56
|
+
end
|
57
|
+
include InstanceExecMethods
|
58
|
+
|
59
|
+
# Evaluate the block with the given arguments within the context of
|
60
|
+
# this object, so self is set to the method receiver.
|
61
|
+
#
|
62
|
+
# From Mauricio's http://eigenclass.org/hiki/bounded+space+instance_exec
|
63
|
+
def instance_exec(*args, &block)
|
64
|
+
begin
|
65
|
+
old_critical, Thread.critical = Thread.critical, true
|
66
|
+
n = 0
|
67
|
+
n += 1 while respond_to?(method_name = "__instance_exec#{n}")
|
68
|
+
InstanceExecMethods.module_eval { define_method(method_name, &block) }
|
69
|
+
ensure
|
70
|
+
Thread.critical = old_critical
|
71
|
+
end
|
72
|
+
|
73
|
+
begin
|
74
|
+
send(method_name, *args)
|
75
|
+
ensure
|
76
|
+
InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
######################################################################
|
2
|
+
### core_ext
|
3
|
+
|
4
|
+
class Net::IRC::Message
|
5
|
+
def channel
|
6
|
+
params[0]
|
7
|
+
end
|
8
|
+
|
9
|
+
def message
|
10
|
+
params[1]
|
11
|
+
end
|
12
|
+
|
13
|
+
def reply(client, text)
|
14
|
+
to = (client.config.nick == params[0]) ? prefix.nick : params[0]
|
15
|
+
client.privmsg to, text.to_s unless text.to_s.empty?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
data/lib/ircbot/framework.rb
CHANGED
@@ -1,50 +1,32 @@
|
|
1
1
|
module Ircbot
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
class InvalidFilename < SecurityError; end
|
6
|
-
class FileNotFound < SecurityError; end
|
7
|
-
|
8
|
-
class Recover < RuntimeError
|
9
|
-
attr_reader :wait
|
10
|
-
|
11
|
-
def initialize(wait = 300)
|
12
|
-
@wait = wait
|
13
|
-
end
|
2
|
+
def paths
|
3
|
+
@paths ||= Mash.new
|
14
4
|
end
|
15
5
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
def root=(value)
|
22
|
-
@root = value
|
23
|
-
end
|
6
|
+
def root
|
7
|
+
@root || Pathname(Dir.pwd).expand_path
|
8
|
+
end
|
24
9
|
|
25
|
-
|
26
|
-
|
27
|
-
|
10
|
+
def system_root
|
11
|
+
(Pathname(File.dirname(__FILE__)) + ".." + "..").expand_path
|
12
|
+
end
|
28
13
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
14
|
+
def root=(value)
|
15
|
+
@root = Pathname(value)
|
16
|
+
end
|
33
17
|
|
34
|
-
|
35
|
-
|
36
|
-
|
18
|
+
def push_path(type, path)
|
19
|
+
paths[type] ||= []
|
20
|
+
paths[type] << Pathname(path)
|
21
|
+
end
|
37
22
|
|
38
|
-
|
39
|
-
|
40
|
-
|
23
|
+
def glob_for(type, name)
|
24
|
+
Array(paths[type]).reverse.select{|p| p.directory?}.map{|d|
|
25
|
+
Dir.glob(d + "**/#{name}.*")
|
26
|
+
}.flatten.compact.map{|i| Pathname(i)}
|
27
|
+
end
|
41
28
|
|
42
|
-
|
43
|
-
path.readable_real? or
|
44
|
-
raise FileNotFound, name
|
29
|
+
attr_accessor :toplevel_binding
|
45
30
|
|
46
|
-
|
47
|
-
end
|
48
|
-
end
|
31
|
+
extend self
|
49
32
|
end
|
50
|
-
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Ircbot
|
4
|
+
class Plugin
|
5
|
+
class Null
|
6
|
+
def method_missing(*)
|
7
|
+
self
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class InitialMessage < Net::IRC::Message
|
12
|
+
def initialize(nick = nil)
|
13
|
+
super nick, "PRIVMSG", ["#channel", "(initialize)"]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_accessor :message
|
18
|
+
attr_accessor :plugins
|
19
|
+
attr_accessor :running
|
20
|
+
attr_accessor :plugin_name
|
21
|
+
|
22
|
+
def initialize(plugins = nil)
|
23
|
+
@plugins = plugins || Plugins.new
|
24
|
+
@message = InitialMessage.new(self.class.name)
|
25
|
+
@running = false
|
26
|
+
end
|
27
|
+
|
28
|
+
######################################################################
|
29
|
+
### Accessors
|
30
|
+
|
31
|
+
delegate :plugin!, :client, :bot, :config, :to=>"@plugins"
|
32
|
+
|
33
|
+
def plugin_name
|
34
|
+
@plugin_name ||= Extlib::Inflection.foreign_key(self.class.name).sub(/(_plugin)?_id$/,'')
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
"<%sPlugin: %s>" % [running ? '*' : '', plugin_name]
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
"%s%s" % [running ? '*' : '', plugin_name]
|
43
|
+
end
|
44
|
+
|
45
|
+
######################################################################
|
46
|
+
### Operations
|
47
|
+
|
48
|
+
def help
|
49
|
+
raise "no helps for #{plugin_name}"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def plugin(name)
|
54
|
+
plugin!(name)
|
55
|
+
rescue
|
56
|
+
Null.new
|
57
|
+
end
|
58
|
+
|
59
|
+
def direct?
|
60
|
+
message.channel == config.nick
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Ircbot
|
4
|
+
class PluginNotFound < RuntimeError; end
|
5
|
+
|
6
|
+
class Plugins
|
7
|
+
attr_reader :plugins
|
8
|
+
attr_reader :client
|
9
|
+
delegate :config, :to=>"client"
|
10
|
+
|
11
|
+
def initialize(client = nil, plugins = nil)
|
12
|
+
@client = client || Client::Standalone.new
|
13
|
+
@plugins = Dictionary.new
|
14
|
+
|
15
|
+
load_plugins Array(plugins)
|
16
|
+
end
|
17
|
+
|
18
|
+
######################################################################
|
19
|
+
### Enumerable
|
20
|
+
|
21
|
+
include Enumerable
|
22
|
+
|
23
|
+
def each(&block)
|
24
|
+
plugins.values.__send__(:each, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
######################################################################
|
28
|
+
### Accessors
|
29
|
+
|
30
|
+
def plugin!(name)
|
31
|
+
find_plugin!(name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def plugin(name)
|
35
|
+
plugin!(name) rescue nil
|
36
|
+
end
|
37
|
+
alias :[] :plugin!
|
38
|
+
|
39
|
+
def bot
|
40
|
+
client
|
41
|
+
end
|
42
|
+
|
43
|
+
######################################################################
|
44
|
+
### Operations
|
45
|
+
|
46
|
+
def start(plugin)
|
47
|
+
find_plugin(plugin).running = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def stop(plugin)
|
51
|
+
find_plugin(plugin).running = false
|
52
|
+
end
|
53
|
+
|
54
|
+
def load(plugin)
|
55
|
+
load_plugins(plugin)
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete(plugin)
|
59
|
+
name = plugin.is_a?(Plugin) ? plugin.name : plugin.to_s
|
60
|
+
plugin!(name)
|
61
|
+
@plugins.delete(name)
|
62
|
+
end
|
63
|
+
|
64
|
+
######################################################################
|
65
|
+
### IO
|
66
|
+
|
67
|
+
def load_plugins(plugin)
|
68
|
+
case plugin
|
69
|
+
when Array
|
70
|
+
plugin.each do |name|
|
71
|
+
self << name
|
72
|
+
end
|
73
|
+
when Plugin
|
74
|
+
plugin.plugins = self
|
75
|
+
plugins[plugin.plugin_name] = plugin
|
76
|
+
plugin.running = true
|
77
|
+
when Class
|
78
|
+
if plugin.ancestors.include?(Ircbot::Plugin)
|
79
|
+
self << plugin.new(self)
|
80
|
+
else
|
81
|
+
raise ArgumentError, "#{plugin} is not Ircbot::Plugin"
|
82
|
+
end
|
83
|
+
when String, Symbol
|
84
|
+
begin
|
85
|
+
name = plugin.to_s
|
86
|
+
unless @plugins[name]
|
87
|
+
self << load_plugin(name)
|
88
|
+
end
|
89
|
+
rescue Exception => e
|
90
|
+
broadcast "Plugin error(#{name}): #{e}[#{e.class}]"
|
91
|
+
end
|
92
|
+
else
|
93
|
+
raise NotImplementedError, "#<< for #{plugin.class}"
|
94
|
+
end
|
95
|
+
return self
|
96
|
+
end
|
97
|
+
alias :<< :load_plugins
|
98
|
+
|
99
|
+
def load_plugin(name)
|
100
|
+
path = Ircbot.glob_for(:plugin, name).first or
|
101
|
+
raise PluginNotFound, name.to_s
|
102
|
+
|
103
|
+
script = path.read{}
|
104
|
+
eval(script, Ircbot.toplevel_binding)
|
105
|
+
|
106
|
+
class_name = Extlib::Inflection.camelize(name) + "Plugin"
|
107
|
+
return Object.const_get(class_name).new
|
108
|
+
|
109
|
+
# Object.subclasses_of(Plugin).each do |k|
|
110
|
+
# return k.new if Extlib::Inflection.demodulize(k.name) =~ /^#{class_name}(Plugin)?$/
|
111
|
+
# end
|
112
|
+
|
113
|
+
rescue NameError => e
|
114
|
+
raise LoadError, "Expected #{path} to define #{class_name} (#{e})"
|
115
|
+
end
|
116
|
+
|
117
|
+
def active
|
118
|
+
select(&:running)
|
119
|
+
end
|
120
|
+
|
121
|
+
def inspect
|
122
|
+
plugins.values.inspect
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
def find_plugin!(plugin)
|
127
|
+
case plugin
|
128
|
+
when String, Symbol
|
129
|
+
@plugins[plugin.to_s] or raise PluginNotFound, plugin.to_s
|
130
|
+
when Plugin
|
131
|
+
plugin
|
132
|
+
else
|
133
|
+
raise PluginNotFound, "#{plugin.class}(#{plugin})"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def broadcast(text)
|
138
|
+
p text
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|