cereal 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|
+
|