crazydoll 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.md ADDED
@@ -0,0 +1,26 @@
1
+ # CrazyDoll
2
+
3
+ ## Whats?
4
+ CrazyDoll is a simple IRC bot that aim flexibility.
5
+ With CrazyDoll you can:
6
+
7
+ * Use a different backend for config like MySQL, PostgreSQL, FireBird
8
+ or any other ruby supported backend(currently only using YAML)
9
+ * Run multiple bots on same ruby process.
10
+ * Bots do not share the plugins. You can can have join and nick plugin
11
+ to Bot A and nick and search plugin to Bot B
12
+ * Use a different IRC library
13
+ * More coming soon...
14
+
15
+
16
+ ## How I install?
17
+ Only run `gem install crazydoll`
18
+
19
+ ## How I use it?
20
+ Given that the path of bin folder is in your $PATH variable, only run `crazy_doll`
21
+ In first time it will run a wizard to add basic info
22
+ If you want to re-run this wizard, only delete `~/.crazy_doll/database.yaml`
23
+
24
+ ## License
25
+ This project is licensed under MIT license.
26
+ See MIT-LICENSE file for more details.
data/bin/crazy_doll ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ #encoding: utf8
3
+
4
+ file = File.join(File.dirname(__FILE__), '..', 'lib', 'crazy_doll.rb')
5
+ if FileTest.file?(file)
6
+ require file
7
+ else
8
+ require 'rubygems'
9
+ require 'crazy_doll'
10
+ end
11
+
12
+ CrazyDoll.init
data/lib/crazy_doll.rb ADDED
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH << File.dirname(File.expand_path(__FILE__))
2
+
3
+ require 'rubygems'
4
+ require 'net/irc'
5
+ require 'httparty'
6
+ require 'crazy_doll/core'
7
+ require 'crazy_doll/fixes'
8
+ require 'crazy_doll/config'
9
+ require 'crazy_doll/config_backend/yaml'
10
+ require 'crazy_doll/events'
11
+ require 'crazy_doll/irc'
12
+ require 'crazy_doll/plugin_manager'
13
+ require 'crazy_doll/message'
14
+ require 'crazy_doll/bot'
15
+ require 'crazy_doll/wizard'
@@ -0,0 +1,25 @@
1
+ class CrazyDoll::Bot
2
+
3
+
4
+ attr_reader :irc, :event_manager, :plugin_manager, :config
5
+ def initialize(config=nil)
6
+ @config = config || CrazyDoll::Config.new('yaml', File.join(ENV['HOME'], '.crazy_doll','database.yaml'))
7
+ core_config = @config.config_of 'Core'
8
+ @irc = CrazyDoll::IRC.new(core_config.server, core_config.port)
9
+ @event_manager = CrazyDoll::EventManager.new(@irc)
10
+ @plugin_manager = CrazyDoll::PluginManager
11
+ @plugin_manager.load_plugins
12
+ @plugin_manager.find_usable_errors
13
+ @plugin_manager.register_plugins(@event_manager, @config)
14
+ end
15
+
16
+ def start
17
+ @config.start_use
18
+ begin
19
+ @irc.start
20
+ ensure
21
+ @config.end_use
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,131 @@
1
+ require 'yaml'
2
+
3
+ module CrazyDoll::Config
4
+
5
+ def self.new(type, *args)
6
+ case type
7
+ when /yaml/i then Backend::YAML.new(*args)
8
+ else raise ArgumentError, "no config backend #{type.inspect}"
9
+ end
10
+ end
11
+
12
+ def self.parse(owner, key, description, value)
13
+ out = setup_class(value.class) unless value.respond_to?(:crazy_doll_setuped?) and value.to_yaml('class_setuped?') == 'YES SIR !'
14
+ return value if value.crazy_doll_setuped?
15
+ value.owner = owner
16
+ value.key = key
17
+ value.description = description
18
+ value
19
+ end
20
+
21
+ def self.setup_class(klass)
22
+ klass.class_eval do
23
+ def crazy_doll_setuped?
24
+ @owner and @key and @description ? true : false
25
+ end
26
+
27
+ alias :crazy_doll_setuped :crazy_doll_setuped?
28
+
29
+ [['owner', 'to_s'], ['key', 'to_sym'], ['description', 'to_s']].each do |name,m|
30
+ class_eval <<-EVAL
31
+ def #{name}
32
+ @#{name} || nil
33
+ end
34
+
35
+ def #{name}=(value)
36
+ raise ArgumentError, "can't redefine #{name}" if defined?(@#{name})
37
+ @#{name} = value.#{m}
38
+ end
39
+ EVAL
40
+ end
41
+
42
+ alias :__to_yaml :to_yaml
43
+
44
+ def _to_yaml(opts = {})
45
+ return __to_yaml(opts) unless crazy_doll_setuped?
46
+ a, b, c = @owner, @key, @description
47
+ ['@owner', '@key', '@description'].each { |x| remove_instance_variable(x) }
48
+ begin
49
+ return __to_yaml(opts)
50
+ ensure
51
+ @owner, @key, @description = a, b, c
52
+ end
53
+ end
54
+
55
+ def to_yaml( opts = {} )
56
+ return 'YES SIR !' if opts == 'class_setuped?'
57
+ return _to_yaml(opts) unless crazy_doll_setuped?
58
+ YAML.quick_emit( self.object_id, opts ) do |out|
59
+ out.map( "tag:crazydoll.org,2010:ConfigVar", "crazydoll.org,2010/ConfigVar" ) do |map|
60
+ map.add('owner', self.owner )
61
+ map.add('key', self.key )
62
+ map.add('description', self.description)
63
+ map.add('value', self._to_yaml )
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ class Instance
71
+
72
+ attr_reader :config, :owner
73
+ def initialize(owner, config={})
74
+ @config = config.empty? ? {} : config
75
+ @owner = owner
76
+ @config.keys.each { |k| create_key(k) }
77
+ end
78
+
79
+ def register(owner, key, description, value)
80
+ k = key.to_sym
81
+ return false if @config[k]
82
+ @config[k] = CrazyDoll::Config.parse(@owner, key, description, value)
83
+ create_key(k)
84
+ end
85
+
86
+ private
87
+ def create_key(name)
88
+ self.instance_eval <<-EVAL
89
+ def #{name}
90
+ @config[:#{name}]
91
+ end
92
+
93
+ def #{name}=(val)
94
+ c = @config[:#{name}]
95
+ key = c.key
96
+ desc = c.description
97
+ @config[:#{name}] = CrazyDoll::Config.parse(@owner, key, desc, val)
98
+ end
99
+ EVAL
100
+ end
101
+
102
+ def method_missing(name, *args, &block)
103
+ if name.to_s.match(/=$/) and args.size == 1
104
+ key = name.to_s.gsub(/=$/,'').to_sym
105
+ puts "!!! key #{key} missing, but creating !!!"
106
+ @config[key] = CrazyDoll::Config.parse(@owner, key, 'created by method_missing', args[0])
107
+ create_key(key)
108
+ @config[key]
109
+ elsif name.to_s.match(/\?$/) and self.respond_to?((k=name.to_s.gsub(/\?$/,'')))
110
+ puts "!!! method #{name} missing, creating it !!!"
111
+ self.instance_eval <<-EVAL
112
+ def #{name}
113
+ @config[:#{k}]
114
+ end
115
+ EVAL
116
+ send(name)
117
+ else
118
+ puts "!!! method #{name} missing !!!"
119
+ nil
120
+ end
121
+ end
122
+
123
+ end
124
+
125
+ end
126
+
127
+ CrazyDoll::Config.setup_class(Object)
128
+
129
+ YAML.add_domain_type('crazydoll.org,2010', 'ConfigVar') do |type,val|
130
+ CrazyDoll::Config.parse(val['owner'], val['key'], val['description'], YAML.load(val['value']))
131
+ end
@@ -0,0 +1,69 @@
1
+ require 'thread'
2
+ require 'fileutils'
3
+
4
+ module CrazyDoll::Config::Backend
5
+
6
+ class YAML
7
+
8
+ attr_reader :instances
9
+ def initialize(file)
10
+ @file = file
11
+ self.load
12
+ end
13
+
14
+ def start_use
15
+ @thread = Thread.new(self) do |b|
16
+ loop do
17
+ sleep 60
18
+ b.save
19
+ end
20
+ end
21
+ end
22
+
23
+ def end_use
24
+ @thread.kill if @thread and @thread.alive?
25
+ end
26
+
27
+ def setup_table_if_needed
28
+ FileUtis.mkdir_p(File.dirname(@file)) unless FileTest.directory?(File.dirname(@file))
29
+ File.open(@file, "w+") { |f| f.write(::YAML.dump({})) } unless FileTest.file?(@file)
30
+ end
31
+
32
+ def create_instances(data=nil)
33
+ return false unless data.is_a?(Hash)
34
+ @instances ||= {}
35
+ keys = data.keys
36
+ keys.each do |k|
37
+ key = real_key(k)
38
+ @instances[key] = CrazyDoll::Config::Instance.new(key, data[k])
39
+ end
40
+ end
41
+
42
+ def load
43
+ setup_table_if_needed
44
+ create_instances(::YAML.load_file(@file))
45
+ end
46
+
47
+ def save
48
+ setup_table_if_needed
49
+ d = {}
50
+ @instances.each { |k,v| d[k] = v.config }
51
+ File.open(@file, 'w+') { |f| f.write(::YAML.dump(d)) }
52
+ end
53
+
54
+ def config_of(key)
55
+ key = real_key(key)
56
+ @instances[key] ||= CrazyDoll::Config::Instance.new(key, {})
57
+ end
58
+
59
+ def real_key(k)
60
+ [String, Symbol].include?(k.class) ? k.to_s : k.class.to_s
61
+ end
62
+
63
+ def [](key)
64
+ @instances[key]
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,14 @@
1
+ module CrazyDoll
2
+
3
+ VERSION = '0.0.1'.freeze
4
+
5
+ def self.init
6
+ config_file = File.join(ENV['HOME'], '.crazy_doll', 'database.yaml')
7
+ config_instance = CrazyDoll::Config.new('yaml', config_file)
8
+ config = config_instance.config_of 'Core'
9
+ CrazyDoll::Wizard.new(config)
10
+ config_instance.save
11
+ CrazyDoll::Bot.new(config_instance).start
12
+ end
13
+
14
+ end
@@ -0,0 +1,49 @@
1
+ class CrazyDoll::Event
2
+
3
+ attr_reader :klass, :real_method_name, :method_name
4
+ def initialize(klass, opts = {})
5
+ @klass = klass
6
+ verify(opts)
7
+ @real_method_name, @method_name = opts[:real_method_name], opts[:method_name]
8
+ end
9
+
10
+ def verify(opts)
11
+ errors = []
12
+ errors << 'need a real method name' unless @klass.respond_to?(opts[:real_method_name])
13
+ errors << 'need a method name' unless opts[:method_name]
14
+ return true if errors.empty?
15
+ raise ArgumentError, errors.join(', ')
16
+ end
17
+
18
+ def call(opts, line='')
19
+ @klass.opts = opts
20
+ @klass.line = line
21
+ @klass.send(@real_method_name) rescue nil
22
+ end
23
+
24
+ end
25
+
26
+ class CrazyDoll::EventManager
27
+
28
+ attr_reader :irc, :events, :config
29
+ def initialize(irc)
30
+ @irc = irc
31
+ @irc.event_manager = self
32
+ @events = { :sys => {}, :get => {}, :put => {} }
33
+ end
34
+
35
+ def fire(type, name, opts = {}, line = '')
36
+ type, name = type.to_sym, name.to_sym
37
+ return false unless [:sys, :get, :put].include?(type)
38
+ @events[type][name] ||= []
39
+ @events[type][name].each { |x| x.call(opts, line) }
40
+ fire(type, :command_message, opts, line) if opts.is_a?(CrazyDoll::Message) and opts.command == 'PRIVMSG' and name != :command_message
41
+ end
42
+
43
+ def register(type, klass, opts)
44
+ type, name = type.to_sym, opts[:method_name].to_sym
45
+ @events[type][name] ||= []
46
+ @events[type][name] << CrazyDoll::Event.new(klass, opts)
47
+ end
48
+
49
+ end
@@ -0,0 +1,72 @@
1
+ class Hash
2
+
3
+ def +(other_hash)
4
+ raise ArgumentError, 'hash + hash only' unless other_hash.class == Hash
5
+ new_hash = self.clone
6
+ for key in other_hash.keys
7
+ new_hash[key] = other_hash[key]
8
+ end
9
+ return new_hash
10
+ end
11
+
12
+ end
13
+
14
+ module YAML
15
+
16
+ def load_file(f)
17
+ return false unless FileTest.file?(f)
18
+ super
19
+ end
20
+
21
+ end
22
+
23
+ class String
24
+
25
+ def to_crazy_doll_regexp
26
+ x=(' ' << self).gsub(/([^\\])::(\S+)/, '\1(\S+)').gsub(/([^\\])\*\*(\S+)/, '\1(.*)').gsub(/#{0.chr}[^#{1.chr}]+#{1.chr}([^#{2.chr}]+)#{2.chr}/, '\1')
27
+ x[0] = ''
28
+ Regexp.new("^#{x}$")
29
+ end
30
+
31
+ end
32
+
33
+ class MatchData
34
+
35
+ def to_crazy_doll_params(line)
36
+ out = {}
37
+ params = (' ' << line).scan(/[^\\](::|\*\*)(\S+)|#{0.chr}([^#{1.chr}]+)#{1.chr}[^#{2.chr}]+#{2.chr}/).flatten.
38
+ map { |x| ['::', '**'].include?(x) ? nil : x }.compact
39
+ params.each_with_index do |key,idx|
40
+ out[key.to_sym] = self[idx+1]
41
+ end
42
+ OpenStruct.new(out)
43
+ end
44
+
45
+ end
46
+
47
+ Numeric.class_eval do
48
+ HUMAN_SIZES = [
49
+ [1024.0, 'K'],
50
+ [1048576.0, 'M'],
51
+ [1073741824.0, 'G'],
52
+ [1099511627776.0, 'P']
53
+ ]
54
+
55
+ def to_human_readable_size
56
+ return self if self < HUMAN_SIZES[0][0]
57
+ fsize, fletter = nil, nil
58
+
59
+ for size, letter in HUMAN_SIZES
60
+ t = self/size
61
+ if t <= 1024
62
+ fsize, fletter = size, letter
63
+ break
64
+ end
65
+ end
66
+
67
+ fsize, fletter = HUMAN_SIZES.last if fsize.nil?
68
+
69
+ return "%.03f %s" % [self/fsize, fletter]
70
+ end
71
+
72
+ end
@@ -0,0 +1,52 @@
1
+ class CrazyDoll::IRC < Net::IRC::Client
2
+
3
+ attr_accessor :event_manager
4
+
5
+ def start
6
+ # reset config
7
+ @server_config = Message::ServerConfig.new
8
+ @socket = TCPSocket.open(@host, @port)
9
+ @event_manager.fire(:sys, :socket_start)
10
+ while l = @socket.gets
11
+ begin
12
+ @log.debug "RECEIVE: #{l.chomp}"
13
+ m = Message.parse(l)
14
+ name = real_name((COMMANDS[m.command.upcase] || m.command).downcase, m)
15
+ @log.debug "TYPE: get_#{name}"
16
+ @event_manager.fire(:get, name, CrazyDoll::Message.parse(l.chomp), l.chomp)
17
+ #next if on_message(m) === true
18
+ #name = "on_#{(COMMANDS[m.command.upcase] || m.command).downcase}"
19
+ #send(name, m) if respond_to?(name)
20
+ rescue Exception => e
21
+ warn e
22
+ warn e.backtrace.join("\r\t")
23
+ raise
24
+ rescue Message::InvalidMessage
25
+ @log.error "MessageParse: " + l.inspect
26
+ end
27
+ end
28
+ rescue IOError
29
+ ensure
30
+ finish
31
+ end
32
+
33
+ public :post
34
+
35
+ def finish
36
+ super
37
+ @event_manager.fire(:sys, :socket_stop)
38
+ end
39
+
40
+ def real_name(n,m)
41
+ $msg = [n, m]
42
+ o = if n == 'privmsg'
43
+ if m[0][0].chr == '#'
44
+ 'chanmsg'
45
+ elsif m[1] == "\x01VERSION \x01"
46
+ 'version'
47
+ end
48
+ end
49
+ o.nil? ? n : o
50
+ end
51
+
52
+ end
@@ -0,0 +1,102 @@
1
+ class CrazyDoll::Message < OpenStruct
2
+
3
+ def self.parse_from(args)
4
+ return '' if args.nil?
5
+ if args.class == MatchData
6
+ new({ :nick => args[1], :user => args[2], :host => args[3] })
7
+ else
8
+ new({ :nick => args[0], :user => args[1], :host => args[2] })
9
+ end
10
+ end
11
+
12
+
13
+ def self.parse(line)
14
+
15
+ new case line
16
+ when /\A:([^!]+)!([^@]+)@(\S+) PRIVMSG (\S+) :(.*)\z/ then
17
+ {
18
+ :from => parse_from($~),
19
+ :command => "PRIVMSG",
20
+ :to => $~[4],
21
+ :message => $~[5]
22
+ }
23
+ when /\APING :(.*)$/ then
24
+ {
25
+ :from => $~[1],
26
+ :to => $~[1],
27
+ :command => "PING"
28
+ }
29
+ when /\A:([^!]+)!([^@]+)@(\S+) NOTICE (\S+) :(.*)\z/ then
30
+ {
31
+ :from => parse_from($~),
32
+ :to => $~[4],
33
+ :message => $~[5],
34
+ :command => "NOTICE"
35
+ }
36
+ when /\A:([^!]+)!([^@]+)@(\S+) JOIN :(.*)\z/ then
37
+ {
38
+ :from => parse_from($~),
39
+ :user => $~[1],
40
+ :channel => $~[4],
41
+ :command => "JOIN"
42
+ }
43
+ when /\A:(\S+) 353 (\S+) = (\S+) :(.*)\z/ then
44
+ {
45
+ :from => $~[1],
46
+ :me => $~[2],
47
+ :channel => $~[3],
48
+ :users => $~[4].split(" "),
49
+ :command => 353
50
+ }
51
+ when /\A:([^!]+)!([^@]+)@(\S+) NICK :(.*)\z/ then
52
+ {
53
+ :from => parse_from($~),
54
+ :command => "NICK",
55
+ :new_nick => $~[4]
56
+ }
57
+ when /\A:([^!]+)!([^@]+)@(\S+) (\S+) (\S+) :(.*)\z/ then
58
+ {
59
+ :from => parse_from($~),
60
+ :command => $~[4],
61
+ :to => $~[5],
62
+ :message => $~[6]
63
+ }
64
+ when /\A:([^!]+)!([^@]+)@(\S+) (\S+) :(.*)\z/ then
65
+ {
66
+ :from => parse_from($~),
67
+ :command => $~[4],
68
+ :to => $~[1],
69
+ :message => $~[5]
70
+ }
71
+ when /\A:(\S+) (\S+) (\S+) (\S+) :(.*)\z/ then
72
+ {
73
+ :from => $~[1],
74
+ :command => $~[2],
75
+ :to => $~[3],
76
+ :extra => $~[4],
77
+ :message => $~[5]
78
+ }
79
+ when /\A:(\S+) (\S+) (\S+) :(.*)\z/
80
+ {
81
+ :from => $~[1],
82
+ :command => $~[2],
83
+ :to => $~[3],
84
+ :message => $~[4]
85
+ }
86
+ when /\A:(\S+) (\S+) (\S+) ([^:]+) :(.*)\z/ then
87
+ {
88
+ :from => $~[1],
89
+ :command => $~[2],
90
+ :to => $~[3],
91
+ :extras => $~[4].split(' '),
92
+ :message => $~[5]
93
+ }
94
+ else
95
+ {
96
+ :extras => line.split(' ')
97
+ }
98
+ end.update({ :source => line })
99
+
100
+ end
101
+
102
+ end
@@ -0,0 +1,286 @@
1
+ module CrazyDoll::PluginManager
2
+
3
+ @path =
4
+ [
5
+ File.join(ENV['HOME'], '.crazy_doll', 'plugins'),
6
+ File.join(File.expand_path(File.dirname(__FILE__)), 'plugins')
7
+ ]
8
+ @plugins = { :success => {}, :error => {}, :not_usable => {} }
9
+
10
+ class << self
11
+
12
+ def path
13
+ @path
14
+ end
15
+
16
+ def plugins
17
+ @plugins
18
+ end
19
+
20
+ def register_plugins(event_manager, config, plugins=:all)
21
+ plugins = @plugins[:success].map { |k,v| k.to_sym } if plugins == :all
22
+ registered = []
23
+ for plugin in plugins
24
+ if @plugins[:success][plugin]
25
+ @plugins[:success][plugin][:class].new(event_manager, config)
26
+ registered << plugin
27
+ end
28
+ end
29
+ registered
30
+ end
31
+
32
+ def load_plugins
33
+ for path in @path
34
+ next unless FileTest.directory?(path)
35
+ for e in Dir.entries(path) - ['.','..']
36
+ entrie = File.join(path, e)
37
+ if entrie.match(/_plugin(\.rb)?$/)
38
+ if FileTest.file?(entrie)
39
+ load_plugin(entrie)
40
+ elsif FileTest.directory?(entrie) and FileTest.file?(File.join(entrie, 'plugin.rb'))
41
+ load_plugin(File.join(entrie, 'plugin.rb'), File.basename(entrie))
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def unload_plugins
49
+ for plugin,info in @plugins[:success]
50
+ unload_plugin(plugin)
51
+ end
52
+ @plugins = { :success => {}, :error => {} }
53
+ return true
54
+ end
55
+
56
+ def reload_plugins
57
+ unload_plugins
58
+ load_plugins
59
+ end
60
+
61
+ def unload_plugin(name)
62
+ Object.send(:remove_const, name) rescue false
63
+ end
64
+
65
+ def load_plugin(path, file_name=nil)
66
+ file_name ||= File.basename(path)
67
+ name = file_name.gsub(/\.rb$/,'').split('_').map { |x| x.capitalize }.join
68
+ begin
69
+ load path
70
+ klass = eval(name)
71
+ @plugins[:success][name.to_sym] = { :path => path, :class => klass }
72
+ rescue => e
73
+ @plugins[:error][name.to_sym] = { :path => path, :error => e }
74
+ end
75
+ end
76
+
77
+ def find_usable_errors
78
+ for name, opts in @plugins[:success]
79
+ config = opts[:class].config
80
+ _plugins = config.plugins
81
+ _gems = config.gems
82
+ p = problem_with_plugins?(_plugins)
83
+ g = problem_with_gems?(_gems)
84
+ next if p == false and g == false
85
+ plugin_failed(name, p, g)
86
+ end
87
+ end
88
+
89
+ def problem_with_plugins?(plugins)
90
+ return false
91
+ # errors = []
92
+ # for plugin in plugins
93
+ # end
94
+ end
95
+
96
+ def problem_with_gems?(gems)
97
+ errors = {}
98
+ for g,opts in gems
99
+ begin
100
+ gem g.to_s, opts[:version] || '>= 0'
101
+ require opts[:lib] || g.to_s
102
+ rescue Gem::LoadError => e
103
+ errors[g] = e
104
+ end
105
+ end
106
+ return false if errors.empty?
107
+ errors
108
+ end
109
+
110
+ def plugin_failed(name, p, g)
111
+ @plugins[:not_usable][name] = @plugins[:success].delete(name).update({:plugins => p, :gems => g })
112
+ end
113
+
114
+ end
115
+
116
+ class Config
117
+
118
+ attr_reader :gems, :plugins, :keys, :custom_messages
119
+ def initialize(klass)
120
+ @klass = klass
121
+ @gems, @plugins, @keys, @custom_messages = {}, {}, {}, []
122
+ end
123
+
124
+ def gem(name, opts = {})
125
+ @gems[name.to_sym] = opts
126
+ end
127
+
128
+ def plugin(name, opts = {})
129
+ @plugins[name.to_sym] = opts
130
+ end
131
+
132
+ def key(name, value, description='')
133
+ @keys[name.to_sym] = [description, value]
134
+ end
135
+
136
+ def register(name, line, method, where=[:chan, :priv], regexp=nil)
137
+ line = '' unless line.is_a?(String)
138
+ line = "#{name}#{line.empty? ? '' : ' ' + line}"
139
+ @custom_messages << [regexp || line.to_crazy_doll_regexp, line, method, [where].flatten]
140
+ end
141
+
142
+ end
143
+
144
+ end
145
+
146
+ class CrazyDoll::Plugin
147
+
148
+ include Net::IRC::Constants
149
+
150
+ attr_accessor :opts, :line
151
+ attr_reader :params
152
+ def initialize(manager, config)
153
+ @manager = manager
154
+ @irc = manager.irc
155
+ @my_config = config.config_of self.class.to_s
156
+ @master_config = config.config_of 'Core'
157
+ @config = config
158
+ @opts, @line, @params = {}, '', {}
159
+ register_keys
160
+ register_events
161
+ init if respond_to?(:init)
162
+ end
163
+
164
+ def register_events
165
+ for method in methods.grep(/^(sys|get|put)_/)
166
+ type, name = method.split('_', 2)
167
+ @manager.register(type, self, { :method_name => name, :real_method_name => method })
168
+ end
169
+ @manager.register(:get, self, { :method_name => :chanmsg, :real_method_name => :custom_messages_chan })
170
+ @manager.register(:get, self, { :method_name => :privmsg, :real_method_name => :custom_messages_priv })
171
+ end
172
+
173
+ def register_keys
174
+ for key,opts in self.class.config.keys
175
+ c.register(self, key, opts[0], opts[1])
176
+ end
177
+ end
178
+
179
+ def custom_messages(prefix='!', type=:priv)
180
+ reg = Regexp.new("^#{prefix}")
181
+ return unless opts.message =~ reg
182
+ message = opts.message.gsub(reg, '')
183
+
184
+ for regexp, original_line, method, where in self.class.config.custom_messages
185
+ if where.include?(type) and message =~ regexp
186
+ @params = $~.to_crazy_doll_params(original_line)
187
+ send(method)
188
+ end
189
+ end
190
+ end
191
+
192
+ def custom_messages_chan
193
+ custom_messages('!', :chan)
194
+ end
195
+
196
+ def custom_messages_priv
197
+ custom_messages('', :priv)
198
+ end
199
+
200
+ def post(*args)
201
+ @irc.post(*args)
202
+ end
203
+
204
+ def config
205
+ @master_config
206
+ end
207
+
208
+ def c
209
+ @my_config
210
+ end
211
+
212
+ def current_nick
213
+ config.nick.name
214
+ end
215
+
216
+ def reply_to
217
+ case opts.command
218
+ when 'PRIVMSG' then opts.to.match(/^#/) ? opts.to : opts.from.nick
219
+ when 'JOIN' then opts.from.nick
220
+ end
221
+ end
222
+
223
+ def current_channel
224
+ if opts.respond_to?(:channel)
225
+ opts.channel
226
+ elsif opts.respond_to?(:to) and opts.to =~ /^#/
227
+ opts.to
228
+ else
229
+ raise ArgumentError, "opts don't respond to :channel and :to isn't a channel"
230
+ end
231
+ end
232
+
233
+ def join(chan, pass=nil)
234
+ post JOIN, chan, pass
235
+ end
236
+
237
+ def say(to, message)
238
+ post PRIVMSG, to, message
239
+ end
240
+
241
+ def reply(message, with_nick=true)
242
+ message = parse_message(message)
243
+ case opts.command
244
+ when 'PRIVMSG' then say reply_to, message
245
+ when 'JOIN' then say current_channel, message
246
+ else
247
+ p opts
248
+ raise ArgumentError, "can't reply"
249
+ end
250
+ end
251
+
252
+ def get(url)
253
+ HTTParty.get(url)
254
+ end
255
+
256
+ def parse_message(message)
257
+ message.
258
+ gsub('{{me}}', current_nick).
259
+ gsub('{{to}}', reply_to).
260
+ gsub('{{channel}}', reply_to_a_channel? ? current_channel : '')
261
+ end
262
+
263
+ def reply_to_a_channel?
264
+ (reply_to || '') =~ /^#/
265
+ end
266
+
267
+ alias :reply_to_a_channel :reply_to_a_channel?
268
+
269
+ def talked_with_me?
270
+ return false unless opts.message
271
+ opts.message.downcase.include?(current_nick.downcase)
272
+ end
273
+
274
+ alias :talked_with_me :talked_with_me?
275
+
276
+ class << self
277
+
278
+ def config(&block)
279
+ @config ||= CrazyDoll::PluginManager::Config.new(self)
280
+ return @config unless block_given?
281
+ block.call(@config)
282
+ end
283
+
284
+ end
285
+
286
+ end
@@ -0,0 +1,30 @@
1
+ class JoinPlugin < CrazyDoll::Plugin
2
+
3
+ config do |c|
4
+ c.key :channels, [], 'Channels'
5
+ c.register 'join', '::channel', :join_channel
6
+ end
7
+
8
+ def init
9
+ c.channels = []
10
+ end
11
+
12
+ def join_all
13
+ for channel,password in config.channels - c.channels
14
+ join(channel, password)
15
+ end
16
+ end
17
+
18
+ def get_rpl_welcome
19
+ join_all
20
+ end
21
+
22
+ def get_join
23
+ (c.channels << opts.channel).uniq!
24
+ end
25
+
26
+ def join_channel
27
+ join(@params.channel)
28
+ end
29
+
30
+ end
@@ -0,0 +1,77 @@
1
+ class NickPlugin < CrazyDoll::Plugin
2
+
3
+ config do |c|
4
+ c.key :nick, {}, 'Current Bot Nick'
5
+ c.key :random_nick, true, 'Using a Random Nick?'
6
+ c.key :identified, false, 'Nick are identified?'
7
+ c.register 'nick', '', :change_nick
8
+ end
9
+
10
+ def init
11
+ c.identified = false
12
+ c.random_nick = true
13
+ end
14
+
15
+ def set_current_nick
16
+ if c.random_nick?
17
+ c.nick = config.nick
18
+ c.random_nick = false
19
+ else
20
+ c.nick = gen_random_nick
21
+ c.random_nick = true
22
+ end
23
+ send_nick
24
+ send_user
25
+ end
26
+
27
+ def send_nick(nick=nil)
28
+ post NICK, nick || c.nick.name
29
+ end
30
+
31
+ def send_user(user=nil, real_name=nil)
32
+ post USER, user||c.nick.user, "0", "*", real_name||c.nick.real_name
33
+ end
34
+
35
+ def gen_random_nick
36
+ letters = [*97..122].map { |x| x.chr }
37
+ OpenStruct.new({ :name => 8.times.map { letters.shuffle[0] }.join, :user => 'CrazyDoll', :real_name => 'CrazyDoll Bot' })
38
+ end
39
+
40
+ def identify(pass=nil)
41
+ say 'NickServ', "IDENTIFY #{pass||c.nick.password}" if pass or (not c.random_nick and c.nick.password)
42
+ end
43
+
44
+ def sys_socket_start
45
+ set_current_nick
46
+ end
47
+
48
+ def get_err_unavailresource
49
+ if opts.extra == c.nick.name
50
+ set_current_nick
51
+ identify
52
+ end
53
+ end
54
+
55
+ def get_rpl_welcome
56
+ identify
57
+ end
58
+
59
+ def get_ping
60
+ post PONG, opts.from
61
+ end
62
+
63
+ def get_nick
64
+ c.nick.name = opts.new_nick
65
+ end
66
+
67
+ def get_version
68
+ post NOTICE, opts.from.nick, "\x01VERSION CrazyDoll IRC Bot v.#{CrazyDoll::VERSION}\x01"
69
+ end
70
+
71
+ def change_nick
72
+ return reply "I'm already using my correct nick." if current_nick == config.nick.name
73
+ set_current_nick
74
+ identify
75
+ end
76
+
77
+ end
@@ -0,0 +1,15 @@
1
+ class SearchPlugin < CrazyDoll::Plugin
2
+
3
+ config do |c|
4
+ c.gem 'ruby-web-search'
5
+ c.key :number_of_results, 3, 'Number of results to be displayed'
6
+ c.register 'g', '**query', :google, [:chan, :priv]
7
+ end
8
+
9
+ def google
10
+ return if c.number_of_results <= 0
11
+ reply RubyWebSearch::Google.search(:query => @params.query).results[0..(c.number_of_results-1)].map { |x|
12
+ "#{CGI.unescapeHTML(x[:title])} - #{x[:url]}" }.join(" | ")
13
+ end
14
+
15
+ end
@@ -0,0 +1,34 @@
1
+ class ShowUrlInfoPlugin < CrazyDoll::Plugin
2
+
3
+ config do |c|
4
+ c.gem 'nokogiri'
5
+ c.gem 'httparty'
6
+ c.key :show_info, true, 'Should show info of urls?'
7
+ end
8
+
9
+ def get_privmsg
10
+ return unless c.show_info
11
+ for url in URI::extract(opts.message, ['http','https'])
12
+ reply url_info(url), false
13
+ end
14
+ end
15
+
16
+ alias :get_chanmsg :get_privmsg
17
+
18
+ def url_info(url)
19
+ resp = HTTParty::Request.new(Net::HTTP::Head, url)
20
+ resp.perform
21
+ lr = resp.last_response
22
+ if lr['content-type'].include?('text/html')
23
+ title = (Nokogiri::HTML(get(url))/'title').text
24
+ title = 'No title' if title.empty?
25
+ "[Site] #{title}"
26
+ else
27
+ file = File.basename(resp.uri.path).gsub(/\?.*/,'')
28
+ file = 'unknown' if file.empty?
29
+ "[file] #{file.inspect}, content-type: #{lr['content-type'].inspect}, content-length: #{lr['content-length'].to_i.to_human_readable_size}, " +
30
+ "last-modified: #{lr['last-modified'].inspect}"
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,33 @@
1
+ class WelcomePlugin < CrazyDoll::Plugin
2
+
3
+ config do |c|
4
+ c.key :messages, {}, 'Channels and messages'
5
+ c.register 'welcome', 'ignore ::user', :add_ignore, :chan
6
+ c.register 'welcome', 'message **message', :change_message, :chan
7
+ end
8
+
9
+ def get_join
10
+ chan = opts.channel
11
+ setup_chan(chan)
12
+ reply c.messages[chan][:message] if opts.user != current_nick and not c.messages[chan][:ignore].include?(opts.user) and c.messages[chan][:message]
13
+ end
14
+
15
+ def add_ignore
16
+ setup_chan(opts.to)
17
+ return reply "I'm already ignoring #{@params.user}." if c.messages[opts.to][:ignore].include?(@params.user)
18
+ c.messages[opts.to][:ignore] << @params.user
19
+ reply "I've started to ignore #{@params.user}"
20
+ end
21
+
22
+ def setup_chan(chan)
23
+ c.messages[chan] ||= {}
24
+ c.messages[chan][:ignore] ||= []
25
+ end
26
+
27
+ def change_message
28
+ setup_chan(opts.to)
29
+ c.messages[opts.to][:message] = @params.message
30
+ reply 'Message updated!'
31
+ end
32
+
33
+ end
@@ -0,0 +1,15 @@
1
+ class WellcomePlugin < CrazyDoll::Plugin
2
+
3
+ config do |c|
4
+ c.key :messages,
5
+ { '#funrer' => 'Seja bem vindo ao #funrer', '#ubuntugames' => 'Seja bem vindo ao UbuntuGames, o seu canal de jogos :)' },
6
+ 'Canais e mensagens a serem mostradas aos visitantes'
7
+ end
8
+
9
+ def get_join
10
+ channel = opts.message
11
+ post PRIVMSG, channel, "#{opts.from.nick}, #{config[:messages][channel]}" if
12
+ config[:messages][channel] and opts.from.nick != @config[:nick][:current_nick][0]
13
+ end
14
+
15
+ end
@@ -0,0 +1,45 @@
1
+ class CrazyDoll::Wizard
2
+
3
+ def initialize(config)
4
+ @config = config
5
+ run
6
+ end
7
+
8
+ def run
9
+ @config.register(self,
10
+ :server,
11
+ 'Address of IRC server',
12
+ get("Type the address of server(i.e. irc.freenode.net):", 'irc.freenode.net')) unless @config.respond_to?(:server) and @config.server.is_a?(String)
13
+ @config.register(self,
14
+ :port,
15
+ 'The port of IRC server',
16
+ get("Type the port of server(default 6667):", 6667){|x| Integer(x) rescue 6667 }) unless @config.respond_to?(:port) and @config.port.is_a?(Integer)
17
+
18
+ unless @config.respond_to?(:nick) and @config.nick.is_a?(OpenStruct) and @config.nick.name and not @config.nick.name.empty?
19
+ @config.register(self, :nick, 'Info(nick, user, name, password) of bot', OpenStruct.new({
20
+ :name => get("Type the nick of bot(default CrazyDoll):", 'CrazyDoll') { |x| x.empty? ? 'CrazyDoll' : x },
21
+ :user => get("Type the user of bot(default CrazyBot):", 'CrazyBot') { |x| x.empty? ? 'CrazyBot' : x },
22
+ :real_name => get("Type the 'real name' of bot(default CrazyDoll #{CrazyDoll::VERSION}):",
23
+ "CrazyDoll #{CrazyDoll::VERSION}") { |x| x.empty? ? "CrazyDoll #{CrazyDoll::VERSION}" : x },
24
+ :password => get("Type the password of bot(default not use):", nil) { |x| x.empty? ? nil : x }
25
+ }))
26
+ end
27
+
28
+ @config.register(self,
29
+ :channels,
30
+ 'Channels to join',
31
+ get("Type the channels to enter separed by comma(i.e. #crazydoll,#ubuntugames):", "#crazydoll") { |chans|
32
+ chans.gsub(' ','').split(',') }) unless @config.respond_to?(:channels) and @config.channels.is_a?(Array) and not @config.channels.empty?
33
+ end
34
+
35
+ def get(string, default=nil, &block)
36
+ print string << " "
37
+ g = gets.chomp
38
+ g = default if g.empty?
39
+ if g and block_given?
40
+ return yield g
41
+ end
42
+ g
43
+ end
44
+
45
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crazydoll
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
+ - Renan Fernandes
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-02-27 00:00:00 -04:00
18
+ default_executable: crazy_doll
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: net-irc
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 0
30
+ - 9
31
+ version: 0.0.9
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: httparty
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ - 5
44
+ - 2
45
+ version: 0.5.2
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: A plugin oriented IRC bot
49
+ email: renan@kauamanga.com.br
50
+ executables:
51
+ - crazy_doll
52
+ extensions: []
53
+
54
+ extra_rdoc_files:
55
+ - README.md
56
+ files:
57
+ - lib/crazy_doll.rb
58
+ - lib/crazy_doll/bot.rb
59
+ - lib/crazy_doll/config.rb
60
+ - lib/crazy_doll/config_backend/yaml.rb
61
+ - lib/crazy_doll/core.rb
62
+ - lib/crazy_doll/events.rb
63
+ - lib/crazy_doll/fixes.rb
64
+ - lib/crazy_doll/irc.rb
65
+ - lib/crazy_doll/message.rb
66
+ - lib/crazy_doll/plugin_manager.rb
67
+ - lib/crazy_doll/plugins/join_plugin.rb
68
+ - lib/crazy_doll/plugins/nick_plugin.rb
69
+ - lib/crazy_doll/plugins/search_plugin.rb
70
+ - lib/crazy_doll/plugins/show_url_info_plugin.rb
71
+ - lib/crazy_doll/plugins/welcome_plugin.rb
72
+ - lib/crazy_doll/welcome_plugin.rb
73
+ - lib/crazy_doll/wizard.rb
74
+ - README.md
75
+ has_rdoc: true
76
+ homepage: http://github.com/ShadowBelmolve/crazydoll
77
+ licenses: []
78
+
79
+ post_install_message: "\n =================================================================\n Thanks for installing CrazyDoll\n If you want to use the 'show_url_info' plugin you'll need to\n install nokogiri gem\n\n gem install nokogiri \n\n =================================================================\n "
80
+ rdoc_options:
81
+ - --charset=UTF-8
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ requirements: []
99
+
100
+ rubyforge_project:
101
+ rubygems_version: 1.3.6
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: A IRC bot
105
+ test_files: []
106
+