irc 0.0.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.markdown +50 -0
- data/lib/irc.rb +17 -0
- data/lib/irc/base.rb +6 -0
- data/lib/irc/bot.rb +114 -0
- data/lib/irc/callback.rb +57 -0
- data/lib/irc/connection.rb +91 -0
- data/lib/irc/extensions.rb +33 -0
- data/lib/irc/message.rb +46 -0
- data/lib/irc/store.rb +24 -0
- metadata +53 -0
data/README.markdown
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Usage
|
|
2
|
+
=====
|
|
3
|
+
|
|
4
|
+
## Quickstart
|
|
5
|
+
``` ruby
|
|
6
|
+
require 'irc'
|
|
7
|
+
|
|
8
|
+
host 'localhost'
|
|
9
|
+
nick 'MyBot'
|
|
10
|
+
channel '#MyChannel', '#OtherChannel'
|
|
11
|
+
|
|
12
|
+
# Say hi when somebody joins the channel:
|
|
13
|
+
on :join do
|
|
14
|
+
say "Hi #{nick}!"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Tell the time and date:
|
|
18
|
+
match /^!(?:time|now)/ do
|
|
19
|
+
reply Time.now.strftime("it is %l:%M %P on %A, %B %-d, %Y.").gsub(/[ ]+/, ' ' )
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
start!
|
|
23
|
+
```
|
|
24
|
+
Then, `ruby newbot.rb`
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Subclass
|
|
28
|
+
|
|
29
|
+
You can also `require 'irc/base'` and subclass `IRC::Bot`:
|
|
30
|
+
``` ruby
|
|
31
|
+
require 'irc/base'
|
|
32
|
+
|
|
33
|
+
class MyBot < IRC::Bot
|
|
34
|
+
host 'localhost'
|
|
35
|
+
nick 'MyBot'
|
|
36
|
+
channel '#MyChannel'
|
|
37
|
+
|
|
38
|
+
start!
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
You can re-load the bot to update its callbacks without disconnecting:
|
|
43
|
+
``` ruby
|
|
44
|
+
# mention_match requires messages to start with the name of the bot
|
|
45
|
+
mention_match /reload!/ do
|
|
46
|
+
self.class.reset!
|
|
47
|
+
|
|
48
|
+
load __FILE__
|
|
49
|
+
end
|
|
50
|
+
```
|
data/lib/irc.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'irc/base'
|
|
2
|
+
|
|
3
|
+
class NewBot < IRC::Bot
|
|
4
|
+
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module IRC
|
|
8
|
+
module Delegator
|
|
9
|
+
NewBot.public_methods.each do |method_name|
|
|
10
|
+
define_method method_name do |*args, &block|
|
|
11
|
+
NewBot.send method_name, *args, &block
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
extend IRC::Delegator
|
data/lib/irc/base.rb
ADDED
data/lib/irc/bot.rb
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
module IRC
|
|
2
|
+
class Bot
|
|
3
|
+
attr_reader :message
|
|
4
|
+
|
|
5
|
+
def initialize message
|
|
6
|
+
@message = message
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def stop
|
|
10
|
+
Thread.current.kill
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def say something, recipients = nil
|
|
14
|
+
message.connection.privmsg something, recipients || self.class.channels
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def reply something, mention = true
|
|
18
|
+
if message.middle == self.class.nick
|
|
19
|
+
say something, message.nick
|
|
20
|
+
elsif mention
|
|
21
|
+
say "#{message.nick}, #{something}", message.middle
|
|
22
|
+
else
|
|
23
|
+
say something, message.middle
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def store namespace = nil
|
|
28
|
+
@store ||= IRC::Store.store
|
|
29
|
+
|
|
30
|
+
if namespace
|
|
31
|
+
(@namespace ||= {})[namespace] ||= Redis::Namespace.new(namespace, redis: @store)
|
|
32
|
+
else
|
|
33
|
+
@store
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def conjugate noun, lookup = nil
|
|
38
|
+
lookup ||= {
|
|
39
|
+
'me' => nick,
|
|
40
|
+
'you' => self.class.nick,
|
|
41
|
+
nick => 'you'
|
|
42
|
+
}
|
|
43
|
+
noun.gsub /[\w]+/i, lookup
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def method_missing method_name, *args
|
|
47
|
+
message.public_send method_name, *args
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class << self
|
|
51
|
+
attr_accessor :nick, :host, :port, :channels
|
|
52
|
+
|
|
53
|
+
def start!
|
|
54
|
+
@connection ||= Connection.new @host, @port
|
|
55
|
+
|
|
56
|
+
on(:ping) { connection.pong params }
|
|
57
|
+
|
|
58
|
+
@connection.nick @nick
|
|
59
|
+
@connection.join @channels
|
|
60
|
+
|
|
61
|
+
@connection.listen
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def reset!
|
|
65
|
+
Callback.reset!
|
|
66
|
+
@channels = []
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def host _host = nil
|
|
70
|
+
puts " host: '#{_host}'"
|
|
71
|
+
@host = _host || @host
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def port _port = nil
|
|
75
|
+
puts " port: '#{_port}'"
|
|
76
|
+
@port = _port || @port
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def nick _nick = nil
|
|
80
|
+
puts " nick: '#{_nick}'" if _nick
|
|
81
|
+
@nick = _nick || @nick
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def store _store, options = nil
|
|
85
|
+
require 'irc/store'
|
|
86
|
+
|
|
87
|
+
case _store
|
|
88
|
+
when :redis
|
|
89
|
+
IRC::Store.options = options
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def channel *_channels
|
|
94
|
+
_channels.each do |c|
|
|
95
|
+
puts "channel: '#{c}'"
|
|
96
|
+
(@channels ||= []) << c
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def match regex, &block
|
|
101
|
+
on(:privmsg, regex, &block)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def mention_match regex, &block
|
|
105
|
+
r = /^#{@nick}[,: ]*/ + regex
|
|
106
|
+
on(:privmsg, r, &block)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def on action, regex = nil, &block
|
|
110
|
+
Callback.add(action, regex, self, &block)
|
|
111
|
+
end
|
|
112
|
+
end # class << Bot
|
|
113
|
+
end # class Bot
|
|
114
|
+
end # module IRC
|
data/lib/irc/callback.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module IRC
|
|
2
|
+
class Callback
|
|
3
|
+
attr_accessor :action, :regex
|
|
4
|
+
|
|
5
|
+
def initialize action, regex = nil, factory = nil, &block
|
|
6
|
+
@action = action
|
|
7
|
+
@regex = regex || //
|
|
8
|
+
@block = block
|
|
9
|
+
@factory = factory
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call! message
|
|
13
|
+
if match = @regex.match(message.content)
|
|
14
|
+
(class << message; self; end).send :attr_accessor, *@regex.names
|
|
15
|
+
@regex.names.each do |name|
|
|
16
|
+
message.instance_variable_set("@#{name}", match[name])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Thread.new do
|
|
20
|
+
# begin
|
|
21
|
+
if @factory
|
|
22
|
+
@factory.new(message).instance_eval(&@block)
|
|
23
|
+
else
|
|
24
|
+
puts "calling #{@block}"
|
|
25
|
+
@block.call(message, match)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
attr_accessor :callbacks
|
|
33
|
+
|
|
34
|
+
def handle message
|
|
35
|
+
callbacks[message.action].each do |callback|
|
|
36
|
+
callback.call! message
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def add action, *args, &block
|
|
41
|
+
callback = new(action, *args, &block)
|
|
42
|
+
|
|
43
|
+
callbacks[action] << callback
|
|
44
|
+
callbacks[:all] << callback
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def callbacks
|
|
48
|
+
@callbacks ||= Hash.new {|hash,key| hash[key] = []}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def reset!
|
|
52
|
+
@callbacks = nil
|
|
53
|
+
callbacks
|
|
54
|
+
end
|
|
55
|
+
end # class << Callback
|
|
56
|
+
end # class Callback
|
|
57
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'thread'
|
|
3
|
+
|
|
4
|
+
module IRC
|
|
5
|
+
class Connection
|
|
6
|
+
def initialize host = nil, port = 6667
|
|
7
|
+
@host = host
|
|
8
|
+
@port = port
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def mutex
|
|
12
|
+
@mutex ||= Mutex.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def disconnected?
|
|
16
|
+
!@socket || @socket.closed?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def connected?
|
|
20
|
+
!disconnected?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def write *strings
|
|
24
|
+
# mutex do
|
|
25
|
+
strings.each do |string|
|
|
26
|
+
puts '-> ' + string
|
|
27
|
+
socket.print string + "\r\n"
|
|
28
|
+
end
|
|
29
|
+
# end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def join *channels
|
|
33
|
+
channels.flatten.each do |channel|
|
|
34
|
+
@channel = channel
|
|
35
|
+
write "JOIN #{channel}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def part *channels
|
|
40
|
+
channels.flatten.each do |channel|
|
|
41
|
+
write "PART #{channel}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def nick _nick, realname = nil
|
|
46
|
+
realname ||= _nick
|
|
47
|
+
write "NICK #{_nick}"
|
|
48
|
+
write "USER #{_nick} localhost #{@host} :#{realname}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def privmsg content, *recipients
|
|
52
|
+
recipients = @channel unless recipients.any?
|
|
53
|
+
recipients = [recipients].flatten.join(',')
|
|
54
|
+
|
|
55
|
+
msg = "PRIVMSG #{recipients} :#{content}"
|
|
56
|
+
write msg.slice!(0,500)
|
|
57
|
+
privmsg msg, recipients if msg != ''
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def quit message = nil
|
|
61
|
+
write "QUIT :#{message}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def pong value
|
|
65
|
+
write "PONG #{value}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def disconnect
|
|
69
|
+
quit
|
|
70
|
+
socket.close
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def socket
|
|
74
|
+
@socket ||= TCPSocket.open(@host, @port)
|
|
75
|
+
end
|
|
76
|
+
Thread.abort_on_exception = true
|
|
77
|
+
|
|
78
|
+
def listen
|
|
79
|
+
unless @listening
|
|
80
|
+
loop do
|
|
81
|
+
@listening = true
|
|
82
|
+
socket.lines "\r\n" do |line|
|
|
83
|
+
message = Message.new(line, self)
|
|
84
|
+
Callback.handle message
|
|
85
|
+
end
|
|
86
|
+
@listening = false
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end # class Connection
|
|
91
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class Array
|
|
2
|
+
def to_sentence
|
|
3
|
+
case length
|
|
4
|
+
when 0
|
|
5
|
+
''
|
|
6
|
+
when 1
|
|
7
|
+
self[0].to_s
|
|
8
|
+
when 2
|
|
9
|
+
"#{self[0]} and #{self[1]}"
|
|
10
|
+
else
|
|
11
|
+
"#{self[0..-2].join(', ')}, and #{self.last}"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class String
|
|
17
|
+
def pluralize number = 2
|
|
18
|
+
case number
|
|
19
|
+
when 0
|
|
20
|
+
"no #{self}s"
|
|
21
|
+
when 1
|
|
22
|
+
"1 #{self}"
|
|
23
|
+
else
|
|
24
|
+
"#{number} #{self}s"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class Regexp
|
|
30
|
+
def +(r)
|
|
31
|
+
Regexp.new(source + r.source)
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/irc/message.rb
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module IRC
|
|
2
|
+
class Message
|
|
3
|
+
REGEX = /^
|
|
4
|
+
(:
|
|
5
|
+
(?<prefix>
|
|
6
|
+
(
|
|
7
|
+
(?<servername>(?<nick>[a-z][a-z0-9_\-\[\]\\`\^\{\}\.\|]*))
|
|
8
|
+
)
|
|
9
|
+
(!(?<user>[a-z0-9~\.]+))?
|
|
10
|
+
(@(?<host>[a-z0-9\.]+))?
|
|
11
|
+
)
|
|
12
|
+
[\ ]+
|
|
13
|
+
)?
|
|
14
|
+
(?<command>[a-z]+|[0-9]{3})
|
|
15
|
+
(?<params>
|
|
16
|
+
(\ +(?<middle>[^:\r\n\ ][^\r\n\ ]*))*
|
|
17
|
+
(\ *:(?<trailing>.+))?
|
|
18
|
+
)
|
|
19
|
+
\r\n$
|
|
20
|
+
/xi
|
|
21
|
+
|
|
22
|
+
attr_reader :raw
|
|
23
|
+
attr_accessor :connection
|
|
24
|
+
attr_accessor *REGEX.names
|
|
25
|
+
|
|
26
|
+
def initialize message_string, connection
|
|
27
|
+
@raw = message_string
|
|
28
|
+
|
|
29
|
+
@connection = connection
|
|
30
|
+
@match = REGEX.match(message_string) || {}
|
|
31
|
+
|
|
32
|
+
REGEX.names.each do |name|
|
|
33
|
+
instance_variable_set("@#{name}", @match[name])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def action
|
|
39
|
+
@action ||= (command || '').downcase.intern
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def content
|
|
43
|
+
@content ||= trailing || middle
|
|
44
|
+
end
|
|
45
|
+
end # class Message
|
|
46
|
+
end
|
data/lib/irc/store.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'redis'
|
|
2
|
+
require 'redis-namespace'
|
|
3
|
+
|
|
4
|
+
module IRC
|
|
5
|
+
class Store
|
|
6
|
+
attr_reader :store
|
|
7
|
+
|
|
8
|
+
def initialize options = {}
|
|
9
|
+
options = {:host => 'localhost', :port => 6379}.merge(options)
|
|
10
|
+
@store = Redis.new options
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def method_missing method_name, *args, &block
|
|
14
|
+
store.send method_name, *args, &block
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
attr_accessor :options
|
|
19
|
+
def store
|
|
20
|
+
self.new options
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: irc
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Jeff Peterson
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-10-02 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
14
|
+
description: a simple ruby irc (bot framework)
|
|
15
|
+
email: jeff@petersonj.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- README.markdown
|
|
21
|
+
- lib/irc.rb
|
|
22
|
+
- lib/irc/base.rb
|
|
23
|
+
- lib/irc/bot.rb
|
|
24
|
+
- lib/irc/callback.rb
|
|
25
|
+
- lib/irc/connection.rb
|
|
26
|
+
- lib/irc/extensions.rb
|
|
27
|
+
- lib/irc/message.rb
|
|
28
|
+
- lib/irc/store.rb
|
|
29
|
+
homepage: http://github.com/jeffpeterson/irc
|
|
30
|
+
licenses: []
|
|
31
|
+
post_install_message:
|
|
32
|
+
rdoc_options: []
|
|
33
|
+
require_paths:
|
|
34
|
+
- lib
|
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
36
|
+
none: false
|
|
37
|
+
requirements:
|
|
38
|
+
- - ! '>='
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
|
+
none: false
|
|
43
|
+
requirements:
|
|
44
|
+
- - ! '>='
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
requirements: []
|
|
48
|
+
rubyforge_project:
|
|
49
|
+
rubygems_version: 1.8.24
|
|
50
|
+
signing_key:
|
|
51
|
+
specification_version: 3
|
|
52
|
+
summary: a simple ruby irc (bot) framework
|
|
53
|
+
test_files: []
|