beerbot 0.1.0

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.
@@ -0,0 +1,47 @@
1
+ # The files in this directory are part of BeerBot, a a ruby irc bot library.
2
+ # Copyright (C) 2014 Daniel Bush
3
+ # This program is distributed under the terms of the GNU
4
+ # General Public License. A copy of the license should be
5
+ # enclosed with this project in the file LICENSE. If not
6
+ # see <http://www.gnu.org/licenses/>.
7
+
8
+ require_relative 'World'
9
+
10
+ module BeerBot
11
+
12
+ module Utils
13
+
14
+ # The chief specialisation of IRCWorld is to handle user names which
15
+ # are sometimes prepended with characters like '@'.
16
+ #
17
+ # TODO: are there any more?
18
+
19
+ class IRCWorld < World
20
+ def remove_op nick
21
+ nick.sub(/^@/,'')
22
+ end
23
+
24
+ def user nick
25
+ nick = self.remove_op(nick)
26
+ super
27
+ end
28
+
29
+ def nick oldnick,nick
30
+ oldnick = self.remove_op(oldnick)
31
+ nick = self.remove_op(nick)
32
+ super
33
+ end
34
+
35
+ def part nick,channel
36
+ nick = self.remove_op(nick)
37
+ super
38
+ end
39
+
40
+ def join nick,channel
41
+ nick = self.remove_op(nick)
42
+ super
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,84 @@
1
+ # The files in this directory are part of BeerBot, a a ruby irc bot library.
2
+ # Copyright (C) 2014 Daniel Bush
3
+ # This program is distributed under the terms of the GNU
4
+ # General Public License. A copy of the license should be
5
+ # enclosed with this project in the file LICENSE. If not
6
+ # see <http://www.gnu.org/licenses/>.
7
+
8
+ module BeerBot
9
+
10
+ module Utils
11
+
12
+ # There should be one world per connection instance tracking
13
+ # the channels joined and users on those channels.
14
+
15
+ class World < Hash
16
+
17
+ def initialize nick
18
+ self[:nick] = nick
19
+
20
+ # An index/lookup for channels we know about.
21
+ self[:channels] = Hash.new {|h,k| h[k] = {users:Set.new}}
22
+
23
+ # An index/lookup for users we know about.
24
+ self[:users] = Hash.new {|h,k| h[k] = {channels:Set.new}}
25
+
26
+ end
27
+
28
+ # Fetch a user.
29
+ def user user
30
+ if self[:users].has_key?(user) then
31
+ self[:users][user]
32
+ else
33
+ end
34
+ end
35
+
36
+ # Fetch a channel.
37
+ def channel channel
38
+ if self[:channels].has_key?(channel) then
39
+ self[:channels][channel]
40
+ else
41
+ end
42
+ end
43
+
44
+ # Someone joins channel.
45
+ def join nick,channel
46
+ self[:channels][channel][:users].add(nick)
47
+ self[:users][nick][:channels].add(channel)
48
+ self
49
+ end
50
+
51
+ # Someone leaves channel.
52
+ def part nick,channel
53
+ self[:channels][channel][:users].delete(nick)
54
+ self[:users][nick][:channels].delete(channel)
55
+ self
56
+ end
57
+
58
+ # Someone changes nick.
59
+ def nick oldnick,nick
60
+ self[:channels].each_pair{|name,chan|
61
+ if chan.member?(oldnick) then
62
+ chan.delete(oldnick)
63
+ chan.add(nick)
64
+ end
65
+ }
66
+ self[:users][nick] = self[:users][oldnick]
67
+ self[:users].delete(oldnick)
68
+ # If it's us, update our nick:
69
+ if self[:nick] == oldnick then
70
+ self[:nick] = nick
71
+ end
72
+ self
73
+ end
74
+
75
+ # TODO: User quits altogether.
76
+ def quit user
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+
@@ -0,0 +1,23 @@
1
+ # The files in this directory are part of BeerBot, a a ruby irc bot library.
2
+ # Copyright (C) 2013,2014 Daniel Bush
3
+ # This program is distributed under the terms of the GNU
4
+ # General Public License. A copy of the license should be
5
+ # enclosed with this project in the file LICENSE. If not
6
+ # see <http://www.gnu.org/licenses/>.
7
+
8
+ module BeerBot
9
+ class Connection
10
+ attr_accessor :queue
11
+ def initialize
12
+ @queue = Queue.new
13
+ end
14
+ def open
15
+ end
16
+ def close
17
+ end
18
+ def write str
19
+ end
20
+ def ready? &block
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,133 @@
1
+ # The files in this directory are part of BeerBot, a a ruby irc bot library.
2
+ # Copyright (C) 2013,2014 Daniel Bush
3
+ # This program is distributed under the terms of the GNU
4
+ # General Public License. A copy of the license should be
5
+ # enclosed with this project in the file LICENSE. If not
6
+ # see <http://www.gnu.org/licenses/>.
7
+
8
+ require 'socket'
9
+ require 'set'
10
+ require_relative 'Connection'
11
+
12
+ module BeerBot
13
+
14
+ # An instance of IRCConnection represents a specific connection to
15
+ # an irc server (for a given user/bot).
16
+ #
17
+ # We also do some low-level bookkeeping, like returning pong and
18
+ # looking for welcome message.
19
+
20
+ class IRCConnection < Connection
21
+
22
+ # Queue containing received messages from the server.
23
+ attr_accessor :queue,:writeq,:readyq
24
+ attr_accessor :connection,:server,:port,:nick,:thread
25
+
26
+ def initialize server:nil,port:6667,nick:'beerbot'
27
+ @server = server
28
+ @port = port
29
+ @nick = nick
30
+ @queue = Queue.new # received messages
31
+ @writeq = Queue.new # messages to send out
32
+
33
+ # This queue is only used at start up when the connection
34
+ # to the irc server isn't ready yet:
35
+ @readyq = Queue.new
36
+ @ready = false
37
+ @ready_mutex = Mutex.new
38
+ @write_mutex = Mutex.new
39
+
40
+ end
41
+
42
+ # Flag the connection as ready.
43
+ #
44
+ # Any blocks passed to ready? will now be executed.
45
+
46
+ def ready!
47
+ @ready_mutex.synchronize {
48
+ @ready = true
49
+ while @readyq.size > 0
50
+ block = @readyq.deq
51
+ block.call
52
+ end
53
+ }
54
+ end
55
+
56
+ def ready? &block
57
+ return @ready unless block_given?
58
+ @ready_mutex.synchronize {
59
+ if @ready then
60
+ block.call
61
+ else
62
+ @readyq.enq(block)
63
+ end
64
+ }
65
+ end
66
+
67
+ # Open and maintain the connection with an irc server.
68
+ #
69
+ # If you pass in a connection object it will be used instead of
70
+ # opening a tcp socket.
71
+ # It should respond to whatever is called on @connection
72
+ # eg open,gets,write.
73
+ # Use for testing this class.
74
+
75
+ def open connection=nil
76
+ if connection then
77
+ @connection = connection
78
+ @connection.open(self.server, self.port)
79
+ else
80
+ @connection = TCPSocket.open(self.server, self.port)
81
+ end
82
+ self.write("USER #{@nick} #{@nick} #{@nick} :#{@nick}")
83
+ self.write("NICK #{@nick}")
84
+ @thread = Thread.new {
85
+ while not @connection.eof? do
86
+ str = @connection.gets()
87
+ puts "<< #{str}"
88
+ case str
89
+ when /^PING (.*)$/
90
+ self.write "PONG #{$1}"
91
+ when / 001 / # ready
92
+ self.ready!
93
+ else
94
+ self.queue.enq(str)
95
+ end
96
+ end
97
+ }
98
+ @write_thread = Thread.new {
99
+ loop do
100
+ thing = @writeq.deq
101
+ self.write thing
102
+ end
103
+ }
104
+
105
+ @thread
106
+ end
107
+
108
+ def close
109
+ end
110
+
111
+ # Write out to the socket.
112
+ #
113
+ # Chomps message and then adds "\r\n".
114
+
115
+ def write message
116
+ case message
117
+ when String
118
+ message = message.chomp + "\r\n"
119
+ puts ">> #{message}"
120
+ @write_mutex.synchronize {
121
+ @connection.print(message)
122
+ }
123
+ when Array
124
+ message.each{|m| self.write(m) }
125
+ when NilClass
126
+ else
127
+ puts "IRCConnection: can't process #{message}"
128
+ end
129
+ end
130
+
131
+ end
132
+
133
+ end
@@ -0,0 +1,59 @@
1
+ # The files in this directory are part of BeerBot, a a ruby irc bot library.
2
+ # Copyright (C) 2013,2014 Daniel Bush
3
+ # This program is distributed under the terms of the GNU
4
+ # General Public License. A copy of the license should be
5
+ # enclosed with this project in the file LICENSE. If not
6
+ # see <http://www.gnu.org/licenses/>.
7
+
8
+ module BeerBot
9
+
10
+ module Protocol
11
+
12
+ module BotMsg
13
+
14
+ # Convert botmsg to an array of one or more botmsg hashes.
15
+ #
16
+ # Proc's are executed to retrieve an array or hash.
17
+ #
18
+ # Array => Array
19
+ # Hash => [Hash]
20
+ # Proc => [Hash]
21
+
22
+ def self.to_a botmsg
23
+ case botmsg
24
+ when Hash
25
+ return [botmsg]
26
+ when Array
27
+ return botmsg
28
+ when Proc
29
+ return self.to_a(botmsg.call)
30
+ else
31
+ return []
32
+ end
33
+ end
34
+
35
+ # Convert botmsg to an action if it starts with '*'.
36
+
37
+ def self.actionify botmsg
38
+ case botmsg
39
+ when Hash
40
+ case botmsg[:msg]
41
+ when /^\*\s*/
42
+ botmsg[:action] = botmsg[:msg].sub(/^\*\s*/,'')
43
+ botmsg[:msg] = nil
44
+ end
45
+ botmsg
46
+ when Array
47
+ botmsg.map {|b|
48
+ self.actionify(b)
49
+ }
50
+ else
51
+ botmsg
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+
58
+ end
59
+ end
@@ -0,0 +1,250 @@
1
+ # The files in this directory are part of BeerBot, a a ruby irc bot library.
2
+ # Copyright (C) 2013,2014 Daniel Bush
3
+ # This program is distributed under the terms of the GNU
4
+ # General Public License. A copy of the license should be
5
+ # enclosed with this project in the file LICENSE. If not
6
+ # see <http://www.gnu.org/licenses/>.
7
+
8
+ module BeerBot
9
+
10
+ module Protocol
11
+
12
+ module IRC
13
+
14
+ # In coming IRC messages are parsed into hashes with several
15
+ # additional methods.
16
+
17
+ class IRCMessage < Hash
18
+
19
+ # This regexp captures the basic pattern used for prefixed and
20
+ # unprefixed irc commands sent by the server to a client.
21
+ #
22
+ # See: http://tools.ietf.org/html/rfc1459.html sec 2.3.1
23
+
24
+ CMD = Regexp.new(
25
+ # nick!~user@host:
26
+ '^(:(?<prefix>[^:\s]+(@\S+)?)\s+)?'+
27
+ # eg 'PRIVMSG' , 3-digit code:
28
+ '(?<command>[^:\s]+)'+
29
+ # Bit after the command (one or more words):
30
+ '(\s+(?<params>[^:\s]+(\s+[^:\s]+)*))?\s*'+
31
+ # Bit after second ':' the msg in PRIVMSG
32
+ '\s:\s*(?<trailing>.*)$'
33
+ )
34
+
35
+ def initialize raw
36
+
37
+ @valid = false
38
+ @has_prefix = false
39
+ @user_prefix = false # message came from nick
40
+ self[:prefix] = {}
41
+ self[:command] = :unknown
42
+ self[:raw] = raw
43
+ self[:params] = []
44
+ self[:trailing] = nil
45
+
46
+ if m = CMD.match(raw) then
47
+ @valid = true
48
+ if m[:prefix] then
49
+ @has_prefix = true
50
+ nick,host = m[:prefix].split('!')
51
+ if host then
52
+ @user_prefix = true
53
+ user,host = host.split('@')
54
+ self[:prefix][:nick] = nick
55
+ self[:prefix][:user] = user
56
+ self[:prefix][:host] = host
57
+ else
58
+ # It aint a user prefix, so just bung it in host for the
59
+ # moment.
60
+ self[:prefix][:host] = nick
61
+ end
62
+ end
63
+
64
+ self[:command] = m[:command].strip
65
+ params = if m[:params] then m[:params].strip else "" end
66
+ self[:params] = params.split(/\s+/)
67
+ self[:trailing]= m[:trailing].strip
68
+ end
69
+
70
+ end
71
+
72
+ # We couldn't parse the command if not valid.
73
+
74
+ def valid?
75
+ @valid
76
+ end
77
+
78
+ # Is a prefixed irc string sent by server.
79
+
80
+ def prefix?
81
+ @has_prefix
82
+ end
83
+
84
+ # Is prefixed and the prefix is nick!~user@host .
85
+
86
+ def user_prefix?
87
+ @user_prefix
88
+ end
89
+
90
+ # Check that syms exist in the hash otherwise return the missing
91
+ # sym.
92
+
93
+ def check *syms
94
+ result = true
95
+ syms.each{|sym|
96
+ case sym
97
+ when :prefix,:nick,:user,:host
98
+ return :prefix unless self[:prefix]
99
+ case sym
100
+ when :prefix
101
+ else
102
+ return sym unless self[:prefix][sym]
103
+ end
104
+ else
105
+ return sym unless self[sym]
106
+ end
107
+ }
108
+ result
109
+ end
110
+
111
+ end
112
+
113
+ # Parse raw irc string and then yield for various events.
114
+ #
115
+ # The lambda receives an incoming irc string, tries to parse
116
+ # it and act on it.
117
+ # The lambda should return nil or a botmsg hash.
118
+ # Note that connection readiness and PONG protocol are handled by
119
+ # the irc connection, not here.
120
+
121
+ def self.parse str,&block
122
+
123
+ m = IRCMessage.new(str)
124
+ #puts "[parse] #{m}"
125
+ result = []
126
+
127
+ case m[:command]
128
+
129
+ when 'NICK' # change of nick
130
+ case s=m.check(:prefix,:nick,:trailing)
131
+ when Symbol
132
+ puts "* NICK expected #{s}"
133
+ return nil
134
+ end
135
+ old = m[:prefix][:nick]
136
+ nick = m[:trailing]
137
+ result = [:nick,old,nick]
138
+
139
+ when 'PART' # someone leaves channel
140
+ case s=m.check(:prefix,:nick,:params)
141
+ when Symbol
142
+ puts "* PART expected #{s}"
143
+ return nil
144
+ end
145
+ channel = m[:params][0]
146
+ nick = m[:prefix][:nick]
147
+ result = [:part,nick,channel]
148
+
149
+ when 'JOIN' # someone joins channel
150
+ case s=m.check(:prefix,:nick,:trailing)
151
+ when Symbol
152
+ puts "* JOIN expected #{s}"
153
+ return nil
154
+ end
155
+ channel = m[:trailing]
156
+ nick = m[:prefix][:nick]
157
+ result = [:join,nick,channel]
158
+
159
+ when '353' # channel user list when we join the channel
160
+ case s=m.check(:params,:trailing)
161
+ when Symbol
162
+ puts "* 353 expected #{s}"
163
+ return nil
164
+ end
165
+ channel = m[:params][2]
166
+ users = m[:trailing].split(/\s+/)
167
+ result = [:chanlist,channel,users]
168
+ #puts "[parse/353] #{result}"
169
+
170
+ when '366' # end of 353
171
+ result = [:chanlistend]
172
+
173
+ when 'PRIVMSG'
174
+ case s=m.check(:prefix,:nick,:params,:trailing)
175
+ when Symbol
176
+ #puts "* PRIVMSG expected #{s}"
177
+ return nil
178
+ end
179
+
180
+ msg = m[:trailing].strip
181
+ from = m[:prefix][:nick].strip
182
+ to = m[:params][0].strip unless m[:params].empty?
183
+ result = [:privmsg,from,to,msg]
184
+
185
+ else # command we don't handle
186
+ result = [:default,m]
187
+ end
188
+
189
+ if block_given? then
190
+ yield result
191
+ else
192
+ result
193
+ end
194
+ end
195
+
196
+ # Return irc-conformat string from a botmsg hash.
197
+ #
198
+ # Generates nil if it can't handle 'botmsg'.
199
+
200
+ def self.to_irc botmsg
201
+ case botmsg
202
+ when Hash
203
+ to = botmsg[:to]
204
+ return nil unless to
205
+ if botmsg[:action] then
206
+ self.action(to,botmsg[:action])
207
+ elsif botmsg[:msg] then
208
+ self.msg(to,botmsg[:msg])
209
+ else
210
+ nil
211
+ end
212
+ when Array
213
+ botmsg.map{|reply|
214
+ self.to_irc reply
215
+ }
216
+ when Proc
217
+ #p botmsg.call
218
+ self.to_irc(botmsg.call)
219
+ else
220
+ nil
221
+ end
222
+
223
+ end
224
+
225
+
226
+ # Send PRIVMSG to channel or nick.
227
+
228
+ def self.msg to,str
229
+ "PRIVMSG #{to} :#{str}"
230
+ end
231
+ class << self
232
+ alias_method :privmsg,:msg
233
+ end
234
+
235
+ # Send a /me-style action to channel or nick.
236
+
237
+ def self.action to,str
238
+ "PRIVMSG #{to} :\u0001#{'ACTION'} #{str}\u0001"
239
+ end
240
+
241
+ # Join a channel
242
+
243
+ def self.join chan
244
+ "JOIN #{chan}"
245
+ end
246
+
247
+ end
248
+ end
249
+
250
+ end