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 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. We then define a plugin using the <em>plugin</em> method and pass it a rule (a String in this
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.0'
12
+ VERSION = '0.3.1'
13
13
 
14
- # Setup bot options and return a new Cinch::Base instance
15
- def self.setup(ops={}, &blk)
16
- Cinch::Base.new(ops, &blk)
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 Microframework",
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.map {|x| x.to_s.downcase.to_sym }.each do |cmd|
154
- if @listeners.key?(cmd)
155
- @listeners[cmd] << blk
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[cmd] = [blk]
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
- @listeners[message.symbol].each {|l| l.call(message) }
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
- prefix = options.prefix
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
@@ -2,6 +2,7 @@ lib = File.dirname(__FILE__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  require 'socket'
5
+ require 'timeout'
5
6
 
6
7
  require 'irc/parser'
7
8
  require 'irc/message'
@@ -24,7 +24,7 @@ module Cinch
24
24
  attr_reader :params
25
25
 
26
26
  # Message symbol (lowercase command, ie. :privmsg, :join, :kick)
27
- attr_reader :symbol
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
- attr_reader :text
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")
@@ -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
- add_pattern :hex, /[\dA-Fa-f]/
52
+ add_pattern :hex, /[\dA-Fa-f]/
53
53
 
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)}/
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
- add_pattern :user, /[^\x00\x10\x0D\x20@]+/
62
- add_pattern :nick, /[A-Za-z\[\]\\`_^{|}][A-Za-z\d\[\]\\`_^{|}-]{0,19}/
61
+ add_pattern :user, /[^\x00\x10\x0D\x20@]+/
62
+ add_pattern :nick, /[A-Za-z\[\]\\`_^{|}][A-Za-z\d\[\]\\`_^{|}-]{0,19}/
63
63
 
64
- add_pattern :userhost, /(#{pattern(:nick)})(?:(?:!(#{pattern(:user)}))?@(#{pattern(:host)}))?/
64
+ add_pattern :ctcp, /\001(\S+)(?:\s([^\x00\r\n\001]+))?\001$/
65
65
 
66
- add_pattern :channel, /(?:[#+&]|![A-Z\d]{5})[^\x00\x07\x10\x0D\x20,:]/
66
+ add_pattern :userhost, /(#{pattern(:nick)})(?:(?:!(#{pattern(:user)}))?@(#{pattern(:host)}))?/
67
67
 
68
- # Server message parsing patterns
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
- add_pattern :params_scan, /(?!:)([^\x00\x20\r\n:]+)|:([^\x00\r\n]*)/
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
 
@@ -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
- @socket = TCPSocket.new(@server, @port)
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
- raise
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|
@@ -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
@@ -29,7 +29,7 @@ describe "Cinch::Base options" do
29
29
  :nick => 'Cinch',
30
30
  :nick_suffix => '_',
31
31
  :username => 'cinch',
32
- :realname => 'Cinch IRC Microframework',
32
+ :realname => 'Cinch IRC Bot Building Framework',
33
33
  :usermode => 0,
34
34
  :prefix => '!',
35
35
  :password => nil,
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
- - 0
9
- version: 0.3.0
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 00:00:00 +01:00
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
- - 6
93
- version: 1.8.6
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.6
111
+ rubygems_version: 1.3.7
105
112
  signing_key:
106
113
  specification_version: 3
107
114
  summary: An IRC Bot Building Framework