crazydoll 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+