vetinari 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +69 -0
- data/examples/echo_bot.rb +24 -0
- data/lib/vetinari/bot.rb +358 -0
- data/lib/vetinari/callback.rb +48 -0
- data/lib/vetinari/callback_container.rb +85 -0
- data/lib/vetinari/channel.rb +249 -0
- data/lib/vetinari/channel_container.rb +74 -0
- data/lib/vetinari/configuration.rb +54 -0
- data/lib/vetinari/dcc/incoming/file.rb +113 -0
- data/lib/vetinari/dcc/server.rb +107 -0
- data/lib/vetinari/dcc/server_manager.rb +83 -0
- data/lib/vetinari/irc.rb +42 -0
- data/lib/vetinari/isupport.rb +175 -0
- data/lib/vetinari/logging/logger.rb +13 -0
- data/lib/vetinari/logging/logger_list.rb +19 -0
- data/lib/vetinari/logging/null_logger.rb +9 -0
- data/lib/vetinari/message_parser.rb +35 -0
- data/lib/vetinari/mode_parser.rb +46 -0
- data/lib/vetinari/user.rb +104 -0
- data/lib/vetinari/user_container.rb +52 -0
- data/lib/vetinari/version.rb +3 -0
- data/lib/vetinari.rb +38 -0
- data/spec/callback_spec.rb +19 -0
- data/spec/channel_management_spec.rb +39 -0
- data/spec/default_callbacks_spec.rb +53 -0
- data/spec/isupport_spec.rb +260 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/user_management_spec.rb +135 -0
- data/vetinari.gemspec +30 -0
- metadata +154 -0
data/lib/vetinari/irc.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Vetinari
|
2
|
+
module IRC
|
3
|
+
def register
|
4
|
+
raw "PASS #{@config.password}" if @config.password
|
5
|
+
raw "NICK #{@config.nick}"
|
6
|
+
raw "USER #{@config.username} * * :#{@config.real_name}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def rename(nick)
|
10
|
+
raw "NICK :#{nick}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def away(message = nil)
|
14
|
+
if message
|
15
|
+
raw "AWAY :#{message}"
|
16
|
+
else
|
17
|
+
raw "AWAY"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def back
|
22
|
+
away
|
23
|
+
end
|
24
|
+
|
25
|
+
def quit(message = nil)
|
26
|
+
@quitted = true
|
27
|
+
|
28
|
+
if message
|
29
|
+
raw "QUIT :#{message}"
|
30
|
+
else
|
31
|
+
raw 'QUIT'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def join(channel_name, key = nil)
|
36
|
+
unless @channels.has_channel?(channel_name)
|
37
|
+
channel = Channel.new(channel_name, @actor)
|
38
|
+
channel.join key
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module Vetinari
|
2
|
+
class ISupport < Hash
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
|
6
|
+
# Defaults:
|
7
|
+
self['CASEMAPPING'] = 'rfc1459'
|
8
|
+
self['CHANLIMIT'] = {}
|
9
|
+
self['CHANMODES'] = {
|
10
|
+
'A' => ['b'],
|
11
|
+
'B' => ['k'],
|
12
|
+
'C' => ['l'],
|
13
|
+
'D' => %w(i m n p s t r)
|
14
|
+
}
|
15
|
+
self['CHANNELLEN'] = 200
|
16
|
+
self['CHANTYPES'] = ['#', '&']
|
17
|
+
self['EXCEPTS'] = false
|
18
|
+
self['IDCHAN'] = {}
|
19
|
+
self['INVEX'] = false
|
20
|
+
self['KICKLEN'] = Float::INFINITY
|
21
|
+
self['MAXLIST'] = {}
|
22
|
+
self['MODES'] = 3
|
23
|
+
self['NETWORK'] = ''
|
24
|
+
self['NICKLEN'] = Float::INFINITY
|
25
|
+
self['PREFIX'] = {'o' => '@', 'v' => '+'}
|
26
|
+
self['SAFELIST'] = false
|
27
|
+
self['STATUSMSG'] = false
|
28
|
+
self['STD'] = false
|
29
|
+
self['TARGMAX'] = {}
|
30
|
+
self['TOPICLEN'] = Float::INFINITY
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse(message)
|
34
|
+
patterns = message.split(' ')
|
35
|
+
patterns.delete_at(0)
|
36
|
+
patterns.each do |pattern|
|
37
|
+
return self if pattern.start_with?(':')
|
38
|
+
key = pattern.scan(/\w+/).first
|
39
|
+
method_name = "set_#{key.downcase}"
|
40
|
+
begin
|
41
|
+
if respond_to?(method_name, true)
|
42
|
+
send method_name, pattern
|
43
|
+
else
|
44
|
+
set_different key, pattern
|
45
|
+
end
|
46
|
+
rescue
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def set_casemapping(pattern)
|
54
|
+
self['CASEMAPPING'] = pattern.split('=')[1]
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_chanlimit(pattern)
|
58
|
+
value = pattern.split('=')[1]
|
59
|
+
value.split(',').each do |prefixes_and_limit|
|
60
|
+
prefixes, limit = prefixes_and_limit.split(':')
|
61
|
+
limit = limit.nil? ? Float::INFINITY : limit.to_i
|
62
|
+
prefixes.split('').each do |prefix|
|
63
|
+
self['CHANLIMIT'][prefix] = limit
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_chanmodes(pattern)
|
69
|
+
value = pattern.split('=')[1]
|
70
|
+
modes_per_type = value.split(',').map { |modes| modes.split('') }
|
71
|
+
('A'..'D').each_with_index do |type, index|
|
72
|
+
self['CHANMODES'][type] = modes_per_type[index]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def set_channellen(pattern)
|
77
|
+
self['CHANNELLEN'] = pattern.split('=')[1].to_i
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_chantypes(pattern)
|
81
|
+
self['CHANTYPES'] = pattern.split('=')[1].split('')
|
82
|
+
end
|
83
|
+
|
84
|
+
def set_excepts(pattern)
|
85
|
+
mode_char = pattern.split('=')[1]
|
86
|
+
self['EXCEPTS'] = mode_char.nil? ? true : mode_char
|
87
|
+
end
|
88
|
+
|
89
|
+
def set_idchan(pattern)
|
90
|
+
value = pattern.split('=')[1]
|
91
|
+
value.split(',').each do |prefix_and_number|
|
92
|
+
prefix, number = prefix_and_number.split(':')
|
93
|
+
self['IDCHAN'][prefix] = number.to_i
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def set_invex(pattern)
|
98
|
+
mode_char = pattern.split('=')[1]
|
99
|
+
self['INVEX'] = mode_char.nil? ? true : mode_char
|
100
|
+
end
|
101
|
+
|
102
|
+
def set_kicklen(pattern)
|
103
|
+
self['KICKLEN'] = pattern.split('=')[1].to_i
|
104
|
+
end
|
105
|
+
|
106
|
+
def set_maxlist(pattern)
|
107
|
+
value = pattern.split('=')[1]
|
108
|
+
value.split(',').each do |prefixes_and_maximum|
|
109
|
+
prefixes, maximum = prefixes_and_maximum.split(':')
|
110
|
+
prefixes.split('').each do |prefix|
|
111
|
+
self['MAXLIST'][prefix] = maximum.to_i
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def set_modes(pattern)
|
117
|
+
mode_char = pattern.split('=')[1]
|
118
|
+
self['MODES'] = mode_char.nil? ? Float::INFINITY : mode_char.to_i
|
119
|
+
end
|
120
|
+
|
121
|
+
def set_network(pattern)
|
122
|
+
self['NETWORK'] = pattern.split('=')[1]
|
123
|
+
end
|
124
|
+
|
125
|
+
def set_nicklen(pattern)
|
126
|
+
self['NICKLEN'] = pattern.split('=')[1].to_i
|
127
|
+
end
|
128
|
+
|
129
|
+
def set_prefix(pattern)
|
130
|
+
modes, prefixes = pattern.scan(/\((.+)\)(.+)/).flatten
|
131
|
+
modes = modes.split('')
|
132
|
+
prefixes = prefixes.split('')
|
133
|
+
modes.zip(prefixes).each do |pair|
|
134
|
+
self['PREFIX'][pair.first] = pair.last
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def set_safelist(pattern)
|
139
|
+
self['SAFELIST'] = true
|
140
|
+
end
|
141
|
+
|
142
|
+
def set_statusmsg(pattern)
|
143
|
+
self['STATUSMSG'] = pattern.split('=')[1].split('')
|
144
|
+
end
|
145
|
+
|
146
|
+
def set_std(pattern)
|
147
|
+
self['STD'] = pattern.split('=')[1].split(',')
|
148
|
+
end
|
149
|
+
|
150
|
+
def set_targmax(pattern)
|
151
|
+
targets = pattern.split('=')[1].split(',')
|
152
|
+
targets.each do |target_with_maximum|
|
153
|
+
target, maximum = target_with_maximum.split(':')
|
154
|
+
maximum = maximum.nil? ? Float::INFINITY : maximum.to_i
|
155
|
+
self['TARGMAX'][target] = maximum
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def set_topiclen(pattern)
|
160
|
+
self['TOPICLEN'] = pattern.split('=')[1].to_i
|
161
|
+
end
|
162
|
+
|
163
|
+
def set_different(key, pattern)
|
164
|
+
if pattern.include? '='
|
165
|
+
if pattern.include? ','
|
166
|
+
self[key] = pattern.split('=')[1].split(',')
|
167
|
+
else
|
168
|
+
self[key] = pattern.split('=')[1]
|
169
|
+
end
|
170
|
+
else
|
171
|
+
self[key] = true
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Vetinari
|
2
|
+
module Logging
|
3
|
+
class Logger < ::Logger
|
4
|
+
def initialize(*args)
|
5
|
+
super(*args)
|
6
|
+
|
7
|
+
self.formatter = proc do |severity, datetime, progname, msg|
|
8
|
+
"#{severity} #{datetime.strftime('%Y-%m-%d %H:%M:%S')} #{msg}\n"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Vetinari
|
2
|
+
module Logging
|
3
|
+
class LoggerList < Array
|
4
|
+
%w(debug info warn error fatal unknown).each do |method_name|
|
5
|
+
define_method(method_name) do |*args, &block|
|
6
|
+
each do |logger|
|
7
|
+
logger.send(method_name, *args, &block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(method_name, *args, &block)
|
13
|
+
each do |logger|
|
14
|
+
logger.send(method_name, *args, &block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Vetinari
|
2
|
+
module MessageParser
|
3
|
+
def parse(message, chantypes)
|
4
|
+
chantypes = chantypes.map { |c| Regexp.escape(c) }.join('|')
|
5
|
+
|
6
|
+
case message
|
7
|
+
# :kornbluth.freenode.net 001 Vetinari5824 :Welcome to the freenode Internet Relay Chat Network Vetinari5824
|
8
|
+
when /^(?:\:\S+ )?(\d\d\d) /
|
9
|
+
number = $1.to_i
|
10
|
+
{:type => number, :params => $'}
|
11
|
+
when /^:(\S+)!(\S+)@(\S+) PRIVMSG ((?:#{chantypes})\S+) :/
|
12
|
+
{:type => :channel, :nick => $1, :user => $2, :host => $3, :channel => $4, :message => $'}
|
13
|
+
when /^:(\S+)!(\S+)@(\S+) PRIVMSG \S+ :/
|
14
|
+
{:type => :query, :nick => $1, :user => $2, :host => $3, :message => $'}
|
15
|
+
when /^:(\S+)!(\S+)@(\S+) JOIN :*(\S+)$/
|
16
|
+
{:type => :join, :nick => $1, :user => $2, :host => $3, :channel => $4}
|
17
|
+
when /^:(\S+)!(\S+)@(\S+) PART (\S+)/
|
18
|
+
{:type => :part, :nick => $1, :user => $2, :host => $3, :channel => $4, :message => $'.sub(/ :/, '')}
|
19
|
+
when /^:(\S+)!(\S+)@(\S+) QUIT/
|
20
|
+
{:type => :quit, :nick => $1, :user => $2, :host => $3, :message => $'.sub(/ :/, '')}
|
21
|
+
when /^:(\S+)!(\S+)@(\S+) MODE ((?:#{chantypes})\S+) ([+-]\S+)/
|
22
|
+
{:type => :channel_mode, :nick => $1, :user => $2, :host => $3, :channel => $4, :modes => $5, :params => $'.lstrip}
|
23
|
+
when /^:(\S+)!(\S+)@(\S+) NICK :/
|
24
|
+
{:type => :nickchange, :nick => $1, :user => $2, :host => $3, :new_nick => $'}
|
25
|
+
when /^:(\S+)!(\S+)@(\S+) KICK (\S+) (\S+) :/
|
26
|
+
{:type => :kick, :nick => $1, :user => $2, :host => $3, :channel => $4, :kickee => $5, :message => $'}
|
27
|
+
when /^:(\S+)!(\S+)@(\S+) TOPIC (\S+) :/
|
28
|
+
{:type => :topic, :nick => $1, :user => $2, :host => $3, :channel => $4, :topic => $'}
|
29
|
+
else
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
module_function :parse
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Vetinari
|
2
|
+
module ModeParser
|
3
|
+
def parse(modes, params, isupport)
|
4
|
+
modes = modes.split(//)
|
5
|
+
direction = modes.shift.to_sym
|
6
|
+
unless [:'+', :'-'].include?(direction)
|
7
|
+
raise(ArgumentError, "Direction for modes argument not given. +/- needed, got: #{direction}.")
|
8
|
+
end
|
9
|
+
params = params.split(/ /) if params.is_a?(String)
|
10
|
+
mode_changes = []
|
11
|
+
|
12
|
+
modes.each do |mode|
|
13
|
+
if needs_a_param?(mode, direction, isupport)
|
14
|
+
param = params.shift
|
15
|
+
|
16
|
+
if param
|
17
|
+
mode_change = {
|
18
|
+
:direction => direction,
|
19
|
+
:mode => mode,
|
20
|
+
:param => param
|
21
|
+
}
|
22
|
+
mode_changes << mode_change
|
23
|
+
end
|
24
|
+
else
|
25
|
+
mode_change = {
|
26
|
+
:direction => direction,
|
27
|
+
:mode => mode
|
28
|
+
}
|
29
|
+
mode_changes << mode_change
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
mode_changes
|
34
|
+
end
|
35
|
+
module_function :parse
|
36
|
+
|
37
|
+
def needs_a_param?(mode, direction, isupport)
|
38
|
+
modes = isupport['CHANMODES']['A'] +
|
39
|
+
isupport['CHANMODES']['B'] +
|
40
|
+
isupport['PREFIX'].keys
|
41
|
+
modes.concat(isupport['CHANMODES']['C']) if direction == :'+'
|
42
|
+
modes.include?(mode)
|
43
|
+
end
|
44
|
+
module_function :needs_a_param?
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Vetinari
|
2
|
+
# TODO: Actor?
|
3
|
+
class User
|
4
|
+
attr_reader :nick, :user, :host, :online, :observed
|
5
|
+
|
6
|
+
def initialize(nick, bot)
|
7
|
+
@bot = bot
|
8
|
+
|
9
|
+
@nick = nick
|
10
|
+
@user = nil
|
11
|
+
@host = nil
|
12
|
+
|
13
|
+
@online = nil
|
14
|
+
@observed = false
|
15
|
+
|
16
|
+
@channels = Set.new
|
17
|
+
@channels_with_modes = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
# Updates the properties of an user.
|
21
|
+
# def whois
|
22
|
+
# connected do
|
23
|
+
# fiber = Fiber.current
|
24
|
+
# callbacks = {}
|
25
|
+
|
26
|
+
# # User is online.
|
27
|
+
# callbacks[311] = @thaum.on(311) do |event_data|
|
28
|
+
# nick = event_data[:params].split(' ')[1]
|
29
|
+
# if nick.downcase == @nick.downcase
|
30
|
+
# @online = true
|
31
|
+
# # TODO: Add properties.
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
|
35
|
+
# # User is not online.
|
36
|
+
# callbacks[401] = @thaum.on(401) do |event_data|
|
37
|
+
# nick = event_data[:params].split(' ')[1]
|
38
|
+
# if nick.downcase == @nick.downcase
|
39
|
+
# @online = false
|
40
|
+
# fiber.resume
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
|
44
|
+
# # End of WHOIS.
|
45
|
+
# callbacks[318] = @thaum.on(318) do |event_data|
|
46
|
+
# nick = event_data[:params].split(' ')[1]
|
47
|
+
# if nick.downcase == @nick.downcase
|
48
|
+
# fiber.resume
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
|
52
|
+
# raw "WHOIS #{@nick}"
|
53
|
+
# Fiber.yield
|
54
|
+
|
55
|
+
# callbacks.each do |type, callback|
|
56
|
+
# @thaum.callbacks[type].delete(callback)
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
|
60
|
+
# self
|
61
|
+
# end
|
62
|
+
|
63
|
+
def online?
|
64
|
+
if @bot.users[@nick]
|
65
|
+
@online = true
|
66
|
+
else
|
67
|
+
# TODO
|
68
|
+
# whois if @online.nil?
|
69
|
+
# @online
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def bot?
|
74
|
+
self == @bot.user
|
75
|
+
end
|
76
|
+
|
77
|
+
def dcc_send(filepath, filename = nil)
|
78
|
+
if @bot.server_manager
|
79
|
+
if File.exist?(filepath)
|
80
|
+
filename = File.basename(filepath) unless filename
|
81
|
+
@bot.server_manager.add_offering(self, filepath, filename)
|
82
|
+
else
|
83
|
+
raise "File '#{filepath}' does not exist."
|
84
|
+
end
|
85
|
+
else
|
86
|
+
raise 'DCC not available: Missing external IP or ports'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# TODO: ping, version, time methods?
|
91
|
+
|
92
|
+
def message(message)
|
93
|
+
@bot.raw "PRIVMSG #{@nick} :#{message}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def notice(message)
|
97
|
+
@bot.raw "NOTICE #{@nick} :#{message}"
|
98
|
+
end
|
99
|
+
|
100
|
+
def inspect
|
101
|
+
"#<User nick=#{@nick}>"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Vetinari
|
2
|
+
# The UserList class holds information about users a Thaum is able to see
|
3
|
+
# in channels.
|
4
|
+
class UserContainer
|
5
|
+
include Celluloid
|
6
|
+
|
7
|
+
attr_reader :users
|
8
|
+
|
9
|
+
exclusive
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@users = Set.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(user)
|
16
|
+
@users << user
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO
|
20
|
+
def remove(user)
|
21
|
+
@users.delete(user)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Find a User given the nick.
|
25
|
+
def [](user_or_nick)
|
26
|
+
case user_or_nick
|
27
|
+
when User
|
28
|
+
user_or_nick if @users.include?(user_or_nick)
|
29
|
+
when String
|
30
|
+
@users.find do |u|
|
31
|
+
u.nick.downcase == user_or_nick.downcase
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_user?(user)
|
37
|
+
self[user] ? true : false
|
38
|
+
end
|
39
|
+
|
40
|
+
def clear
|
41
|
+
@users.clear
|
42
|
+
end
|
43
|
+
|
44
|
+
# Removes all users from the UserContainer that don't share channels with
|
45
|
+
# the Bot.
|
46
|
+
def kill_zombie_users(users)
|
47
|
+
(@users - users).each do |user|
|
48
|
+
@users.delete(user) unless user.bot?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/vetinari.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'logger'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
require 'celluloid/autostart'
|
7
|
+
require 'celluloid/io'
|
8
|
+
|
9
|
+
module Vetinari
|
10
|
+
require 'vetinari/irc'
|
11
|
+
require 'vetinari/bot'
|
12
|
+
require 'vetinari/callback'
|
13
|
+
require 'vetinari/callback_container'
|
14
|
+
require 'vetinari/channel'
|
15
|
+
require 'vetinari/channel_container'
|
16
|
+
require 'vetinari/configuration'
|
17
|
+
require 'vetinari/isupport'
|
18
|
+
require 'vetinari/message_parser'
|
19
|
+
require 'vetinari/mode_parser'
|
20
|
+
require 'vetinari/user'
|
21
|
+
require 'vetinari/user_container'
|
22
|
+
require 'vetinari/version'
|
23
|
+
|
24
|
+
module Dcc
|
25
|
+
require 'vetinari/dcc/server_manager'
|
26
|
+
require 'vetinari/dcc/server'
|
27
|
+
|
28
|
+
module Incoming
|
29
|
+
require 'vetinari/dcc/incoming/file'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Logging
|
34
|
+
require 'vetinari/logging/logger'
|
35
|
+
require 'vetinari/logging/logger_list'
|
36
|
+
require 'vetinari/logging/null_logger'
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Callback' do
|
4
|
+
subject { Vetinari::Bot.new { |c| c.verbose = false } }
|
5
|
+
let(:callbacks) { subject.callbacks.instance_variable_get('@callbacks') }
|
6
|
+
|
7
|
+
it 'is added correctly' do
|
8
|
+
expect(callbacks[:channel]).to have(1).callback
|
9
|
+
subject.on(:channel)
|
10
|
+
expect(callbacks[:channel]).to have(2).callbacks
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'can be removed' do
|
14
|
+
cb = subject.on(:channel)
|
15
|
+
expect(callbacks[:channel]).to have(2).callback
|
16
|
+
cb.remove
|
17
|
+
expect(callbacks[:channel]).to have(1).callback
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Channel Management' do
|
4
|
+
subject { Vetinari::Bot.new { |c| c.verbose = false } }
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
subject.parse(':server 001 Vetinari :Welcome message')
|
8
|
+
subject.parse(':server 376 Vetinari :End of /MOTD command.')
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'adds a channel to the channel_list when joining a channel' do
|
12
|
+
expect(subject.channels.channels).to be_empty
|
13
|
+
subject.parse(':Vetinari!foo@bar JOIN #mended_drum')
|
14
|
+
expect(subject.channels.channels).to have(1).channel
|
15
|
+
expect(subject.channels.channels.first.name).to eq('#mended_drum')
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'removes a channel from the channel_list when leaving a channel' do
|
19
|
+
subject.parse(':Vetinari!foo@bar JOIN #mended_drum')
|
20
|
+
expect(subject.channels.channels).to have(1).channel
|
21
|
+
subject.parse(':Vetinari!foo@bar PART #mended_drum')
|
22
|
+
expect(subject.channels.channels).to be_empty
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'removes a channel from the channel_list when being kicked from a channel' do
|
26
|
+
subject.parse(':Vetinari!foo@bar JOIN #mended_drum')
|
27
|
+
expect(subject.channels.channels).to have(1).channel
|
28
|
+
subject.parse(':TheLibrarian!foo@bar KICK #mended_drum Vetinari :No humans allowed!')
|
29
|
+
expect(subject.channels.channels).to be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'removes all channels from the channel_list when quitting' do
|
33
|
+
subject.parse(':Vetinari!foo@bar JOIN #mended_drum')
|
34
|
+
subject.parse(':Vetinari!foo@bar JOIN #library')
|
35
|
+
expect(subject.channels.channels).to have(2).channels
|
36
|
+
subject.parse(':Vetinari!foo@bar QUIT :Bye mates!')
|
37
|
+
expect(subject.channels.channels).to be_empty
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Vetinari::Bot do
|
4
|
+
subject { Vetinari::Bot.new { |c| c.verbose = false } }
|
5
|
+
let(:bare) { subject.bare_object }
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
subject.parse(':server 001 Vetinari :Welcome message')
|
9
|
+
subject.parse(':server 376 Vetinari :End of /MOTD command.')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'PING PONGs (server)' do
|
13
|
+
bare.should_receive(:raw).with("PONG :server", false)
|
14
|
+
subject.parse("PING :server")
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'PING PONGs (user)' do
|
18
|
+
time = Time.now.to_i
|
19
|
+
bare.should_receive(:raw).with("NOTICE nick :\001PING #{time}\001")
|
20
|
+
subject.parse(":nick!user@host PRIVMSG Vetinari :\001PING #{time}\001")
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'responses to VERSION request' do
|
24
|
+
bare.should_receive(:raw).with("NOTICE nick :\001VERSION Vetinari #{Vetinari::VERSION} (https://github.com/tbuehlmann/vetinari)")
|
25
|
+
subject.parse(":nick!user@host PRIVMSG Vetinari :\001VERSION\001")
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'responses to TIME request' do
|
29
|
+
bare.should_receive(:raw).with("NOTICE nick :\001TIME #{Time.now.strftime('%a %b %d %H:%M:%S %Y')}\001")
|
30
|
+
subject.parse(":nick!user@host PRIVMSG Vetinari :\001TIME\001")
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'rejoin channel after kick' do
|
34
|
+
let(:channel) { subject.channels['#mended_drum'] }
|
35
|
+
|
36
|
+
before(:each) do
|
37
|
+
subject.config.rejoin_after_kick = true
|
38
|
+
subject.parse(':Vetinari!foo@bar JOIN #mended_drum')
|
39
|
+
subject.parse(':TheLibrarian!foo@bar JOIN #mended_drum')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'without a channel key' do
|
43
|
+
channel.should_receive(:join)
|
44
|
+
subject.parse(':TheLibrarian!foo@bar KICK #mended_drum Vetinari :foo')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'with a channel key' do
|
48
|
+
channel.should_receive(:join).with('thaum')
|
49
|
+
subject.parse(':Vetinari!foo@bar MODE #mended_drum +k thaum')
|
50
|
+
subject.parse(':TheLibrarian!foo@bar KICK #mended_drum Vetinari :foo')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|