ircbot 0.0.2 → 0.1.0
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 +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
|