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.
- checksums.yaml +7 -0
- data/bin/run-irc.rb +24 -0
- data/lib/BeerBot.rb +20 -0
- data/lib/BeerBot/00.utils/DataFile.rb +56 -0
- data/lib/BeerBot/00.utils/InOut.rb +46 -0
- data/lib/BeerBot/00.utils/More.rb +68 -0
- data/lib/BeerBot/00.utils/paramExpand.rb +77 -0
- data/lib/BeerBot/00.utils/utils.rb +149 -0
- data/lib/BeerBot/00.utils/world/IRCWorld.rb +47 -0
- data/lib/BeerBot/00.utils/world/World.rb +84 -0
- data/lib/BeerBot/01.connect/Connection.rb +23 -0
- data/lib/BeerBot/01.connect/IRCConnection.rb +133 -0
- data/lib/BeerBot/01.protocols/botmsg.rb +59 -0
- data/lib/BeerBot/01.protocols/irc.rb +250 -0
- data/lib/BeerBot/02.bot/bot.rb +234 -0
- data/lib/BeerBot/03.more/BotMsgMore.rb +34 -0
- data/lib/BeerBot/06.dispatchers/irc.rb +160 -0
- data/lib/BeerBot/70.scheduler/scheduler.rb +22 -0
- data/lib/BeerBot/Config.rb +62 -0
- data/lib/RunIRC.rb +153 -0
- metadata +134 -0
@@ -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
|