net-toc 0.2
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/net/toc.rb +570 -0
- metadata +38 -0
data/net/toc.rb
ADDED
@@ -0,0 +1,570 @@
|
|
1
|
+
# A small library that connects to AOL Instant Messenger using the TOC v2.0 protocol.
|
2
|
+
#
|
3
|
+
# Author:: Ian Henderson (mailto:ian@ianhenderson.org)
|
4
|
+
# Copyright:: Copyright (c) 2006 Ian Henderson
|
5
|
+
# License:: revised BSD license (http://www.opensource.org/licenses/bsd-license.php)
|
6
|
+
# Version:: 0.2
|
7
|
+
#
|
8
|
+
# See Net::TOC for documentation.
|
9
|
+
|
10
|
+
|
11
|
+
require 'socket'
|
12
|
+
|
13
|
+
module Net
|
14
|
+
# == Overview
|
15
|
+
# === Opening a Connection
|
16
|
+
# Pass Net::Toc.new your screenname and password to create a new connection.
|
17
|
+
# It will return a Client object, which is used to communicate with the server.
|
18
|
+
#
|
19
|
+
# client = Net::TOC.new("screenname", "p455w0rd")
|
20
|
+
#
|
21
|
+
# To actually connect, use Client#connect.
|
22
|
+
#
|
23
|
+
# client.connect
|
24
|
+
#
|
25
|
+
# If your program uses an input loop (e.g., reading from stdin), you can start it here.
|
26
|
+
# Otherwise, you must use Client#wait to prevent the program from exiting immediately.
|
27
|
+
#
|
28
|
+
# client.wait
|
29
|
+
#
|
30
|
+
# === Opening a Connection - The Shortcut
|
31
|
+
# If your program only sends IMs in response to received IMs, you can save yourself some code.
|
32
|
+
# Net::TOC.new takes an optional block argument, to be called each time a message arrives (it is passed to Client#on_im).
|
33
|
+
# Client#connect and Client#wait are automatically called.
|
34
|
+
#
|
35
|
+
# Net::TOC.new("screenname", "p455w0rd") do | message, buddy |
|
36
|
+
# # handle the im
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# === Receiving Events
|
40
|
+
# Client supports two kinds of event handlers: Client#on_im and Client#on_error.
|
41
|
+
#
|
42
|
+
# The given block will be called every time the event occurs.
|
43
|
+
# client.on_im do | message, buddy |
|
44
|
+
# puts "#{buddy.screen_name}: #{message}"
|
45
|
+
# end
|
46
|
+
# client.on_error do | error |
|
47
|
+
# puts "!! #{error}"
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# You can also receive events using Buddy#on_status.
|
51
|
+
# Pass it any number of statuses (e.g., :away, :offline, :available, :idle) and a block;
|
52
|
+
# the block will be called each time the buddy's status changes to one of the statuses.
|
53
|
+
#
|
54
|
+
# friend = client.buddy_list.buddy_named("friend")
|
55
|
+
# friend.on_status(:available) do
|
56
|
+
# friend.send_im "Hi!"
|
57
|
+
# end
|
58
|
+
# friend.on_status(:idle, :away) do
|
59
|
+
# friend.send_im "Bye!"
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# === Sending IMs
|
63
|
+
# To send an instant message, call Buddy#send_im.
|
64
|
+
#
|
65
|
+
# friend.send_im "Hello, #{friend.screen_name}!"
|
66
|
+
#
|
67
|
+
# === Status Changes
|
68
|
+
# You can modify your state using these Client methods: Client#go_away, Client#come_back, and Client#idle_time=.
|
69
|
+
#
|
70
|
+
# client.go_away "Away"
|
71
|
+
# client.idle_time = 600 # ten minutes
|
72
|
+
# client.come_back
|
73
|
+
# client.idle_time = 0 # stop being idle
|
74
|
+
#
|
75
|
+
# It is not necessary to call Client#idle_time= continuously; the server will automatically keep track.
|
76
|
+
#
|
77
|
+
# == Examples
|
78
|
+
# === Simple Bot
|
79
|
+
# This bot lets you run ruby commands remotely, but only if your screenname is in the authorized list.
|
80
|
+
#
|
81
|
+
# require 'net/toc'
|
82
|
+
# authorized = ["admin_screenname"]
|
83
|
+
# Net::TOC.new("screenname", "p455w0rd") do | message, buddy |
|
84
|
+
# if authorized.member? buddy.screen_name
|
85
|
+
# begin
|
86
|
+
# result = eval(message.chomp.gsub(/<[^>]+>/,"")) # remove html formatting
|
87
|
+
# buddy.send_im result.to_s if result.respond_to? :to_s
|
88
|
+
# rescue Exception => e
|
89
|
+
# buddy.send_im "#{e.class}: #{e}"
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
# === (Slightly) More Complicated and Contrived Bot
|
94
|
+
# If you message this bot when you're available, you get a greeting and the date you logged in.
|
95
|
+
# If you message it when you're away, you get scolded, and then pestered each time you become available.
|
96
|
+
#
|
97
|
+
# require 'net/toc'
|
98
|
+
# client = Net::TOC.new("screenname", "p455w0rd")
|
99
|
+
# client.on_error do | error |
|
100
|
+
# admin = client.buddy_list.buddy_named("admin_screenname")
|
101
|
+
# admin.send_im("Error: #{error}")
|
102
|
+
# end
|
103
|
+
# client.on_im do | message, buddy, auto_response |
|
104
|
+
# return if auto_response
|
105
|
+
# if buddy.available?
|
106
|
+
# buddy.send_im("Hello, #{buddy.screen_name}. You have been logged in since #{buddy.last_signon}.")
|
107
|
+
# else
|
108
|
+
# buddy.send_im("Liar!")
|
109
|
+
# buddy.on_status(:available) { buddy.send_im("Welcome back, liar.") }
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
# client.connect
|
113
|
+
# client.wait
|
114
|
+
# === Simple Interactive Client
|
115
|
+
# Use screenname<<message to send message.
|
116
|
+
# <<message sends message to the last buddy you messaged.
|
117
|
+
# When somebody sends you a message, it is displayed as screenname>>message.
|
118
|
+
#
|
119
|
+
# require 'net/toc'
|
120
|
+
# print "screen name: "
|
121
|
+
# screen_name = gets.chomp
|
122
|
+
# print "password: "
|
123
|
+
# password = gets.chomp
|
124
|
+
#
|
125
|
+
# client = Net::TOC.new(screen_name, password)
|
126
|
+
#
|
127
|
+
# client.on_im do | message, buddy |
|
128
|
+
# puts "#{buddy}>>#{message}"
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# client.connect
|
132
|
+
#
|
133
|
+
# puts "connected"
|
134
|
+
#
|
135
|
+
# last_buddy = ""
|
136
|
+
# loop do
|
137
|
+
# buddy_name, message = *gets.chomp.split("<<",2)
|
138
|
+
#
|
139
|
+
# buddy_name = last_buddy if buddy_name == ""
|
140
|
+
#
|
141
|
+
# unless buddy_name.nil? or message.nil?
|
142
|
+
# last_buddy = buddy_name
|
143
|
+
# client.buddy_list.buddy_named(buddy_name).send_im(message)
|
144
|
+
# end
|
145
|
+
# end
|
146
|
+
module TOC
|
147
|
+
class CommunicationError < RuntimeError # :nodoc:
|
148
|
+
end
|
149
|
+
|
150
|
+
# Converts a screen name into its canonical form - lowercase, with no spaces.
|
151
|
+
def format_screen_name(screen_name)
|
152
|
+
screen_name.downcase.gsub(/\s+/, '')
|
153
|
+
end
|
154
|
+
|
155
|
+
# Escapes a message so it doesn't confuse the server. You should never have to call this directly.
|
156
|
+
def format_message(message) # :nodoc:
|
157
|
+
msg = message.gsub(/(\r|\n|\r\n)/, '<br>')
|
158
|
+
msg.gsub(/[{}\\"]/, "\\\\\\0") # oh dear
|
159
|
+
end
|
160
|
+
|
161
|
+
# Creates a new Client. See the Client.new method for details.
|
162
|
+
def self.new(screen_name, password, &optional_block) # :yields: message, buddy, auto_response, client
|
163
|
+
Client.new(screen_name, password, &optional_block)
|
164
|
+
end
|
165
|
+
|
166
|
+
Debug = false # :nodoc:
|
167
|
+
|
168
|
+
ErrorCode = {
|
169
|
+
901 => "<param> is not available.",
|
170
|
+
902 => "Warning <param> is not allowed.",
|
171
|
+
903 => "Message dropped; you are exceeding the server speed limit",
|
172
|
+
980 => "Incorrect screen name or password.",
|
173
|
+
981 => "The service is temporarily unavailable.",
|
174
|
+
982 => "Your warning level is too high to sign on.",
|
175
|
+
983 => "You have been connecting and disconnecting too frequently. Wait 10 minutes and try again.",
|
176
|
+
989 => "An unknown error has occurred in the signon process."
|
177
|
+
}
|
178
|
+
|
179
|
+
# The Connection class handles low-level communication using the TOC protocol. You shouldn't use it directly.
|
180
|
+
class Connection # :nodoc:
|
181
|
+
include TOC
|
182
|
+
|
183
|
+
def initialize(screen_name)
|
184
|
+
@user = format_screen_name screen_name
|
185
|
+
@msgseq = rand(100000)
|
186
|
+
end
|
187
|
+
|
188
|
+
def open(server="toc.oscar.aol.com", port=9898)
|
189
|
+
close
|
190
|
+
@sock = TCPSocket.new(server, port)
|
191
|
+
|
192
|
+
@sock.send "FLAPON\r\n\r\n", 0
|
193
|
+
|
194
|
+
toc_version = *recv.unpack("N")
|
195
|
+
|
196
|
+
send [1, 1, @user.length, @user].pack("Nnna*"), :sign_on
|
197
|
+
end
|
198
|
+
|
199
|
+
def close
|
200
|
+
@sock.close unless @sock.nil?
|
201
|
+
end
|
202
|
+
|
203
|
+
FrameType = {
|
204
|
+
:sign_on => 1,
|
205
|
+
:data => 2
|
206
|
+
}
|
207
|
+
|
208
|
+
def send(message, type=:data)
|
209
|
+
message << "\0"
|
210
|
+
puts " send: #{message}" if Debug
|
211
|
+
@msgseq = @msgseq.next
|
212
|
+
header = ['*', FrameType[type], @msgseq, message.length].pack("aCnn")
|
213
|
+
packet = header + message
|
214
|
+
@sock.send packet, 0
|
215
|
+
end
|
216
|
+
|
217
|
+
def recv
|
218
|
+
header = @sock.recv 6
|
219
|
+
raise CommunicationError, "Server didn't send full header." if header.length < 6
|
220
|
+
|
221
|
+
asterisk, type, serverseq, length = header.unpack "aCnn"
|
222
|
+
|
223
|
+
response = @sock.recv length
|
224
|
+
puts " recv: #{response}" if Debug
|
225
|
+
unless type == FrameType[:sign_on]
|
226
|
+
message, value = response.split(":", 2)
|
227
|
+
unless message.nil? or value.nil?
|
228
|
+
msg_sym = message.downcase.to_sym
|
229
|
+
yield msg_sym, value if block_given?
|
230
|
+
end
|
231
|
+
end
|
232
|
+
response
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
# Any unknown methods are assumed to be messages for the server.
|
238
|
+
def method_missing(command, *args)
|
239
|
+
send(([command] + args).join(" "))
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
class Buddy
|
244
|
+
include TOC
|
245
|
+
include Comparable
|
246
|
+
|
247
|
+
attr_reader :screen_name, :status, :warning_level, :last_signon, :idle_time
|
248
|
+
|
249
|
+
def initialize(screen_name, conn) # :nodoc:
|
250
|
+
@screen_name = screen_name
|
251
|
+
@conn = conn
|
252
|
+
@status = :offline
|
253
|
+
@warning_level = 0
|
254
|
+
@on_status = {}
|
255
|
+
@last_signon = :never
|
256
|
+
@idle_time = 0
|
257
|
+
end
|
258
|
+
|
259
|
+
def <=>(other) # :nodoc:
|
260
|
+
format_screen_name(@screen_name) <=> format_screen_name(other.screen_name)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Pass a block to be called when status changes to any of +statuses+. This replaces any previously set on_status block for these statuses.
|
264
|
+
def on_status(*statuses, &callback) #:yields:
|
265
|
+
statuses.each { | status | @on_status[status] = callback }
|
266
|
+
end
|
267
|
+
|
268
|
+
# Returns +true+ unless status == :offline.
|
269
|
+
def online?
|
270
|
+
status != :offline
|
271
|
+
end
|
272
|
+
|
273
|
+
# Returns +true+ if status == :available.
|
274
|
+
def available?
|
275
|
+
status == :available
|
276
|
+
end
|
277
|
+
|
278
|
+
# Returns +true+ if status == :away.
|
279
|
+
def away?
|
280
|
+
status == :away
|
281
|
+
end
|
282
|
+
|
283
|
+
# Returns +true+ if buddy is idle.
|
284
|
+
def idle?
|
285
|
+
@idle_time > 0
|
286
|
+
end
|
287
|
+
|
288
|
+
# Sends the instant message +message+ to the buddy. If +auto_response+ is true, the message is marked as an automated response.
|
289
|
+
def send_im(message, auto_response=false)
|
290
|
+
args = [format_screen_name(@screen_name), "\"" + format_message(message) + "\""]
|
291
|
+
args << "auto" if auto_response
|
292
|
+
@conn.toc_send_im *args
|
293
|
+
end
|
294
|
+
|
295
|
+
# Warns the buddy. If the argument is :anonymous, the buddy is warned anonymously. Otherwise, your name is sent with the warning.
|
296
|
+
# You may only warn buddies who have recently IMed you.
|
297
|
+
def warn(anon=:named)
|
298
|
+
@conn.toc_evil(format_screen_name(@screen_name), anon == :anonymous ? "anon" : "norm")
|
299
|
+
end
|
300
|
+
|
301
|
+
# The string representation of a buddy; equivalent to Buddy#screen_name.
|
302
|
+
def to_s
|
303
|
+
screen_name
|
304
|
+
end
|
305
|
+
|
306
|
+
def raw_update(val) # :nodoc:
|
307
|
+
# TODO: Support user types properly.
|
308
|
+
name, online, warning, signon_time, idle, user_type = *val.split(":")
|
309
|
+
@warning_level = warning.to_i
|
310
|
+
@last_signon = Time.at(signon_time.to_i)
|
311
|
+
@idle_time = idle.to_i
|
312
|
+
if online == "F"
|
313
|
+
update_status :offline
|
314
|
+
elsif user_type[2...3] and user_type[2...3] == "U"
|
315
|
+
update_status :away
|
316
|
+
elsif @idle_time > 0
|
317
|
+
update_status :idle
|
318
|
+
else
|
319
|
+
update_status :available
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
private
|
324
|
+
|
325
|
+
def update_status(status)
|
326
|
+
if @on_status[status] and status != @status
|
327
|
+
@status = status
|
328
|
+
@on_status[status].call
|
329
|
+
else
|
330
|
+
@status = status
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Manages groups and buddies. Don't create one yourself - get one using Client#buddy_list.
|
336
|
+
class BuddyList
|
337
|
+
include TOC
|
338
|
+
|
339
|
+
def initialize(conn) # :nodoc:
|
340
|
+
@conn = conn
|
341
|
+
@buddies = {}
|
342
|
+
@groups = {}
|
343
|
+
@group_order = []
|
344
|
+
end
|
345
|
+
|
346
|
+
# Constructs a printable string representation of the buddy list.
|
347
|
+
def to_s
|
348
|
+
s = ""
|
349
|
+
each_group do | group, buddies |
|
350
|
+
s << "== #{group} ==\n"
|
351
|
+
buddies.each do | buddy |
|
352
|
+
s << " * #{buddy}\n"
|
353
|
+
end
|
354
|
+
end
|
355
|
+
s
|
356
|
+
end
|
357
|
+
|
358
|
+
# Calls the passed block once for each group, passing the group name and the list of buddies as parameters.
|
359
|
+
def each_group
|
360
|
+
@group_order.each do | group |
|
361
|
+
buddies = @groups[group]
|
362
|
+
yield group, buddies
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# Adds a new group named +group_name+.
|
367
|
+
# Setting +sync+ to :dont_sync will prevent this change from being sent to the server.
|
368
|
+
def add_group(group_name, sync=:sync)
|
369
|
+
if @groups[group_name].nil?
|
370
|
+
@groups[group_name] = []
|
371
|
+
@group_order << group_name
|
372
|
+
@conn.toc2_new_group group_name if sync == :sync
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
# Adds the buddy named +buddy_name+ to the group named +group+. If this group does not exist, it is created.
|
377
|
+
# Setting +sync+ to :dont_sync will prevent this change from being sent to the server.
|
378
|
+
def add_buddy(group, buddy_name, sync=:sync)
|
379
|
+
add_group(group, sync) if @groups[group].nil?
|
380
|
+
@groups[group] << buddy_named(buddy_name)
|
381
|
+
@conn.toc2_new_buddies("{g:#{group}\nb:#{format_screen_name(buddy_name)}\n}") if sync == :sync
|
382
|
+
end
|
383
|
+
|
384
|
+
# Removes the buddy named +buddy_name+ from the group named +group+.
|
385
|
+
# Setting +sync+ to :dont_sync will prevent this change from being sent to the server.
|
386
|
+
def remove_buddy(group, buddy_name, sync=:sync)
|
387
|
+
unless @groups[group].nil?
|
388
|
+
buddy = buddy_named(buddy_name)
|
389
|
+
@groups[group].reject! { | b | b == buddy }
|
390
|
+
@conn.toc2_remove_buddy(format_screen_name(buddy_name), group) if sync == :sync
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
# Returns the buddy named +name+. If the buddy does not exist, it is created. +name+ is not case- or whitespace-sensitive.
|
395
|
+
def buddy_named(name)
|
396
|
+
formatted_name = format_screen_name(name)
|
397
|
+
buddy = @buddies[formatted_name]
|
398
|
+
if buddy.nil?
|
399
|
+
buddy = Buddy.new(name, @conn)
|
400
|
+
@buddies[formatted_name] = buddy
|
401
|
+
end
|
402
|
+
buddy
|
403
|
+
end
|
404
|
+
|
405
|
+
# Decodes the buddy list from raw CONFIG data.
|
406
|
+
def decode_toc(val) # :nodoc:
|
407
|
+
current_group = nil
|
408
|
+
val.each_line do | line |
|
409
|
+
letter, name = *line.split(":")
|
410
|
+
name = name.chomp
|
411
|
+
case letter
|
412
|
+
when "g"
|
413
|
+
add_group(name, :dont_sync)
|
414
|
+
current_group = name
|
415
|
+
when "b"
|
416
|
+
add_buddy(current_group, name, :dont_sync)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
# A high-level interface to TOC. It supports asynchronous message handling through the use of threads, and maintains a list of buddies.
|
423
|
+
class Client
|
424
|
+
include TOC
|
425
|
+
|
426
|
+
attr_reader :buddy_list, :screen_name
|
427
|
+
|
428
|
+
# You must initialize the client with your screen name and password.
|
429
|
+
# If a block is given, Client#listen will be invoked with the block after initialization.
|
430
|
+
def initialize(screen_name, password, &optional_block) # :yields: message, buddy, auto_response, client
|
431
|
+
@conn = Connection.new(screen_name)
|
432
|
+
@screen_name = format_screen_name(screen_name)
|
433
|
+
@password = password
|
434
|
+
@callbacks = {}
|
435
|
+
@buddy_list = BuddyList.new(@conn)
|
436
|
+
add_callback(:config, :config2) { |v| @buddy_list.decode_toc v }
|
437
|
+
add_callback(:update_buddy, :update_buddy2) { |v| update_buddy v }
|
438
|
+
on_error do | error |
|
439
|
+
$stderr.puts "Error: #{error}"
|
440
|
+
end
|
441
|
+
listen(&optional_block) if block_given?
|
442
|
+
end
|
443
|
+
|
444
|
+
# Connects to the server and starts an event-handling thread.
|
445
|
+
def connect(server="toc.oscar.aol.com", port=9898, oscar_server="login.oscar.aol.com", oscar_port=5190)
|
446
|
+
@conn.open(server, port)
|
447
|
+
code = 7696 * @screen_name[0] * @password[0]
|
448
|
+
@conn.toc2_signon(oscar_server, oscar_port, @screen_name, roasted_pass, "english", "\"TIC:toc.rb\"", 160, code)
|
449
|
+
|
450
|
+
@conn.recv do |msg, val|
|
451
|
+
if msg == :sign_on
|
452
|
+
@conn.toc_add_buddy(@screen_name)
|
453
|
+
@conn.toc_init_done
|
454
|
+
capabilities.each do |capability|
|
455
|
+
@conn.toc_set_caps(capability)
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
@thread.kill unless @thread.nil? # ha
|
460
|
+
@thread = Thread.new { loop { event_loop } }
|
461
|
+
end
|
462
|
+
|
463
|
+
# Disconnects and kills the event-handling thread. You may still add callbacks while disconnected.
|
464
|
+
def disconnect
|
465
|
+
@thread.kill unless @thread.nil?
|
466
|
+
@thread = nil
|
467
|
+
@conn.close
|
468
|
+
end
|
469
|
+
|
470
|
+
# Connects to the server and forwards received IMs to the given block. See Client#connect for the arguments.
|
471
|
+
def listen(*args) # :yields: message, buddy, auto_response, client
|
472
|
+
on_im do | message, buddy, auto_response |
|
473
|
+
yield message, buddy, auto_response, self
|
474
|
+
end
|
475
|
+
connect(*args)
|
476
|
+
wait
|
477
|
+
end
|
478
|
+
|
479
|
+
# Pass a block to be called every time an IM is received. This will replace any previous on_im handler.
|
480
|
+
def on_im
|
481
|
+
raise ArgumentException, "on_im requires a block argument" unless block_given?
|
482
|
+
add_callback(:im_in, :im_in2) do |val|
|
483
|
+
screen_name, auto, f2, *message = *val.split(":")
|
484
|
+
message = message.join(":")
|
485
|
+
buddy = @buddy_list.buddy_named(screen_name)
|
486
|
+
auto_response = auto == "T"
|
487
|
+
yield message, buddy, auto_response
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
# Pass a block to be called every time an error occurs. This will replace any previous on_error handler, including the default exception-raising behavior.
|
492
|
+
def on_error
|
493
|
+
raise ArgumentException, "on_error requires a block argument" unless block_given?
|
494
|
+
add_callback(:error) do |val|
|
495
|
+
code, param = *val.split(":")
|
496
|
+
error = ErrorCode[code.to_i]
|
497
|
+
error = "An unknown error occurred." if error.nil?
|
498
|
+
error.gsub!("<param>", param) unless param.nil?
|
499
|
+
yield error
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# Sets your status to away and +away_message+ as your away message.
|
504
|
+
def go_away(away_message)
|
505
|
+
@conn.toc_set_away "\"#{away_message.gsub("\"","\\\"")}\""
|
506
|
+
end
|
507
|
+
|
508
|
+
# Sets your status to available.
|
509
|
+
def come_back
|
510
|
+
@conn.toc_set_away
|
511
|
+
end
|
512
|
+
|
513
|
+
# Sets your idle time in seconds. You only need to set this once; afterwards, the server will keep track itself.
|
514
|
+
# Set to 0 to stop being idle.
|
515
|
+
def idle_time=(seconds)
|
516
|
+
@conn.toc_set_idle seconds
|
517
|
+
end
|
518
|
+
|
519
|
+
# Waits for the event-handling thread for +limit+ seconds, or indefinitely if no argument is given. Use this to prevent your program from exiting prematurely.
|
520
|
+
# For example, the following script will exit right after connecting:
|
521
|
+
# client = Net::TOC.new("screenname", "p455w0rd")
|
522
|
+
# client.connect
|
523
|
+
# To prevent this, use wait:
|
524
|
+
# client = Net::TOC.new("screenname", "p455w0rd")
|
525
|
+
# client.connect
|
526
|
+
# client.wait
|
527
|
+
# Now the program will wait until the client has disconnected before exiting.
|
528
|
+
def wait(limit=nil)
|
529
|
+
@thread.join limit
|
530
|
+
end
|
531
|
+
|
532
|
+
# Returns a list of this client's capabilities. Not yet implemented.
|
533
|
+
def capabilities
|
534
|
+
[] # TODO
|
535
|
+
end
|
536
|
+
|
537
|
+
private
|
538
|
+
|
539
|
+
# Returns an "encrypted" version of the password to be sent across the internet.
|
540
|
+
# Decrypting it is trivial, though.
|
541
|
+
def roasted_pass
|
542
|
+
tictoc = "Tic/Toc".unpack "c*"
|
543
|
+
pass = @password.unpack "c*"
|
544
|
+
roasted = "0x"
|
545
|
+
pass.each_index do |i|
|
546
|
+
roasted << sprintf("%02x", pass[i] ^ tictoc[i % tictoc.length])
|
547
|
+
end
|
548
|
+
roasted
|
549
|
+
end
|
550
|
+
|
551
|
+
def update_buddy(val)
|
552
|
+
screen_name = val.split(":").first.chomp
|
553
|
+
buddy = @buddy_list.buddy_named(screen_name)
|
554
|
+
buddy.raw_update(val)
|
555
|
+
end
|
556
|
+
|
557
|
+
def add_callback(*callbacks, &block)
|
558
|
+
callbacks.each do |callback|
|
559
|
+
@callbacks[callback] = block;
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
def event_loop
|
564
|
+
@conn.recv do |msg, val|
|
565
|
+
@callbacks[msg].call(val) unless @callbacks[msg].nil?
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
end
|
570
|
+
end
|
metadata
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: net-toc
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.2"
|
7
|
+
date: 2006-08-02
|
8
|
+
summary: "A ruby library which uses the TOC protocol to connect to AOL's instant messaging
|
9
|
+
network."
|
10
|
+
require_paths:
|
11
|
+
- "."
|
12
|
+
email: ian@ianhenderson.org
|
13
|
+
homepage: http://net-toc.rubyforge.org
|
14
|
+
rubyforge_project: net-toc
|
15
|
+
description:
|
16
|
+
autorequire:
|
17
|
+
default_executable:
|
18
|
+
bindir: bin
|
19
|
+
has_rdoc: true
|
20
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
21
|
+
requirements:
|
22
|
+
-
|
23
|
+
- ">"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.0.0
|
26
|
+
version:
|
27
|
+
platform: ruby
|
28
|
+
authors:
|
29
|
+
- Ian Henderson
|
30
|
+
files:
|
31
|
+
- net/toc.rb
|
32
|
+
test_files: []
|
33
|
+
rdoc_options: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
requirements: []
|
38
|
+
dependencies: []
|