git @ Cat's Eye Technologies mzstorkipiwanbotbotbot / 875098d
Initial import of files for Mzstorkipiwanbotbotbot. Chris Pressey 11 years ago
4 changed file(s) with 312 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 syntax: regexp
1
2 ^config.lua$
0 #!/bin/sh
1 ncat -c "lua mzstorkipiwanbotbotbot.lua" irc.freenode.net 6667
0 -- Sample config file for Mzstorkipiwanbotbotbot.
1 -- Fill this out with sensible values and rename it to "config.lua".
2 -- This file is just Lua source, but it's recommended to not have code in
3 -- here -- just stick to data to keep things simple.
4
5 -- The name of the bot. Unfortunately "Mzstorkipiwanbotbotbot" is
6 -- too long for most IRC servers to handle, so choose something shorter.
7 BOTNAME = 'funkybot'
8
9 -- If there is a NickServ on the server, and it asks you to authenticate,
10 -- you can specify your password here. If not, just leave this as nil.
11 PASSWORD = nil
12
13 -- The channels you want the bot to join. You can have it join as many
14 -- as you want.
15 CHANNELS = {'#aaaaa', '#bbbbb'}
0 #!/usr/bin/env lua
1
2 -- An IRC bot with no plan or purpose, written in Lua.
3 -- This work is in the public domain.
4
5 require "config"
6
7 --[[ GLOBALS ]]--
8
9 local MSG_PATTERN = "^" .. BOTNAME .. "%s*[:,!]%s*(.-)%s*$"
10 local CMD_PATTERN = "^%|%s*(.-)%s*$"
11
12 math.randomseed(os.time())
13
14 -- variable scopes
15
16 local server -- for server scope
17 local room -- for channel scope
18 local user -- for nick scope
19
20 -- syntax error messages
21
22 local snark = {
23 "?SYNTAX ERROR",
24 "omg u errored teh syntax!!1!",
25 "What is this I don't even",
26 "wat.",
27 "I disagree!",
28 "That's wonderful for you!",
29 }
30
31 --[[ FUNCTIONS ]]--
32
33 local p = function(s)
34 io.stderr:write(">>> " .. s .. "\n")
35 io.stdout:write(s)
36 io.stdout:write("\n")
37 io.stdout:flush()
38 end
39
40 local eval
41 eval = function(expr, nick, channel, context, reply)
42
43 context.iter = context.iter + 1
44 if context.iter > 10000 then
45 reply('Out of stack space! Well no, but I stopped it anyway.')
46 return nil
47 end
48
49 -- define the user's space
50 local userspace = user[nick]
51 if userspace == nil then
52 userspace = {}
53 user[nick] = userspace
54 end
55
56 -- FIRST, we find and reduce any expressions.
57 expr = string.gsub(expr, "(%b[])", function (subexpr)
58 subexpr = string.sub(subexpr, 2, -2)
59 local value = eval(subexpr, nick, channel, context, reply)
60 if value == nil then
61 return ''
62 else
63 return value
64 end
65 end)
66
67 -- Assignment to server variable
68 local match, _, name, value = string.find(expr, "^%/(%a-)%s*%=%s*(.-)$")
69 if match then
70 server[name] = value
71 return value
72 end
73
74 -- Assignment to channel variable
75 local match, _, name, value = string.find(expr, "^%#(%a-)%s*%=%s*(.-)$")
76 if match then
77 if room[channel] == nil then room[channel] = {} end
78 room[channel][name] = value
79 return value
80 end
81
82 -- Assignment to user variable
83 local match, _, name, value = string.find(expr, "^%~%/(%a-)%s*%=%s*(.-)$")
84 if match then
85 userspace[name] = value
86 return value
87 end
88
89 -- Print
90 local match, _, value = string.find(expr, "^print%s*(.-)%s*$")
91 if match then
92 reply(value)
93 return nil
94 end
95
96 -- Goto
97 local match, _, newexpr = string.find(expr, "^goto%s*(.-)%s*$")
98 if match then
99 return eval(newexpr, nick, channel, context, reply)
100 end
101
102 -- Reading a server variable's value
103 local match, _, name = string.find(expr, '^%/(%a-)$')
104 if match then
105 return server[name] or ''
106 end
107
108 -- Reading a channel variable's value
109 local match, _, name = string.find(expr, '^%#(%a-)$')
110 if match then
111 local roomspace = room[channel]
112 if roomspace == nil then return '' end
113 return roomspace[name] or ''
114 end
115
116 -- Reading this user variable's value
117 local match, _, name = string.find(expr, '^%~%/(%a-)$')
118 if match then
119 return userspace[name] or ''
120 end
121
122 -- Reading any user variable's value
123 local match, _, fromnick, name = string.find(expr, '^%~([%w_]-)%/(%a-)$')
124 if match then
125 local userspace = user[fromnick]
126 if userspace == nil then return '' end
127 return userspace[name] or ''
128 end
129
130 -- Head and tail
131 local match, _, fst = string.find(expr, '^hd%s*(.).*$')
132 if match then
133 return fst
134 end
135 local match, _, rst = string.find(expr, '^tl%s*.(.*)$')
136 if match then
137 return rst
138 end
139
140 -- tell
141 local match, _, to_nick, msg = string.find(expr, "^tell%s+(.-)%s+(.-)$")
142 if match then
143 message = {from=nick, msg=msg}
144 if user[to_nick] == nil then user[to_nick] = {} end
145 if type(user[to_nick].msgs) ~= table then
146 user[to_nick].msgs = {}
147 end
148 table.insert(user[to_nick].msgs, message)
149 reply("Consider it noted.")
150 return
151 end
152
153 -- source code
154 local match = string.find(expr, "^source%s*$")
155 if match then
156 reply("http://bitbucket.org/catseye/mzstorkipiwanbotbotbot/src/tip/mzstorkipiwanbotbotbot.lua")
157 return
158 end
159
160 -- save state
161 local match = string.find(expr, "^save%s*$")
162 if match then
163 -- TODO save state here
164 -- reply("State saved.")
165 return
166 end
167
168 -- Help
169 local match, _, topic = string.find(expr, "^help%s*(.-)$")
170 if match then
171 if string.find(topic, '^ass') then
172 reply("Assign a nick-scope variable with ~/foo=1. Assign a server-scope variable with /bar=1. Assign a channel-scope variable with #baz=1.")
173 return
174 elseif string.find(topic, '^exp') then
175 reply("All items in [brackets] are replaced by their value, in a recursive, depth-first manner.")
176 return
177 elseif string.find(topic, '^pr') then
178 reply("To print a string, issue the command 'print string'.")
179 return
180 elseif string.find(topic, '^go') then
181 reply("To evaluate a string as a command, issue 'goto command'. This discards control context.")
182 return
183 elseif string.find(topic, '^tell') then
184 reply("To enqueue a message for another user, which they will see publicly when they next speak up, issue the comand 'tell nick <stuff>'.")
185 return
186 elseif string.find(topic, '^sou') then
187 reply("To get a link to the source code for this bot, issue the command 'source'.")
188 return
189 elseif string.find(topic, '^err') then
190 reply("To get more interesting error messages, set ~/errmsgs=snark.")
191 return
192 else
193 reply("Help is available for: assignment expressions print goto tell source errors")
194 return
195 end
196 end
197
198 -- Syntax error
199 if userspace.errmsgs == 'snark' then
200 reply(snark[math.random(#snark)])
201 else
202 reply("Unknown command. Type '|help' for help.")
203 end
204 end
205
206 --[[ MAIN ]]--
207
208 local done = False
209 if state == nil then state = 0 end
210
211 server = {}
212 room = {}
213 user = {}
214 user[BOTNAME] = {
215 BRA="[",
216 KET="]"
217 }
218
219 while not done do
220 local line = io.stdin:read("*line")
221 line = string.gsub(line, "[\r\n]+$", "") -- chomp
222 io.stderr:write("--- " .. line .. "\n")
223 if state == 0 then
224 if string.find(line, "No Ident response") then
225 p(string.format("USER %s %s %s %s", BOTNAME, BOTNAME, BOTNAME, BOTNAME))
226 p(string.format("NICK %s", BOTNAME))
227 if PASSWORD ~= nil then
228 p(string.format("PRIVMSG NickServ :identify %s", PASSWORD))
229 end
230 for i,channel in ipairs(CHANNELS) do
231 p(string.format("JOIN %s", channel))
232 end
233 state = 1
234 end
235 else -- state == 1
236 local match, _, nick, channel, chatline = string.find(line, '^%:(.-)%!.-%s+PRIVMSG%s*(.-)%s*%:(.-)$')
237 if match and string.find(channel, '^\#') then
238 -- someone said something in the channel we're in.
239 local reply = function(s)
240 if s ~= nil then
241 if type(s) == "table" then s = "<table>" end -- TODO: table.tostring(s)
242 p(string.format("PRIVMSG %s :%s: %s", channel, nick, s))
243 end
244 end
245
246 -- was it someone I have a message for?
247 if user[nick] ~= nil and type(user[nick].msgs) == "table" then
248 local c = 0
249 for key,message in pairs(user[nick].msgs) do
250 c = c + 1
251 if c > 5 then
252 reply("And there's more. I'll tell you later.")
253 break
254 else
255 reply(string.format("%s told me to tell you: %s", message.from, message.msg))
256 user[nick].msgs[key] = nil
257 end
258 end
259 end
260
261 -- was it addressed to me?
262 match, _, msg = string.find(chatline, MSG_PATTERN)
263 if not match then
264 match, _, msg = string.find(chatline, CMD_PATTERN)
265 end
266 if match then
267 local context = { iter=0 }
268 reply(eval(msg, nick, channel, context, reply))
269 end
270 end
271 local match, _, nick, botname, msg = string.find(line, '^%:(.-)%!.-%s+PRIVMSG%s+(.-)%s*%:%s*(.-)%s*$')
272 if match then
273 if botname == BOTNAME and nick ~= BOTNAME then
274 local context = { iter=0 }
275 local reply = function(s)
276 if s ~= nil then
277 p(string.format("PRIVMSG %s :%s", nick, s))
278 end
279 end
280 reply(eval(msg, nick, nil, context, reply))
281 end
282 end
283 local match, _, serv = string.find(line, "^PING%s+%:(.-)$")
284 if match then
285 p(string.format("PONG :%s", serv))
286 -- TODO save state here
287 end
288 end
289 end
290