cinch 0.3.0 → 0.3.1
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/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
|