cereal 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +57 -0
- data/MIT-LICENSE +21 -0
- data/README +86 -0
- data/Rakefile +30 -0
- data/TODO +9 -0
- data/lib/cereal.rb +423 -0
- data/lib/connection.rb +404 -0
- data/lib/user.rb +98 -0
- metadata +71 -0
data/CHANGES
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
2009-03-30 v0.1.8 ross
|
2
|
+
|
3
|
+
* lib/connection.rb (handle_line): Curbed a nasty memory leak by manually instantiating garbage collection. Residential memory usage should be more stable now.
|
4
|
+
|
5
|
+
2009-03-13 v0.1.7 ross
|
6
|
+
|
7
|
+
* lib/cereal.rb: All colors and text attributes now work correctly! Yay!
|
8
|
+
|
9
|
+
2009-03-13 v0.1.6 ross
|
10
|
+
|
11
|
+
* lib/cereal.rb (confirm_nick): Added @connection.temp_nick assignment.
|
12
|
+
|
13
|
+
* lib/connection.rb: Added attr_accessor for @temp_nick.
|
14
|
+
|
15
|
+
Fixes #6.
|
16
|
+
|
17
|
+
2009-02-23 v0.1.5 ross
|
18
|
+
|
19
|
+
* lib/cereal.rb (kick, ban): Methods added.
|
20
|
+
|
21
|
+
2009-02-23 v0.1.4 ross
|
22
|
+
|
23
|
+
* lib/connection.rb (initialize, receive_data): Added @incomplete_line_buffer to hold incomplete lines received from the server (lines not ending in a CR-LF or \r\n pair) until we receive the complete line. This should fix issues with large channels not reporting the correct number of users, etc.
|
24
|
+
|
25
|
+
2009-02-23 v0.1.3 ross
|
26
|
+
|
27
|
+
* lib/connection.rb (send_data): Added simplest fix ever that finally rids us of EventMachine complaining about sending data after closing a connection. OH GOD, THIS IS SO SATISFYING.
|
28
|
+
|
29
|
+
* lib/cereal.rb: Updated version number to 0.1.3.
|
30
|
+
|
31
|
+
2009-02-21 v0.1.2 jon
|
32
|
+
|
33
|
+
* lib/connection.rb (handle_line): Moved on_quit callback to begining of block. This allows any on_quit callback to find the channels that the user was in before being removed from the channel list. (Might want a better way to do this, possibly grab a list of channels the user was in and pass it as an argument? Same thing with other blocks such as nick change.)
|
34
|
+
|
35
|
+
2009-02-21 v0.1.2 ross
|
36
|
+
|
37
|
+
* cereal.gemspec: Modified to pull gem version straight from lib/cereal.rb. No more updating! Also added auto updating of spec.date.
|
38
|
+
|
39
|
+
2009-02-21 v0.1.2 ross
|
40
|
+
|
41
|
+
* lib/cereal.rb (channels): Added. Method to grab hash of currently inhabited channels.
|
42
|
+
|
43
|
+
2009-02-21 v0.1.1 ross
|
44
|
+
|
45
|
+
* cereal.gemspec: Changed gem version to 0.1.1 to match actual Cereal version!
|
46
|
+
|
47
|
+
2009-02-21 v0.1.1 ross
|
48
|
+
|
49
|
+
* lib/cereal.rb (get_users): Added. Method to grab array of Cereal::User objects for a given channel that the bot is in.
|
50
|
+
|
51
|
+
* lib/connection.rb: Added attr_reader for @channels. Used by Cereal::Bot#get_users.
|
52
|
+
|
53
|
+
* cereal.gemspec: Added Jon R. to authors list.
|
54
|
+
|
55
|
+
2009-02-17 ross
|
56
|
+
|
57
|
+
* Initial import to SVN!
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2009 Ross Paffett
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
= Cereal
|
2
|
+
This package contains Cereal, an event-driven IRC framework built on
|
3
|
+
EventMachine and inspired by Paul Mutton's excellent
|
4
|
+
PircBot[http://jibble.org/pircbot.php] IRC framework for Java.
|
5
|
+
|
6
|
+
Cereal has the following features:
|
7
|
+
|
8
|
+
* Easy to use! Cereal is designed for quick-and-easy implementation, just like
|
9
|
+
PircBot. Your first bot can be set up by overriding a single class.
|
10
|
+
|
11
|
+
* Your specific functionality is easily added by overriding logically-named
|
12
|
+
methods, like +on_message+.
|
13
|
+
|
14
|
+
Be sure not to confuse _Cereal_ and _CerealBot_. Cereal is a standalone IRC
|
15
|
+
framework. By itself, Cereal does nothing. CerealBot[http://cerealbot.org],
|
16
|
+
on the other hand, is a fully-featured IRC bot project by the same people who
|
17
|
+
have ruined your life with Cereal.
|
18
|
+
|
19
|
+
== Authors
|
20
|
+
[Ross "Raws" Paffett] Lead developer.
|
21
|
+
[Brian "TecnoBrat" S.] Inspiration, Ruby knowledge, general abuse.
|
22
|
+
[Jon "Corgan" R.] Butts.
|
23
|
+
|
24
|
+
== Download
|
25
|
+
Cereal is currently hosted at cerealbot.org[http://cerealbot.org/].
|
26
|
+
|
27
|
+
== Installation
|
28
|
+
|
29
|
+
=== Gem Installation from Source
|
30
|
+
Generate a Cereal .gem file and install it with the following commands:
|
31
|
+
|
32
|
+
rake package
|
33
|
+
gem install --local pkg/cereal
|
34
|
+
|
35
|
+
Cereal will now be available to your Ruby code through rubygems:
|
36
|
+
|
37
|
+
require 'rubygems'
|
38
|
+
require 'cereal'
|
39
|
+
|
40
|
+
You can clean up the package rake task files with:
|
41
|
+
|
42
|
+
rake clobber_package
|
43
|
+
|
44
|
+
==== Generate Documentation
|
45
|
+
You may generate documentation from source with the following command:
|
46
|
+
|
47
|
+
rake rdoc
|
48
|
+
|
49
|
+
RDoc documentation will be created in the directory "doc".
|
50
|
+
|
51
|
+
You can clean up the rdoc rake task files with:
|
52
|
+
|
53
|
+
rake clobber_rdoc
|
54
|
+
|
55
|
+
== Simple Example
|
56
|
+
Once installed, you can create your first bot!
|
57
|
+
|
58
|
+
require 'rubygems'
|
59
|
+
require 'cereal'
|
60
|
+
|
61
|
+
class MyBot < Cereal::Bot
|
62
|
+
def initialize
|
63
|
+
super
|
64
|
+
@name = 'My Bot'
|
65
|
+
@nick = 'MyBot'
|
66
|
+
@verbose = true
|
67
|
+
end
|
68
|
+
|
69
|
+
def on_connect(host)
|
70
|
+
puts "Connected to #{host}!"
|
71
|
+
|
72
|
+
join('#cerealbot')
|
73
|
+
|
74
|
+
msg('#cerealbot', 'Hi!')
|
75
|
+
msg('Corgan', 'Hello!')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
bot = MyBot.new
|
80
|
+
bot.connect('irc.freenode.net')
|
81
|
+
|
82
|
+
== Documentation
|
83
|
+
See the RDoc documentation for the Cereal module for more information.
|
84
|
+
|
85
|
+
== License
|
86
|
+
Cereal is available under the MIT license. See the file MIT-LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Rakefile for Cereal
|
2
|
+
#
|
3
|
+
# Copyright (c) 2009 Ross Paffett. Licensed under the MIT license:
|
4
|
+
# http://www.opensource.org/licenses/mit-license.php
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'rake/packagetask'
|
8
|
+
require 'rake/gempackagetask'
|
9
|
+
require 'rake/rdoctask'
|
10
|
+
|
11
|
+
load 'cereal.gemspec'
|
12
|
+
|
13
|
+
# Generate rdoc documentation in ./doc/.
|
14
|
+
Rake::RDocTask.new do |rd|
|
15
|
+
rd.rdoc_dir = 'doc'
|
16
|
+
rd.main = 'README'
|
17
|
+
rd.rdoc_files.include('MIT-LICENSE', 'README', 'TODO', 'lib/**/*.rb')
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create a Ruby Gem package.
|
21
|
+
Rake::GemPackageTask.new(@@spec) do |pkg|
|
22
|
+
pkg.need_zip = false
|
23
|
+
pkg.need_tar = false
|
24
|
+
end
|
25
|
+
|
26
|
+
# Package Cereal source into tarball
|
27
|
+
Rake::PackageTask.new(@@spec.name, @@spec.version) do |p|
|
28
|
+
p.need_tar_gz = true
|
29
|
+
p.package_files.include('**/**')
|
30
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
= Cereal -- To Do List
|
2
|
+
|
3
|
+
Send suggestions for this list to mailto:nobody@inparticul.ar! Actually, there
|
4
|
+
is someone, but he doesn't have a proper public address set up yet.
|
5
|
+
|
6
|
+
=== To Do
|
7
|
+
* Document the rest of the library.
|
8
|
+
* Write tests (is this even practically possible?).
|
9
|
+
* Figure out if there's anything else to do.
|
data/lib/cereal.rb
ADDED
@@ -0,0 +1,423 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# == Synopsis
|
4
|
+
# Cereal is an IRC connection framework inspired (quite heavily) by
|
5
|
+
# Paul Mutton's excellent PircBot[http://jibble.org/pircbot.php]
|
6
|
+
# IRC library for Java.
|
7
|
+
#
|
8
|
+
# See documentation for the Cereal module for more information.
|
9
|
+
#
|
10
|
+
# == Author
|
11
|
+
# Ross "Raws" Paffett
|
12
|
+
#
|
13
|
+
# == Copyright
|
14
|
+
# Copyright (c) 2009 Ross Paffett. Licensed under the MIT license:
|
15
|
+
# http://www.opensource.org/licenses/mit-license.php
|
16
|
+
|
17
|
+
# Standard library
|
18
|
+
require 'ostruct'
|
19
|
+
require 'thread'
|
20
|
+
|
21
|
+
# Rubygems
|
22
|
+
require 'rubygems'
|
23
|
+
require 'eventmachine'
|
24
|
+
|
25
|
+
# Cereal library
|
26
|
+
require 'connection.rb'
|
27
|
+
require 'user.rb'
|
28
|
+
|
29
|
+
# == Overview
|
30
|
+
# Cereal is a Ruby framework for quickly and easily constructing IRC applications.
|
31
|
+
#
|
32
|
+
# TODO Actually write documentation or something. See Cereal::Bot for some
|
33
|
+
# up-and-coming documentation for now.
|
34
|
+
module Cereal
|
35
|
+
# Definitive version number of this release of Cereal.
|
36
|
+
VERSION = "0.1.8"
|
37
|
+
|
38
|
+
# Colors
|
39
|
+
NORMAL = N = "\017"
|
40
|
+
BOLD = B = "\002"
|
41
|
+
UNDERLINE = UL = "\037"
|
42
|
+
REVERSE = REV = ITALIC = IT = "\026"
|
43
|
+
WHITE = "\0030"
|
44
|
+
BLACK = "\0031"
|
45
|
+
DARK_BLUE = NAVY = "\0032"
|
46
|
+
DARK_GREEN = FOREST = "\0033"
|
47
|
+
RED = "\0034"
|
48
|
+
MAROON = BROWN = "\0035"
|
49
|
+
PURPLE = "\0036"
|
50
|
+
ORANGE = OLIVE = "\0037"
|
51
|
+
YELLOW = "\0038"
|
52
|
+
GREEN = "\0039"
|
53
|
+
TEAL = "\00310"
|
54
|
+
CYAN = "\00311"
|
55
|
+
BLUE = "\00312"
|
56
|
+
MAGENTA = "\00313"
|
57
|
+
DARK_GRAY = "\00314"
|
58
|
+
LIGHT_GRAY = ASH = "\00315"
|
59
|
+
|
60
|
+
class Bot
|
61
|
+
|
62
|
+
attr_accessor :name, :login, :version, :finger, :message_delay, :verbose
|
63
|
+
attr_reader :nick
|
64
|
+
|
65
|
+
def initialize
|
66
|
+
@name = 'Cereal'
|
67
|
+
@nick = @name
|
68
|
+
@login = 'cereal'
|
69
|
+
@version = "Cereal #{Cereal::VERSION} (Ruby #{RUBY_VERSION})"
|
70
|
+
@finger = 'You ought to be arrested for fingering a bot!'
|
71
|
+
@message_delay = 1.0
|
72
|
+
@verbose = false
|
73
|
+
end
|
74
|
+
|
75
|
+
# Connect to a server, optionally specifying a port and password. This method
|
76
|
+
# starts the "main event loop." You may want to rescue +IrcError+ and/or
|
77
|
+
# +NickAlreadyInUseError+ here, as they may be raised upon a connection failure
|
78
|
+
# or other trouble.
|
79
|
+
# [_server_] The server hostname to connect to.
|
80
|
+
# [[_port_]] The port to connect to. Most IRC servers use port 6667 or similar.
|
81
|
+
# [[_password_]] The password to connect to the server with. A value of +nil+
|
82
|
+
# (the default) will cause Cereal to skip this step.
|
83
|
+
def connect(server, port=6667, password=nil)
|
84
|
+
EventMachine::run {
|
85
|
+
@connection = EventMachine::connect(server, port, Cereal::Connection,
|
86
|
+
:bot => self, :password => password)
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
# Add a line to the log. The default behavior is to output to STDOUT using the
|
91
|
+
# "PircBot" log format, which is recognized by pisg[http://pisg.sourceforge.net/],
|
92
|
+
# the Perl IRC Statistics Generator. This is only done if +@verbose+ is set to +true+.
|
93
|
+
#
|
94
|
+
# The PircBot log format is described as each line beginning with an integer that
|
95
|
+
# is the number of milliseconds since the epoch, followed by a single space, " ",
|
96
|
+
# then the log message. Outgoing messages are prefixed by ">>>" immediately
|
97
|
+
# following the space character after the timestamp:
|
98
|
+
#
|
99
|
+
# 1233871496459 >>>NICK Wheaties
|
100
|
+
# 1233871496459 >>>USER wheaties 0 * :Botfast of Champions!
|
101
|
+
# 1233871496488 :ravaged.mule.anus 001 Wheaties :Welcome to the Internet Relay Network Wheaties!~wheaties@Stig
|
102
|
+
# [_line_] The line to add to the log.
|
103
|
+
def log(line)
|
104
|
+
puts (Time.now.to_f * 1000).to_i.to_s + " #{line}" if @verbose
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns an array of Cereal::User objects representing the users in the specified
|
108
|
+
# channel. Note that the bot must be in a channel to be aware of its users. In addition,
|
109
|
+
# this method may return an incomplete list of the bot has not received the entire user
|
110
|
+
# list for a channel immediately after joining. To be sure you receive a full list, you
|
111
|
+
# may want to override on_user_list instead.
|
112
|
+
# [_channel_] The channel to fetch an array of Cereal::User objects for.
|
113
|
+
def get_users(channel)
|
114
|
+
channel.downcase!
|
115
|
+
@connection.channels.fetch(channel, {}).keys
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns a hash of channels that the bot is currently in. The hash's keys are strings representing
|
119
|
+
# the channel name, so that <code>channels.keys</code> will return an array of channel name strings.
|
120
|
+
# The hash's values are arrays of Cereal::User objects representing the users in that channel, which
|
121
|
+
# is the same data returned by get_users.
|
122
|
+
def channels
|
123
|
+
@connection.channels
|
124
|
+
end
|
125
|
+
|
126
|
+
# Join a channel with an optional key.
|
127
|
+
# [_channel_] The channel to join.
|
128
|
+
# [[_key_]] The key that will be used to join the channel.
|
129
|
+
def join(channel, key=nil)
|
130
|
+
send_raw("JOIN #{channel}" + (key.nil? ? '' : " #{key}"))
|
131
|
+
end
|
132
|
+
|
133
|
+
# Part a channel with an optional reason.
|
134
|
+
# [_channel_] The channel to leave.
|
135
|
+
# [[_reason_]] The reason for parting the channel.
|
136
|
+
def part(channel, reason=nil)
|
137
|
+
send_raw("PART #{channel}" + (reason.nil? ? '' : " :#{reason}"))
|
138
|
+
end
|
139
|
+
|
140
|
+
# Quit from the IRC server with an optional reason.
|
141
|
+
# [[_reason_]] The reason for quitting the server.
|
142
|
+
def quit(reason = '')
|
143
|
+
@connection.send_data("QUIT :#{reason}")
|
144
|
+
end
|
145
|
+
|
146
|
+
# Attempt to kick a user from a channel, optionally giving a reason. This
|
147
|
+
# action may require the bot have operator status in the channel.
|
148
|
+
# [_channel_] The channel to kick the user from.
|
149
|
+
# [_nick_] The nick of the user to kick.
|
150
|
+
# [[_reason_]] The reason for kicking the user.
|
151
|
+
def kick(channel, nick, reason='')
|
152
|
+
send_raw("KICK #{channel} #{nick} :#{reason}")
|
153
|
+
end
|
154
|
+
|
155
|
+
# Ban a user from a channel. An example of a valid hostmask is "*!*compu@*.18hp.net".
|
156
|
+
# This may be used in conjunction with the kick method to permanently remove a user
|
157
|
+
# from a channel. This action may require the bot have operator status in the channel.
|
158
|
+
# Some IRC networks allow banning by nick in adddition to hostmask.
|
159
|
+
# [_channel_] The channel to ban the user from.
|
160
|
+
# [_hostmask_] A hostmask representing the user we're banning.
|
161
|
+
def ban(channel, hostmask)
|
162
|
+
send_raw("MODE #{channel} +b #{hostmask}")
|
163
|
+
end
|
164
|
+
|
165
|
+
# Send a message to a channel or a private message to a user.
|
166
|
+
#
|
167
|
+
# # Send the message "Hello!" to the channel #perkele.
|
168
|
+
# send_message('#perkele', 'Hello!')
|
169
|
+
#
|
170
|
+
# # Send a private message to Jibbler that says "Hi!"
|
171
|
+
# send_message('Jibbler', 'Hi!')
|
172
|
+
# [_target_] The channel or user nick to send to.
|
173
|
+
# [_message_] The message to send.
|
174
|
+
def send_message(target, message)
|
175
|
+
send_raw("PRIVMSG #{target} :#{message}")
|
176
|
+
end
|
177
|
+
|
178
|
+
alias :message :send_message
|
179
|
+
alias :msg :send_message
|
180
|
+
alias :pm :send_message
|
181
|
+
|
182
|
+
# Send an action to a channel or user.
|
183
|
+
# [_target_] The channel or user to send to.
|
184
|
+
# [_action_] The action to send.
|
185
|
+
def send_action(target, action)
|
186
|
+
send_ctcp(target, "ACTION #{action}")
|
187
|
+
end
|
188
|
+
|
189
|
+
alias :action :send_action
|
190
|
+
|
191
|
+
# send is a convenience combination of send_message and
|
192
|
+
# send_action. If _message_ begins with "/me ", Cereal sends
|
193
|
+
# an ACTION to _target_. Otherwise, a regular message is sent.
|
194
|
+
# [_target_] The channel or user to send to.
|
195
|
+
# [_message_] The message to send. If _message_ begins with
|
196
|
+
# "/me ", an ACTION is sent.
|
197
|
+
def send(target, message)
|
198
|
+
if message =~ /^\/me (.*)$/
|
199
|
+
send_action(target, $~[1])
|
200
|
+
else
|
201
|
+
send_message(target, message)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Send a notice to a channel or user.
|
206
|
+
# [_target_] The channel or user to send to.
|
207
|
+
# [_notice_] The notice to send.
|
208
|
+
def send_notice(target, notice)
|
209
|
+
send_raw("NOTICE #{target} :#{notice}")
|
210
|
+
end
|
211
|
+
|
212
|
+
alias :notice :send_notice
|
213
|
+
|
214
|
+
# Send a CTCP (client-to-client protocol) command to a channel
|
215
|
+
# or user. Examples of such commands are <code>PING <number></code>,
|
216
|
+
# +FINGER+, +VERSION+, etc.
|
217
|
+
#
|
218
|
+
# # Request the version of the user DeadEd
|
219
|
+
# send_ctcp('DeadEd', 'VERSION')
|
220
|
+
# [_target_] The channel or user to send the CTCP message to.
|
221
|
+
# [_command_] The CTCP command to send.
|
222
|
+
def send_ctcp(target, command)
|
223
|
+
send_raw("PRIVMSG #{target} :\1#{command}\1")
|
224
|
+
end
|
225
|
+
|
226
|
+
alias :ctcp :send_ctcp
|
227
|
+
|
228
|
+
# Change the current nick of the bot. If offline, this method will set
|
229
|
+
# the nick or nicks the bot will try to use upon a connection attempt.
|
230
|
+
# If connected, this method will attempt to change the current nick of
|
231
|
+
# the bot. #nick will return the new nick after the bot receives
|
232
|
+
# confirmation of a successful nick change from the IRC server.
|
233
|
+
#
|
234
|
+
# [_new_nick_] The new nick to use. Can be a String or an Array of
|
235
|
+
# Strings. If passed an Array, the bot will try each nick
|
236
|
+
# in order from first to last until it finds a nick that
|
237
|
+
# is not in use.
|
238
|
+
def nick=(new_nick)
|
239
|
+
if connected?
|
240
|
+
new_nick = new_nick.shift.to_s if new_nick.is_a?(Array)
|
241
|
+
send_raw("NICK #{new_nick}")
|
242
|
+
else
|
243
|
+
@nick = new_nick
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
alias :change_nick :nick=
|
248
|
+
|
249
|
+
# Used by Cereal::Connection to change @nick after we've received a confirmation
|
250
|
+
# that our requested nick has been assigned to us.
|
251
|
+
# [_confirmed_nick_] The nick that has been assigned to us.
|
252
|
+
def confirm_nick(confirmed_nick)
|
253
|
+
@nick = confirmed_nick
|
254
|
+
@connection.temp_nick = confirmed_nick
|
255
|
+
end
|
256
|
+
|
257
|
+
# Returns _true_ if the bot is currently connected to a server, and _false_
|
258
|
+
# if otherwise (even if the bot is currently negotiating a connection).
|
259
|
+
def connected?
|
260
|
+
@connection && @connection.state == Cereal::Connection::STATE_CONNECTED
|
261
|
+
end
|
262
|
+
|
263
|
+
# Send raw data to the IRC server.
|
264
|
+
# [_data_] The data to send to the server.
|
265
|
+
def send_raw(data)
|
266
|
+
@connection.send_raw(data) if !data.nil?
|
267
|
+
end
|
268
|
+
|
269
|
+
alias :<< :send_raw
|
270
|
+
|
271
|
+
def on_action(sender, target, action)
|
272
|
+
|
273
|
+
end
|
274
|
+
|
275
|
+
def on_channel_info(channel, user_count, topic)
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
def on_connect(host)
|
280
|
+
|
281
|
+
end
|
282
|
+
|
283
|
+
def on_disconnect
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
def on_finger(sender, target)
|
288
|
+
send_raw "NOTICE #{sender.nick} :\1FINGER #{@finger}\1"
|
289
|
+
end
|
290
|
+
|
291
|
+
def on_invite(sender, target, channel)
|
292
|
+
|
293
|
+
end
|
294
|
+
|
295
|
+
def on_join(sender, channel)
|
296
|
+
|
297
|
+
end
|
298
|
+
|
299
|
+
def on_kick(sender, channel, recipient, reason)
|
300
|
+
|
301
|
+
end
|
302
|
+
|
303
|
+
def on_message(sender, target, message)
|
304
|
+
|
305
|
+
end
|
306
|
+
|
307
|
+
def on_nick_change(sender, new_nick)
|
308
|
+
|
309
|
+
end
|
310
|
+
|
311
|
+
def on_notice(sender, target, notice)
|
312
|
+
|
313
|
+
end
|
314
|
+
|
315
|
+
def on_part(sender, channel)
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
def on_ping(sender, target, ping_value)
|
320
|
+
send_raw("NOTICE #{sender.nick} :\1PING #{ping_value}\1")
|
321
|
+
end
|
322
|
+
|
323
|
+
def on_private_message(sender, message)
|
324
|
+
|
325
|
+
end
|
326
|
+
|
327
|
+
def on_quit(sender, reason)
|
328
|
+
|
329
|
+
end
|
330
|
+
|
331
|
+
def on_server_ping(response)
|
332
|
+
@connection.send_data("PONG #{response}")
|
333
|
+
end
|
334
|
+
|
335
|
+
# Called when we receive a numeric response from the IRC server. Numeric
|
336
|
+
# replies are received in response to commands sent to the server, and are
|
337
|
+
# documented in RFC 2812, Section 5, "Replies".
|
338
|
+
#
|
339
|
+
# For example, we can use this method to discover the topic of a channel
|
340
|
+
# when we join it. If we join the channel ##test which has a topic of
|
341
|
+
# "My spoon is too big" then the _response_ will be <code>"Cereal ##test
|
342
|
+
# :My spoon is too big"</code> with a _code_ of 332 to signify that this
|
343
|
+
# is a topic. (Note that this is just an example, and that overriding
|
344
|
+
# on_topic is an easier way of finding the topic for a channel.)
|
345
|
+
# [_code_] Integer numeric code for the response.
|
346
|
+
# [_response_] Full response string from the IRC server.
|
347
|
+
def on_server_response(code, response)
|
348
|
+
|
349
|
+
end
|
350
|
+
|
351
|
+
def on_time(sender, target)
|
352
|
+
send_raw("NOTICE #{sender.nick} :\1TIME #{Time.new}\1")
|
353
|
+
end
|
354
|
+
|
355
|
+
# Called whenever a user sets the topic of a channel, or when Cereal
|
356
|
+
# joins a new channel and discovers its topic.
|
357
|
+
# [_set_by_] The nick of the user that set the topic.
|
358
|
+
# [_channel_] The channel whose topic has been set or discovered.
|
359
|
+
# [_date_] When the topic was set as a +Time+.
|
360
|
+
# [_topic_] The topic for the channel.
|
361
|
+
# [_changed_] True if the topic has just been changed, false if we're
|
362
|
+
# simply discovering a new channel's topic.
|
363
|
+
def on_topic(set_by, channel, date, topic, changed)
|
364
|
+
|
365
|
+
end
|
366
|
+
|
367
|
+
# Called whenever we receive a line from the IRC server that Cereal
|
368
|
+
# does not recognize.
|
369
|
+
# [_line_] The raw line that was received from the server.
|
370
|
+
def on_unknown(line)
|
371
|
+
|
372
|
+
end
|
373
|
+
|
374
|
+
# Called when we receive a user list from the server after joining a
|
375
|
+
# channel.
|
376
|
+
#
|
377
|
+
# After joining a channel, Cereal collects this information
|
378
|
+
# provided by the IRC server and calls this method as soon as it
|
379
|
+
# has the full list.
|
380
|
+
# [_channel_] The name of the channel.
|
381
|
+
# [_users_] An array of User objects residing in _channel_.
|
382
|
+
def on_user_list(channel, users)
|
383
|
+
|
384
|
+
end
|
385
|
+
|
386
|
+
# Called whenever we receive a VERSION CTCP request. The default
|
387
|
+
# implementation responds with Cereal's generic version string,
|
388
|
+
# so if you override this method, be sure to either mimic its
|
389
|
+
# functionality or call <code>super(...)</code>.
|
390
|
+
# [_sender_] An OpenStruct object describing the sender of this
|
391
|
+
# request. See on_message.
|
392
|
+
# [_target_] The target of the VERSION request, be it our nick
|
393
|
+
# or a channel name.
|
394
|
+
def on_version(sender, target)
|
395
|
+
send_raw("NOTICE #{sender.nick} :\1VERSION #{@version}\1")
|
396
|
+
end
|
397
|
+
|
398
|
+
end
|
399
|
+
|
400
|
+
# == Synopsis
|
401
|
+
# An IRC error class.
|
402
|
+
#
|
403
|
+
# == Author
|
404
|
+
# Ross "Raws" Paffett
|
405
|
+
#
|
406
|
+
# == Copyright
|
407
|
+
# Copyright (c) 2009 Ross Paffett. Licensed under the MIT license:
|
408
|
+
# http://www.opensource.org/licenses/mit-license.php
|
409
|
+
class IrcError < StandardError; end
|
410
|
+
|
411
|
+
# == Synopsis
|
412
|
+
# A NickAlreadyInUseError class. This exception is raised when Cereal
|
413
|
+
# tries to join an IRC server with a nickname (or nicknames) that is
|
414
|
+
# already in use.
|
415
|
+
#
|
416
|
+
# == Author
|
417
|
+
# Ross "Raws" Paffett
|
418
|
+
#
|
419
|
+
# == Copyright
|
420
|
+
# Copyright (c) 2009 Ross Paffett. Licensed under the MIT license:
|
421
|
+
# http://www.opensource.org/licenses/mit-license.php
|
422
|
+
class NickAlreadyInUseError < IrcError; end
|
423
|
+
end
|
data/lib/connection.rb
ADDED
@@ -0,0 +1,404 @@
|
|
1
|
+
module Cereal
|
2
|
+
|
3
|
+
# == Synopsis
|
4
|
+
# Cereal::Connection represents a connection to an IRC server. It
|
5
|
+
# is used internally by Cereal::Bot, and you never need to touch it
|
6
|
+
# to use Cereal. Each instance of Cereal::Bot has an attached
|
7
|
+
# Cereal::Connection instance that it communicates with the IRC
|
8
|
+
# server via.
|
9
|
+
#
|
10
|
+
# See the documentation for EventMachine::Connection for more detail.
|
11
|
+
#
|
12
|
+
# == Author
|
13
|
+
# Ross "Raws" Paffett
|
14
|
+
#
|
15
|
+
# == Copyright
|
16
|
+
# Copyright (c) 2009 Ross Paffett. Licensed under the MIT license:
|
17
|
+
# http://www.opensource.org/licenses/mit-license.php
|
18
|
+
class Connection < EventMachine::Connection
|
19
|
+
|
20
|
+
STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED = 0, 1, 2
|
21
|
+
|
22
|
+
attr_reader :state, :channels
|
23
|
+
attr_accessor :temp_nick
|
24
|
+
|
25
|
+
def initialize(args)
|
26
|
+
# Our parent Cereal::Bot
|
27
|
+
@bot = args[:bot]
|
28
|
+
|
29
|
+
# Current state of this connection
|
30
|
+
@state = Cereal::Connection::STATE_DISCONNECTED
|
31
|
+
|
32
|
+
# Details about the server we last connected to
|
33
|
+
@host = nil
|
34
|
+
@password = args[:password]
|
35
|
+
|
36
|
+
# Hash of channels that is used to remember which users are
|
37
|
+
# in which channels
|
38
|
+
@channels = {}
|
39
|
+
|
40
|
+
# Hash to temporarily hold channel topics on RPL_TOPIC until
|
41
|
+
# we receive more information on RPL_TOPICINFO
|
42
|
+
@topics = {}
|
43
|
+
|
44
|
+
# A thread which is responsible for sending messages to the IRC
|
45
|
+
# server in a controlled manner. Its queue prevents a flood of
|
46
|
+
# messages from causing the bot to be kicked from a channel or server.
|
47
|
+
@output_queue = Queue.new
|
48
|
+
@output_thread = Thread.new do
|
49
|
+
while true
|
50
|
+
sleep @bot.message_delay
|
51
|
+
send_data(@output_queue.deq)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# A string buffer to hold incomplete lines (lines not ending in \r\n)
|
56
|
+
# that we receive from the server.
|
57
|
+
@incomplete_line_buffer = ''
|
58
|
+
end
|
59
|
+
|
60
|
+
# Send raw data to the IRC server immediately. A trailing newline
|
61
|
+
# is added automatically.
|
62
|
+
# [_data_] The data to send.
|
63
|
+
def send_data(data)
|
64
|
+
return if data.nil? || @state == Cereal::Connection::STATE_DISCONNECTED
|
65
|
+
super(data + "\r\n")
|
66
|
+
@bot.log(">>>#{data}")
|
67
|
+
end
|
68
|
+
|
69
|
+
# Send raw data to the IRC server through the output queue. Messages
|
70
|
+
# are sent every _n_ seconds, where _n_ is <code>@bot.message_delay</code>.
|
71
|
+
# To bypass the output queue, use send_data.
|
72
|
+
def send_raw(data)
|
73
|
+
@output_queue.enq(data)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Called upon receiving one or more lines of data from the IRC server.
|
77
|
+
# handle_line is called for each received line of data.
|
78
|
+
# [_data_] The received data.
|
79
|
+
def receive_data(data)
|
80
|
+
data.each_line do |line|
|
81
|
+
if line[-2,2] != "\r\n"
|
82
|
+
@incomplete_line_buffer << line
|
83
|
+
else
|
84
|
+
line = @incomplete_line_buffer + line
|
85
|
+
@bot.log(line)
|
86
|
+
handle_line(line)
|
87
|
+
@incomplete_line_buffer = ''
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Called upon successfully establishing a socket connection to the IRC
|
93
|
+
# server. Sends initial messages to the server in an attempt to register
|
94
|
+
# a valid client connection.
|
95
|
+
def post_init
|
96
|
+
# Attempt to register connection with server
|
97
|
+
@state = Cereal::Connection::STATE_CONNECTING
|
98
|
+
send_data("PASS #{@password}") if !@password.nil?
|
99
|
+
bot_nick = @bot.nick
|
100
|
+
@temp_nick = 'CerealBot'
|
101
|
+
if bot_nick.is_a? Array
|
102
|
+
@temp_nick = bot_nick.shift
|
103
|
+
else
|
104
|
+
@temp_nick = bot_nick
|
105
|
+
end
|
106
|
+
send_data("NICK #{@temp_nick}")
|
107
|
+
send_data("USER #{@bot.login} 0 * :#{@bot.name}")
|
108
|
+
end
|
109
|
+
|
110
|
+
# Called whenever the connection is closed, whether intentional or not.
|
111
|
+
def unbind
|
112
|
+
@state = Cereal::Connection::STATE_DISCONNECTED
|
113
|
+
@bot.on_disconnect
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# Handle one line of data received from the IRC server. This is probably a
|
119
|
+
# good method to look through if you are a) a masochist, b) a maintainer of
|
120
|
+
# this code or c) stark raving mad.
|
121
|
+
#
|
122
|
+
# Note that if you answer B, you are automatically qualified as both A and C.
|
123
|
+
def handle_line(line)
|
124
|
+
GC.start
|
125
|
+
|
126
|
+
# If this is a PING from the server, respond and return immediately
|
127
|
+
if line =~ /^PING /
|
128
|
+
@bot.on_server_ping(line[5..-1])
|
129
|
+
return
|
130
|
+
end
|
131
|
+
|
132
|
+
line.chomp!
|
133
|
+
|
134
|
+
if @state == Cereal::Connection::STATE_CONNECTING && line =~ /^:\S* (\d{3}) (\S+) (\S*).*$/
|
135
|
+
# We're in the process of connecting
|
136
|
+
code = $~[1].to_i
|
137
|
+
if code == 004
|
138
|
+
# We're connected to the server, joy
|
139
|
+
@state = Cereal::Connection::STATE_CONNECTED
|
140
|
+
@temp_nick = $~[2]
|
141
|
+
@host = $~[3]
|
142
|
+
@bot.confirm_nick(@temp_nick)
|
143
|
+
@bot.on_connect(@host)
|
144
|
+
elsif code == 433
|
145
|
+
# Some bellend is already using our nick
|
146
|
+
if @bot.nick.is_a?(Array) && !@bot.nick.empty?
|
147
|
+
@temp_nick = @bot.nick.shift
|
148
|
+
send_data("NICK #{@temp_nick}")
|
149
|
+
else
|
150
|
+
close_connection
|
151
|
+
raise IrcError, "Nickname(s) already in use."
|
152
|
+
end
|
153
|
+
elsif code >= 400 && code <= 599
|
154
|
+
# Error message of some sort
|
155
|
+
@state = Cereal::Connection::STATE_DISCONNECTED
|
156
|
+
close_connection
|
157
|
+
raise IrcError, "Could not log in to IRC server. (#{line})"
|
158
|
+
end
|
159
|
+
elsif @state == Cereal::Connection::STATE_CONNECTED
|
160
|
+
# We're already connected; handle normal commands
|
161
|
+
tokens = line.split
|
162
|
+
if tokens.size < 2
|
163
|
+
# We don't know what this line means
|
164
|
+
@bot.on_unknown(line)
|
165
|
+
return
|
166
|
+
end
|
167
|
+
|
168
|
+
sender = OpenStruct.new
|
169
|
+
sender.raw = tokens[0]
|
170
|
+
command = tokens[1] ? tokens[1] : ''
|
171
|
+
target = nil
|
172
|
+
|
173
|
+
if sender.raw =~ /^:(\S+)!(\S+)@(\S+)$/
|
174
|
+
# Sender is probably a human (hostmask)
|
175
|
+
sender.nick, sender.login, sender.hostname = $~[1], $~[2], $~[3]
|
176
|
+
elsif sender.raw =~ /^:\S+$/
|
177
|
+
# Message is, presumably, a server response
|
178
|
+
if tokens.size < 3
|
179
|
+
# Message must contain command and parameters; we don't know
|
180
|
+
# what this line means
|
181
|
+
@bot.on_unknown(line)
|
182
|
+
return
|
183
|
+
end
|
184
|
+
|
185
|
+
if command =~ /^\d+$/
|
186
|
+
# Command is a numeric server response
|
187
|
+
response = line[line.index(command, sender.raw.length)+4..-1]
|
188
|
+
command = command.to_i
|
189
|
+
|
190
|
+
if command == 322 && response =~ /([#&]\S+) (\d+) :(.*)/
|
191
|
+
# RPL_LIST: bit of information about a channel
|
192
|
+
channel = $~[1]
|
193
|
+
user_count = $~[2].to_i
|
194
|
+
topic = $~[3]
|
195
|
+
@bot.on_channel_info(channel, user_count, topic)
|
196
|
+
elsif command == 332 && response =~ /([#&]\S+) :(.*)/
|
197
|
+
# RPL_TOPIC: topic of channel we've just joined
|
198
|
+
channel = $~[1]
|
199
|
+
topic = $~[2]
|
200
|
+
@topics[channel] = topic
|
201
|
+
elsif command == 333 && response =~ /([#&]\S+) (\S+) (\S+)/
|
202
|
+
# RPL_TOPICINFO: more topic info of channel we've just joined
|
203
|
+
channel = $~[1]
|
204
|
+
topic = @topics.delete(channel)
|
205
|
+
return if topic.nil?
|
206
|
+
set_by = $~[2]
|
207
|
+
date = Time.at($~[3].to_i)
|
208
|
+
@bot.on_topic(set_by, channel, date, topic, false)
|
209
|
+
elsif command == 353 && response =~ /([=*@]) ([#&]\S+) :(.*)/
|
210
|
+
# RPL_NAMREPLY: list of nicks in a channel we've just joined
|
211
|
+
channel = $~[2]
|
212
|
+
nicks = $~[3].split
|
213
|
+
nicks.each do |str|
|
214
|
+
add_user(Cereal::User.new($~[1], $~[2]), channel) if str =~ /^([@+]{0,2})(\S+)$/
|
215
|
+
end
|
216
|
+
elsif command == 366 && response =~ /([#&]\S+) :End of NAMES list$/
|
217
|
+
# RPL_ENDOFNAMES: we've got the full list of users in a channel
|
218
|
+
# we've just joined
|
219
|
+
channel = $~[1]
|
220
|
+
users = @channels[channel]
|
221
|
+
@bot.on_user_list(channel, users)
|
222
|
+
end
|
223
|
+
|
224
|
+
@bot.on_server_response(command, response)
|
225
|
+
|
226
|
+
return
|
227
|
+
else
|
228
|
+
# This is not a server response; must be a nick without a login
|
229
|
+
# and hostname, or perhaps a NOTICE, etc. from the server
|
230
|
+
sender.nick = sender.raw
|
231
|
+
target = command
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
return if !command || !sender.nick
|
236
|
+
|
237
|
+
command.upcase!
|
238
|
+
sender.nick.slice!(0) if sender.nick[0,1] == ':'
|
239
|
+
target = tokens[2] if !target
|
240
|
+
target.slice!(0) if target[0,1] == ':'
|
241
|
+
|
242
|
+
# Check for CTCP requests
|
243
|
+
if command == 'PRIVMSG' && line.index(":\1") && line.index(":\1") > 0 && line[-1,1] == "\1"
|
244
|
+
request = line[line.index(":\1")+2..-2]
|
245
|
+
|
246
|
+
case
|
247
|
+
when request == "VERSION"
|
248
|
+
@bot.on_version(sender, target)
|
249
|
+
when request =~ /^ACTION/
|
250
|
+
@bot.on_action(sender, target, request[7..-1])
|
251
|
+
when request =~ /^PING/
|
252
|
+
@bot.on_ping(sender, target, request[5..-1])
|
253
|
+
when request == "TIME"
|
254
|
+
@bot.on_time(sender, target)
|
255
|
+
when request == "FINGER"
|
256
|
+
@bot.on_finger(sender, target)
|
257
|
+
when (tokens = request.split).size >= 5 && tokens[0] == "DCC"
|
258
|
+
# TODO Handle DCC requests -- for now, unknown line
|
259
|
+
@bot.on_unknown(line)
|
260
|
+
else
|
261
|
+
# Unknown CTCP message
|
262
|
+
@bot.on_unknown(line)
|
263
|
+
end
|
264
|
+
elsif command == 'PRIVMSG' && target =~ /^[#&].*$/
|
265
|
+
# Normal message to a channel
|
266
|
+
@bot.on_message(sender, target, line[line.index(' :')+2..-1])
|
267
|
+
elsif command == 'PRIVMSG'
|
268
|
+
# Private message to us
|
269
|
+
@bot.on_private_message(sender, line[line.index(' :')+2..-1])
|
270
|
+
elsif command == 'JOIN'
|
271
|
+
# Someone's joined a channel
|
272
|
+
add_user(sender.nick, target)
|
273
|
+
@bot.on_join(sender, target)
|
274
|
+
elsif command == 'PART'
|
275
|
+
# Someone's parted a channel
|
276
|
+
remove_user(sender.nick, target)
|
277
|
+
remove_channel(target) if sender.nick == @bot.nick
|
278
|
+
@bot.on_part(sender, target)
|
279
|
+
elsif command == 'NICK'
|
280
|
+
# Someone's changed their nick
|
281
|
+
rename_user(sender.nick, target)
|
282
|
+
# Update our nick if it was us that changed
|
283
|
+
@bot.confirm_nick(target) if sender.nick == @temp_nick
|
284
|
+
@bot.on_nick_change(sender, target)
|
285
|
+
elsif command == 'NOTICE'
|
286
|
+
# Someone is sending a notice
|
287
|
+
@bot.on_notice(sender, target, line[line.index(' :')+2..-1])
|
288
|
+
elsif command == 'QUIT'
|
289
|
+
@bot.on_quit(sender, line[line.index(' :')+2..-1])
|
290
|
+
# Someone's quit from the server
|
291
|
+
if sender.nick == @nick
|
292
|
+
remove_all_channels
|
293
|
+
else
|
294
|
+
remove_user(sender.nick)
|
295
|
+
end
|
296
|
+
elsif command == 'KICK'
|
297
|
+
# Someone's been kicked from a channel
|
298
|
+
recipient = tokens[3]
|
299
|
+
remove_channel(target) if recipient == @nick
|
300
|
+
remove_user(recipient, target)
|
301
|
+
@bot.on_kick(sender, target, recipient, line[line.index(' :')+2..-1])
|
302
|
+
elsif command == 'MODE'
|
303
|
+
# Someone's changed the mode on a channel or user
|
304
|
+
mode = line[line.index(target, 2)+target.length+1..-1]
|
305
|
+
mode.slice!(0) if mode[0,1] == ':'
|
306
|
+
process_mode(sender, target, mode)
|
307
|
+
elsif command == 'TOPIC'
|
308
|
+
# Someone's changed a channel's topic
|
309
|
+
@bot.on_topic(sender.nick, target, Time.new, line[line.index(' :')+2..-1], true)
|
310
|
+
elsif command == 'INVITE'
|
311
|
+
# Someone's inviting someone else to a channel
|
312
|
+
@bot.on_invite(sender, target, tokens[3])
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Called when the mode of a channel or user is set. Calls the
|
318
|
+
# appropriate on_op, on_deop, etc methods before handing the event
|
319
|
+
# over to @bot.on_mode.
|
320
|
+
def process_mode(sender, target, mode)
|
321
|
+
if target =~ /^[#&]/
|
322
|
+
# A channel's mode is being changed
|
323
|
+
# TODO Implement this
|
324
|
+
else
|
325
|
+
# A user's mode is being changed
|
326
|
+
# TODO Implement this, too
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# Add a user to the specified channel we know about. Overwrite any
|
331
|
+
# existing entry.
|
332
|
+
def add_user(user, channel)
|
333
|
+
user = Cereal::User.new('', user.to_s) if !user.is_a? Cereal::User
|
334
|
+
|
335
|
+
channel.downcase!
|
336
|
+
users = @channels.fetch(channel) { |channel| @channels[channel] = {} }
|
337
|
+
users[user] = user
|
338
|
+
end
|
339
|
+
|
340
|
+
# Remove user from the specified channel or all channels we know about.
|
341
|
+
def remove_user(user, channel=nil)
|
342
|
+
user = Cereal::User.new('', user.to_s) if !user.is_a? Cereal::User
|
343
|
+
|
344
|
+
if channel
|
345
|
+
channel.downcase!
|
346
|
+
users = @channels.fetch(channel) { |channel| @channels[channel] = {} }
|
347
|
+
users.delete(user)
|
348
|
+
else
|
349
|
+
@channels.each { |users| users.delete(user) }
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# Rename a user if they appear in any of our known channels.
|
354
|
+
def rename_user(old_nick, new_nick)
|
355
|
+
old_user = Cereal::User.new('', old_nick)
|
356
|
+
@channels.each do |channel,users|
|
357
|
+
user = users[old_user]
|
358
|
+
user.nick = new_nick if user
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# Change a user's mode (prefix). Valid values for +mode_change+ include:
|
363
|
+
# ["+@+"] add op and voice
|
364
|
+
# ["+@"] add op
|
365
|
+
# ["++"] add voice
|
366
|
+
# ["-+"] remove voice
|
367
|
+
# ["-@"] remove op
|
368
|
+
# ["-@+"] remove op and voice
|
369
|
+
def change_user_mode(nick, channel, mode_change)
|
370
|
+
channel.downcase!
|
371
|
+
users = @channels.fetch(channel) { |channel| @channels[channel] = {} }
|
372
|
+
user = users[Cereal::User.new('', nick)]
|
373
|
+
if user
|
374
|
+
case mode_change
|
375
|
+
when '+@+'
|
376
|
+
user.op(true)
|
377
|
+
user.voice(true)
|
378
|
+
when '+@'
|
379
|
+
user.op(true)
|
380
|
+
when '++'
|
381
|
+
user.voice(true)
|
382
|
+
when '-+'
|
383
|
+
user.voice(false)
|
384
|
+
when '-@'
|
385
|
+
user.op(false)
|
386
|
+
when '-@+'
|
387
|
+
user.op(false)
|
388
|
+
user.voice(false)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
# Remove a channel from our known channels.
|
394
|
+
def remove_channel(channel)
|
395
|
+
@channels.delete(channel.downcase)
|
396
|
+
end
|
397
|
+
|
398
|
+
# Clear all known channels.
|
399
|
+
def remove_all_channels
|
400
|
+
@channels = {}
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
end
|
data/lib/user.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
module Cereal
|
2
|
+
|
3
|
+
# == Synopsis
|
4
|
+
# Cereal::User Represents a user in an IRC channel. This class is
|
5
|
+
# used by Cereal::Connection and Cereal::Bot to keep track of who is
|
6
|
+
# in each channel the bot currenty occupies. You may receive an array
|
7
|
+
# of Users if you request a list of users in a channel from the bot.
|
8
|
+
#
|
9
|
+
# <code>@lower_nick</code> is set on initialization (or when
|
10
|
+
# <code>@nick</code> is changed) and is simply a <code>downcase</code>d
|
11
|
+
# version of <code>@nick</code>, stored as an instance variable for
|
12
|
+
# performance reasons.
|
13
|
+
#
|
14
|
+
# == Author
|
15
|
+
# Ross "Raws" Paffett
|
16
|
+
#
|
17
|
+
# == Copyright
|
18
|
+
# Copyright (c) 2009 Ross Paffett. Licensed under the MIT license:
|
19
|
+
# http://www.opensource.org/licenses/mit-license.php
|
20
|
+
class User
|
21
|
+
|
22
|
+
include Comparable
|
23
|
+
|
24
|
+
attr_reader :prefix, :nick, :lower_nick
|
25
|
+
|
26
|
+
# Constructs a User object with a known prefix and nick.
|
27
|
+
def initialize(prefix, nick)
|
28
|
+
@prefix = prefix.strip
|
29
|
+
@nick = nick.strip
|
30
|
+
@lower_nick = @nick.downcase
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns whether or not this user is an operator.
|
34
|
+
def op?
|
35
|
+
!@prefix.index('@').nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
# Sets whether or not this user is an operator.
|
39
|
+
def op(is_op)
|
40
|
+
if is_op
|
41
|
+
@prefix = voice? ? '@+' : '@'
|
42
|
+
else
|
43
|
+
@prefix = voice? ? '+' : ''
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns whether or not this user has voice.
|
48
|
+
def voice?
|
49
|
+
!@prefix.index('+').nil?
|
50
|
+
end
|
51
|
+
|
52
|
+
# Sets whether or not this user has voice.
|
53
|
+
def voice(has_voice)
|
54
|
+
if has_voice
|
55
|
+
@prefix = op? ? '@+' : '+'
|
56
|
+
else
|
57
|
+
@prefix = op? ? '@' : ''
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the nick of the user with their prefix,
|
62
|
+
# if any, attached.
|
63
|
+
#
|
64
|
+
# u = Cereal::User.new('+', 'Monty')
|
65
|
+
# u.to_s => "+Monty"
|
66
|
+
def to_s
|
67
|
+
@prefix + @nick
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set this user's nick.
|
71
|
+
def nick=(new_nick)
|
72
|
+
@nick = new_nick.strip
|
73
|
+
@lower_nick = @nick.downcase
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns true as in ==.
|
77
|
+
def eql?(other)
|
78
|
+
self == other
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the hash of this User object.
|
82
|
+
def hash
|
83
|
+
@lower_nick.hash
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the result of calling <=> on lowercased nicks.
|
87
|
+
# This is useful for sorting lists of Users.
|
88
|
+
def <=>(other)
|
89
|
+
if other.is_a? Cereal::User
|
90
|
+
other.lower_nick <=> @lower_nick
|
91
|
+
else
|
92
|
+
other.to_s.downcase! <=> @lower_nick
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cereal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.8
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ross Paffett
|
8
|
+
- Jon R.
|
9
|
+
- Brian S
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2009-04-01 15:51:42 -04:00
|
15
|
+
default_executable:
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
18
|
+
name: eventmachine
|
19
|
+
type: :runtime
|
20
|
+
version_requirement:
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.12.2
|
26
|
+
version:
|
27
|
+
description: Cereal provides an event-based IRC connection framework built on EventMachine and inspired by PircBot.
|
28
|
+
email: nobody@inparticul.ar
|
29
|
+
executables: []
|
30
|
+
|
31
|
+
extensions: []
|
32
|
+
|
33
|
+
extra_rdoc_files: []
|
34
|
+
|
35
|
+
files:
|
36
|
+
- lib/cereal.rb
|
37
|
+
- lib/connection.rb
|
38
|
+
- lib/user.rb
|
39
|
+
- CHANGES
|
40
|
+
- MIT-LICENSE
|
41
|
+
- Rakefile
|
42
|
+
- README
|
43
|
+
- TODO
|
44
|
+
has_rdoc: false
|
45
|
+
homepage: http://cerealbot.org/
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.1
|
67
|
+
signing_key:
|
68
|
+
specification_version: 2
|
69
|
+
summary: Event-based IRC connection framework.
|
70
|
+
test_files: []
|
71
|
+
|