cinch 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +4 -2
- data/lib/cinch.rb +8 -4
- data/lib/cinch/base.rb +41 -9
- data/lib/cinch/irc.rb +1 -0
- data/lib/cinch/irc/message.rb +8 -2
- data/lib/cinch/irc/parser.rb +28 -19
- data/lib/cinch/irc/socket.rb +40 -6
- data/spec/base_spec.rb +5 -0
- data/spec/irc/parser_spec.rb +1 -1
- data/spec/options_spec.rb +1 -1
- metadata +13 -6
data/README.rdoc
CHANGED
@@ -42,7 +42,9 @@ It doesn't take much to work out what's happening here, but I'll explain it anyw
|
|
42
42
|
|
43
43
|
First we run the <em>Cinch::setup</em> block which is required in every application. Cinch is boxed
|
44
44
|
with a load of default values, so the <b>only</b> option required in this block is the <em>server</em>
|
45
|
-
option.
|
45
|
+
option.
|
46
|
+
|
47
|
+
We then define a plugin using the <em>plugin</em> method and pass it a rule (a String in this
|
46
48
|
case). Every plugin must be mapped to a rule. When the rule matches an IRC message its block is
|
47
49
|
invoked, the contents of which contains your plugin interface. The variable passed to the block is
|
48
50
|
an instance of IRC::Message. This provides us with a couple of helper methods which can be used to
|
@@ -131,7 +133,7 @@ More examples of this can be found in the /examples directory
|
|
131
133
|
Since version 0.2, Cinch supports named parameter patterns. It means stuff like the this works:
|
132
134
|
|
133
135
|
bot.plugin("say :n-digit :text") do |m|
|
134
|
-
m.args[:n].to_i.times
|
136
|
+
m.args[:n].to_i.times do
|
135
137
|
m.reply m.args[:text]
|
136
138
|
end
|
137
139
|
end
|
data/lib/cinch.rb
CHANGED
@@ -9,11 +9,15 @@ require 'cinch/rules'
|
|
9
9
|
require 'cinch/base'
|
10
10
|
|
11
11
|
module Cinch
|
12
|
-
VERSION = '0.3.
|
12
|
+
VERSION = '0.3.1'
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
Cinch::Base
|
14
|
+
class << self
|
15
|
+
|
16
|
+
# Setup bot options and return a new Cinch::Base instance
|
17
|
+
def setup(ops={}, &blk)
|
18
|
+
Cinch::Base.new(ops, &blk)
|
19
|
+
end
|
20
|
+
alias_method :configure, :setup
|
17
21
|
end
|
18
22
|
|
19
23
|
end
|
data/lib/cinch/base.rb
CHANGED
@@ -40,10 +40,11 @@ module Cinch
|
|
40
40
|
:nick => "Cinch",
|
41
41
|
:nick_suffix => '_',
|
42
42
|
:username => 'cinch',
|
43
|
-
:realname => "Cinch IRC
|
43
|
+
:realname => "Cinch IRC Bot Building Framework",
|
44
44
|
:prefix => '!',
|
45
45
|
:usermode => 0,
|
46
|
-
:password => nil
|
46
|
+
:password => nil,
|
47
|
+
:ssl => false,
|
47
48
|
}
|
48
49
|
|
49
50
|
# Options can be passed via a hash, a block, or on the instance
|
@@ -70,6 +71,7 @@ module Cinch
|
|
70
71
|
|
71
72
|
@rules = Rules.new
|
72
73
|
@listeners = {}
|
74
|
+
@listeners[:ctcp] = {}
|
73
75
|
|
74
76
|
@custom_patterns = {
|
75
77
|
'digit' => "(\\d+?)",
|
@@ -79,7 +81,7 @@ module Cinch
|
|
79
81
|
'lower' => "([a-z]+?)",
|
80
82
|
}
|
81
83
|
|
82
|
-
@irc = IRC::Socket.new(options[:server], options[:port])
|
84
|
+
@irc = IRC::Socket.new(options[:server], options[:port], options[:ssl])
|
83
85
|
@parser = IRC::Parser.new
|
84
86
|
|
85
87
|
# Default listeners
|
@@ -93,6 +95,8 @@ module Cinch
|
|
93
95
|
if @options.respond_to?(:channels)
|
94
96
|
on("004") { @options.channels.each {|c| @irc.join(c) } }
|
95
97
|
end
|
98
|
+
|
99
|
+
on(:ctcp, :version) {|m| m.ctcp_reply "Cinch IRC Bot Building Framework v#{Cinch::VERSION}"}
|
96
100
|
end
|
97
101
|
|
98
102
|
# Parse command line options
|
@@ -106,6 +110,7 @@ module Cinch
|
|
106
110
|
op.on("-n nick") {|v| options[:nick] = v }
|
107
111
|
op.on("-c command_prefix") {|v| options[:prefix] = v }
|
108
112
|
op.on("-v", "--verbose", "Enable verbose mode") {|v| options[:verbose] = true }
|
113
|
+
op.on("--ssl") {|v| options[:ssl] = true }
|
109
114
|
op.on("-C", "--channels x,y,z", Array, "Autojoin channels") {|v|
|
110
115
|
options[:channels] = v.map {|c| %w(# + &).include?(c[0].chr) ? c : c.insert(0, '#') }
|
111
116
|
}
|
@@ -150,11 +155,25 @@ module Cinch
|
|
150
155
|
# String and not Integer. This is because 001.to_s == "1" so the
|
151
156
|
# command will not work as expected.
|
152
157
|
def on(*commands, &blk)
|
153
|
-
commands.
|
154
|
-
|
155
|
-
|
158
|
+
if commands.first == :message
|
159
|
+
rule, options = commands[1..2]
|
160
|
+
options = {} unless options.is_a?(Hash)
|
161
|
+
plugin(rule, options, &blk)
|
162
|
+
elsif commands.first == :ctcp
|
163
|
+
action = commands[1]
|
164
|
+
|
165
|
+
if @listeners[:ctcp].key?(action)
|
166
|
+
@listeners[:ctcp][action] << blk
|
156
167
|
else
|
157
|
-
@listeners[
|
168
|
+
@listeners[:ctcp][action] = [blk]
|
169
|
+
end
|
170
|
+
else
|
171
|
+
commands.map {|x| x.to_s.downcase.to_sym }.each do |cmd|
|
172
|
+
if @listeners.key?(cmd)
|
173
|
+
@listeners[cmd] << blk
|
174
|
+
else
|
175
|
+
@listeners[cmd] = [blk]
|
176
|
+
end
|
158
177
|
end
|
159
178
|
end
|
160
179
|
end
|
@@ -263,6 +282,7 @@ module Cinch
|
|
263
282
|
exit
|
264
283
|
end
|
265
284
|
end
|
285
|
+
alias :start :run
|
266
286
|
|
267
287
|
# Process the next line read from the server
|
268
288
|
def process(line)
|
@@ -275,7 +295,15 @@ module Cinch
|
|
275
295
|
@listeners[:any].each { |l| l.call(message) } if @listeners.key?(:any)
|
276
296
|
|
277
297
|
if @listeners.key?(message.symbol)
|
278
|
-
|
298
|
+
if message.symbol == :ctcp
|
299
|
+
action = message.ctcp_action.downcase.to_sym
|
300
|
+
|
301
|
+
if @listeners[:ctcp].include?(action)
|
302
|
+
@listeners[:ctcp][action].each {|l| l.call(message) }
|
303
|
+
end
|
304
|
+
else
|
305
|
+
@listeners[message.symbol].each {|l| l.call(message) }
|
306
|
+
end
|
279
307
|
end
|
280
308
|
|
281
309
|
if [:privmsg].include?(message.symbol)
|
@@ -295,7 +323,11 @@ module Cinch
|
|
295
323
|
prefix = rule.options[:prefix]
|
296
324
|
end
|
297
325
|
else
|
298
|
-
|
326
|
+
if [:bot, :botnick, options.nick].include? options.prefix
|
327
|
+
prefix = options.nick + "[:,] "
|
328
|
+
else
|
329
|
+
prefix = options.prefix
|
330
|
+
end
|
299
331
|
end
|
300
332
|
else
|
301
333
|
prefix = nil
|
data/lib/cinch/irc.rb
CHANGED
data/lib/cinch/irc/message.rb
CHANGED
@@ -24,7 +24,7 @@ module Cinch
|
|
24
24
|
attr_reader :params
|
25
25
|
|
26
26
|
# Message symbol (lowercase command, ie. :privmsg, :join, :kick)
|
27
|
-
|
27
|
+
attr_accessor :symbol
|
28
28
|
|
29
29
|
# The raw string passed to ::new
|
30
30
|
attr_reader :raw
|
@@ -33,7 +33,7 @@ module Cinch
|
|
33
33
|
attr_reader :data
|
34
34
|
|
35
35
|
# Message text
|
36
|
-
|
36
|
+
attr_accessor :text
|
37
37
|
|
38
38
|
# Arguments parsed from a rule
|
39
39
|
attr_accessor :args
|
@@ -103,6 +103,12 @@ module Cinch
|
|
103
103
|
@irc.privmsg(data[:channel], "#{data[:nick]}: #{text}")
|
104
104
|
end
|
105
105
|
|
106
|
+
# Send a CTCP reply
|
107
|
+
def ctcp_reply(text)
|
108
|
+
recipient = data[:channel] || data[:nick]
|
109
|
+
@irc.notice(recipient, "\001#{@data[:ctcp_action]} #{text}\001")
|
110
|
+
end
|
111
|
+
|
106
112
|
# The deadly /me action
|
107
113
|
def action(text)
|
108
114
|
reply("\001ACTION #{text}\001")
|
data/lib/cinch/irc/parser.rb
CHANGED
@@ -49,31 +49,33 @@ module Cinch
|
|
49
49
|
# Set up some default patterns used directly by this class
|
50
50
|
def setup_patterns
|
51
51
|
add_pattern :letter, /[a-zA-Z]/
|
52
|
-
|
52
|
+
add_pattern :hex, /[\dA-Fa-f]/
|
53
53
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
54
|
+
add_pattern :ip4addr, /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
55
|
+
add_pattern :ip6addr, /[\dA-Fa-f](?::[\dA-Fa-f]){7}|0:0:0:0:0:(?:0|[Ff]{4}):#{pattern(:ip4addr)}/
|
56
|
+
add_pattern :hostaddr, /#{pattern(:ip4addr)}|#{pattern(:ip6addr)}/
|
57
|
+
add_pattern :shortname, /[A-Za-z0-9][A-Za-z0-9-]*/
|
58
|
+
add_pattern :hostname, /#{pattern(:shortname)}(?:\.#{pattern(:shortname)})*/
|
59
|
+
add_pattern :host, /#{pattern(:hostname)}|#{pattern(:hostaddr)}/
|
60
60
|
|
61
|
-
|
62
|
-
|
61
|
+
add_pattern :user, /[^\x00\x10\x0D\x20@]+/
|
62
|
+
add_pattern :nick, /[A-Za-z\[\]\\`_^{|}][A-Za-z\d\[\]\\`_^{|}-]{0,19}/
|
63
63
|
|
64
|
-
|
64
|
+
add_pattern :ctcp, /\001(\S+)(?:\s([^\x00\r\n\001]+))?\001$/
|
65
65
|
|
66
|
-
|
66
|
+
add_pattern :userhost, /(#{pattern(:nick)})(?:(?:!(#{pattern(:user)}))?@(#{pattern(:host)}))?/
|
67
67
|
|
68
|
-
|
69
|
-
add_pattern :prefix, /(?:(\S+)\x20)?/
|
70
|
-
add_pattern :command, /([A-Za-z]+|\d{3})/
|
71
|
-
add_pattern :middle, /[^\x00\x20\r\n:][^\x00\x20\r\n]*/
|
72
|
-
add_pattern :trailing, /[^\x00\r\n]*/
|
73
|
-
add_pattern :params, /(?:((?:#{pattern(:middle)}){0,14}(?::?#{pattern(:trailing)})?))/
|
74
|
-
add_pattern :message, /\A#{pattern(:prefix)}#{pattern(:command)}#{pattern(:params)}\Z/
|
68
|
+
add_pattern :channel, /(?:[#+&]|![A-Z\d]{5})[^\x00\x07\x10\x0D\x20,:]/
|
75
69
|
|
76
|
-
|
70
|
+
# Server message parsing patterns
|
71
|
+
add_pattern :prefix, /(?:(\S+)\x20)?/
|
72
|
+
add_pattern :command, /([A-Za-z]+|\d{3})/
|
73
|
+
add_pattern :middle, /[^\x00\x20\r\n:][^\x00\x20\r\n]*/
|
74
|
+
add_pattern :trailing, /[^\x00\r\n]*/
|
75
|
+
add_pattern :params, /(?:((?:#{pattern(:middle)}){0,14}(?::?#{pattern(:trailing)})?))/
|
76
|
+
add_pattern :message, /\A#{pattern(:prefix)}#{pattern(:command)}#{pattern(:params)}\Z/
|
77
|
+
|
78
|
+
add_pattern :params_scan, /(?!:)([^\x00\x20\r\n:]+)|:([^\x00\r\n]*)/
|
77
79
|
end
|
78
80
|
private :setup_patterns
|
79
81
|
|
@@ -101,6 +103,13 @@ module Cinch
|
|
101
103
|
end
|
102
104
|
end
|
103
105
|
|
106
|
+
# Parse CTCP response
|
107
|
+
if m.symbol == :privmsg && data = m.text.match(pattern(:ctcp))
|
108
|
+
m.symbol = :ctcp
|
109
|
+
m.text = data[2]
|
110
|
+
m.add(:ctcp_action, data[1])
|
111
|
+
end
|
112
|
+
|
104
113
|
m # Return our IRC::Message
|
105
114
|
end
|
106
115
|
|
data/lib/cinch/irc/socket.rb
CHANGED
@@ -49,6 +49,12 @@ module Cinch
|
|
49
49
|
# The TCPSocket instance
|
50
50
|
attr_reader :socket
|
51
51
|
|
52
|
+
# How long to wait before aborting a connection
|
53
|
+
attr_accessor :timeout
|
54
|
+
|
55
|
+
# Maximum attempts to retry failed connection
|
56
|
+
attr_accessor :attempts
|
57
|
+
|
52
58
|
# Creates a new IRCSocket and automatically connects
|
53
59
|
#
|
54
60
|
# === Example
|
@@ -57,7 +63,7 @@ module Cinch
|
|
57
63
|
# while data = irc.read
|
58
64
|
# puts data
|
59
65
|
# end
|
60
|
-
def self.open(server, port=6667)
|
66
|
+
def self.open(server, port=6667, ssl=false)
|
61
67
|
irc = new(server, port)
|
62
68
|
irc.connect
|
63
69
|
irc
|
@@ -67,12 +73,15 @@ module Cinch
|
|
67
73
|
# If an optional code block is given, it will be passed an instance of the IRCSocket.
|
68
74
|
# NOTE: Using the block form does not mean the socket will send the applicable QUIT
|
69
75
|
# command to leave the IRC server. You must send this yourself.
|
70
|
-
def initialize(server, port=6667)
|
76
|
+
def initialize(server, port=6667, ssl=false)
|
71
77
|
@server = server
|
72
78
|
@port = port
|
79
|
+
@ssl = ssl
|
73
80
|
|
74
81
|
@socket = nil
|
75
82
|
@connected = false
|
83
|
+
@timeout = 5
|
84
|
+
@attempts = 5
|
76
85
|
|
77
86
|
if block_given?
|
78
87
|
connect
|
@@ -88,13 +97,38 @@ module Cinch
|
|
88
97
|
|
89
98
|
# Connect to an IRC server, returns true on a successful connection, or
|
90
99
|
# raises otherwise
|
91
|
-
def connect(server=nil, port=nil)
|
100
|
+
def connect(server=nil, port=nil, ssl=false)
|
92
101
|
@server = server if server
|
93
102
|
@port = port if port
|
94
|
-
|
95
|
-
|
103
|
+
@ssl = ssl if ssl
|
104
|
+
|
105
|
+
Timeout.timeout(@timeout) do
|
106
|
+
socket = TCPSocket.new(@server, @port)
|
107
|
+
|
108
|
+
if @ssl
|
109
|
+
require 'openssl'
|
110
|
+
|
111
|
+
ssl = OpenSSL::SSL::SSLContext.new
|
112
|
+
ssl.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
113
|
+
@socket = OpenSSL::SSL::SSLSocket.new(socket, ssl)
|
114
|
+
@socket.sync = true
|
115
|
+
@socket.connect
|
116
|
+
else
|
117
|
+
@socket = socket
|
118
|
+
end
|
119
|
+
end
|
120
|
+
rescue Timeout::Error
|
121
|
+
if @attempts == 0
|
122
|
+
puts "Maximum attempts reached. Aborting .."
|
123
|
+
exit 1
|
124
|
+
else
|
125
|
+
puts "Connection timed out. Retrying #{@attempts} more time#{'s' unless @attempts == 1} .."
|
126
|
+
@attempts -= 1
|
127
|
+
connect(server, port)
|
128
|
+
end
|
96
129
|
rescue Interrupt
|
97
|
-
|
130
|
+
puts "\nAborting connection .."
|
131
|
+
exit
|
98
132
|
rescue Exception
|
99
133
|
raise
|
100
134
|
else
|
data/spec/base_spec.rb
CHANGED
@@ -28,6 +28,11 @@ describe "Cinch::Base" do
|
|
28
28
|
it "should add a 433 nick taken listener" do
|
29
29
|
@base.listeners.should include :'433'
|
30
30
|
end
|
31
|
+
|
32
|
+
it "should add a ctcp version listener" do
|
33
|
+
@base.listeners.should include :ctcp
|
34
|
+
@base.listeners[:ctcp].should include :version
|
35
|
+
end
|
31
36
|
|
32
37
|
it "should add default custom_patterns" do
|
33
38
|
[:digit, :word, :string, :upper, :lower].each do |l|
|
data/spec/irc/parser_spec.rb
CHANGED
@@ -5,11 +5,11 @@ commands = {
|
|
5
5
|
:ping => "PING :foobar",
|
6
6
|
:nick => ":foo!~baz@host.com NICK Baz",
|
7
7
|
:join => ":foo!~bar@host.com JOIN #baz",
|
8
|
+
:ctcp => ":foo!~bar@host.com PRIVMSG Baz :\001VERSION\001",
|
8
9
|
|
9
10
|
:privmsg => {
|
10
11
|
"to a channel" => ":foo!~bar@host.com PRIVMSG #baz :hello world",
|
11
12
|
"to a user" => ":foo!~bar@host.com PRIVMSG Baz :hello world",
|
12
|
-
"with an action" => ":foo!~bar@host.com PRIVMSG #baz :\001ACTION hello word\001",
|
13
13
|
},
|
14
14
|
|
15
15
|
:notice => {
|
data/spec/options_spec.rb
CHANGED
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cinch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 17
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
8
|
- 3
|
8
|
-
-
|
9
|
-
version: 0.3.
|
9
|
+
- 1
|
10
|
+
version: 0.3.1
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Lee 'injekt' Jarvis
|
@@ -14,16 +15,18 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-05-
|
18
|
+
date: 2010-05-26 00:00:00 +01:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
22
|
name: rspec
|
22
23
|
prerelease: false
|
23
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
24
26
|
requirements:
|
25
27
|
- - "="
|
26
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 27
|
27
30
|
segments:
|
28
31
|
- 1
|
29
32
|
- 3
|
@@ -83,25 +86,29 @@ rdoc_options:
|
|
83
86
|
require_paths:
|
84
87
|
- lib
|
85
88
|
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
86
90
|
requirements:
|
87
91
|
- - ">="
|
88
92
|
- !ruby/object:Gem::Version
|
93
|
+
hash: 57
|
89
94
|
segments:
|
90
95
|
- 1
|
91
96
|
- 8
|
92
|
-
-
|
93
|
-
version: 1.8.
|
97
|
+
- 7
|
98
|
+
version: 1.8.7
|
94
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
95
101
|
requirements:
|
96
102
|
- - ">="
|
97
103
|
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
98
105
|
segments:
|
99
106
|
- 0
|
100
107
|
version: "0"
|
101
108
|
requirements: []
|
102
109
|
|
103
110
|
rubyforge_project:
|
104
|
-
rubygems_version: 1.3.
|
111
|
+
rubygems_version: 1.3.7
|
105
112
|
signing_key:
|
106
113
|
specification_version: 3
|
107
114
|
summary: An IRC Bot Building Framework
|