├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.markdown ├── asyncoperations.lua ├── doc └── irc.luadoc ├── handlers.lua ├── init.lua ├── push-luadoc.sh ├── set.lua └── util.lua /.gitignore: -------------------------------------------------------------------------------- 1 | /gh-pages/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: lua 2 | 3 | notifications: 4 | email: false 5 | 6 | env: 7 | global: 8 | - secure: "kFhU+DZjhq/KbDt0DIDWnlskXMa12miNelmhhy30fQGgVIdiibDGKMNGyLahWp8CnPu1DARb5AZWK2TDfARdnURT2pgcsG83M7bYIY6cR647BWjL7oAhJ6CYEzTWJTBjeUjpN/o4vIgfXSDR0c7vboDi7Xz8ilfrBujPL2Oi/og=" 9 | 10 | install: 11 | - sudo apt-get -y update 12 | - sudo apt-get -y install lua5.1 luadoc 13 | 14 | script: 15 | - ./push-luadoc.sh 16 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | --[[ 2 | Lua IRC library 3 | 4 | Copyright (c) 2010 Jakob Ovrum 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE.]] 26 | 27 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/JakobOvrum/LuaIRC.svg?branch=master)](https://travis-ci.org/JakobOvrum/LuaIRC) 2 | LuaIRC 3 | ============ 4 | 5 | IRC client library for Lua. 6 | 7 | Dependencies 8 | ------------- 9 | 10 | * [LuaSocket](http://w3.impa.br/~diego/software/luasocket/) 11 | 12 | **Only required if you want to make use of the TLS support** 13 | 14 | * [LuaSec](http://www.inf.puc-rio.br/~brunoos/luasec/) 15 | 16 | Documentation 17 | ------------- 18 | Documentation can be automatically generated by passing irc.luadoc (in doc/) to [LuaDoc](http://luadoc.luaforge.net/), or pre-generated documentation can be found in the 'gh-pages' branch, which can also be browsed [online](http://jakobovrum.github.com/LuaIRC/doc/modules/irc.html). 19 | 20 | -------------------------------------------------------------------------------- /asyncoperations.lua: -------------------------------------------------------------------------------- 1 | local table = table 2 | local assert = assert 3 | local error = error 4 | local select = select 5 | local pairs = pairs 6 | 7 | module "irc" 8 | 9 | local meta = _META 10 | 11 | function meta:send(msg, ...) 12 | if select("#", ...) > 0 then 13 | msg = msg:format(...) 14 | end 15 | self:invoke("OnSend", msg) 16 | 17 | local bytes, err = self.socket:send(msg .. "\r\n") 18 | 19 | if not bytes and err ~= "timeout" and err ~= "wantwrite" then 20 | self:invoke("OnDisconnect", err, true) 21 | self:shutdown() 22 | error(err, errlevel) 23 | end 24 | end 25 | 26 | local function verify(str, errLevel) 27 | if str:find("^:") or str:find("%s%z") then 28 | error(("malformed parameter '%s' to irc command"):format(str), errLevel) 29 | end 30 | 31 | return str 32 | end 33 | 34 | function meta:sendChat(target, msg) 35 | -- Split the message into segments if it includes newlines. 36 | for line in msg:gmatch("([^\r\n]+)") do 37 | self:send("PRIVMSG %s :%s", verify(target, 3), line) 38 | end 39 | end 40 | 41 | function meta:sendNotice(target, msg) 42 | -- Split the message into segments if it includes newlines. 43 | for line in msg:gmatch("([^\r\n]+)") do 44 | self:send("NOTICE %s :%s", verify(target, 3), line) 45 | end 46 | end 47 | 48 | function meta:join(channel, key) 49 | if key then 50 | self:send("JOIN %s :%s", verify(channel, 3), verify(key, 3)) 51 | else 52 | self:send("JOIN %s", verify(channel, 3)) 53 | end 54 | end 55 | 56 | function meta:part(channel) 57 | channel = verify(channel, 3) 58 | self:send("PART %s", channel) 59 | if self.track_users then 60 | self.channels[channel] = nil 61 | end 62 | end 63 | 64 | function meta:trackUsers(b) 65 | self.track_users = b 66 | if not b then 67 | for k,v in pairs(self.channels) do 68 | self.channels[k] = nil 69 | end 70 | end 71 | end 72 | 73 | function meta:setMode(t) 74 | local target = t.target or self.nick 75 | local mode = "" 76 | local add, rem = t.add, t.remove 77 | 78 | assert(add or rem, "table contains neither 'add' nor 'remove'") 79 | 80 | if add then 81 | mode = table.concat{"+", verify(add, 3)} 82 | end 83 | 84 | if rem then 85 | mode = table.concat{mode, "-", verify(rem, 3)} 86 | end 87 | 88 | self:send("MODE %s %s", verify(target, 3), mode) 89 | end 90 | -------------------------------------------------------------------------------- /doc/irc.luadoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobOvrum/LuaIRC/76a621dc546786ce73ef93d50ce437a1b3c8a5c8/doc/irc.luadoc -------------------------------------------------------------------------------- /handlers.lua: -------------------------------------------------------------------------------- 1 | local pairs = pairs 2 | local error = error 3 | local tonumber = tonumber 4 | local table = table 5 | 6 | module "irc" 7 | 8 | handlers = {} 9 | 10 | handlers["PING"] = function(o, prefix, query) 11 | o:send("PONG :%s", query) 12 | end 13 | 14 | handlers["001"] = function(o, prefix, me) 15 | o.authed = true 16 | o.nick = me 17 | end 18 | 19 | handlers["PRIVMSG"] = function(o, prefix, channel, message) 20 | o:invoke("OnChat", parsePrefix(prefix), channel, message) 21 | end 22 | 23 | handlers["NOTICE"] = function(o, prefix, channel, message) 24 | o:invoke("OnNotice", parsePrefix(prefix), channel, message) 25 | end 26 | 27 | handlers["JOIN"] = function(o, prefix, channel) 28 | local user = parsePrefix(prefix) 29 | if o.track_users then 30 | if user.nick == o.nick then 31 | o.channels[channel] = {users = {}} 32 | else 33 | o.channels[channel].users[user.nick] = user 34 | end 35 | end 36 | 37 | o:invoke("OnJoin", user, channel) 38 | end 39 | 40 | handlers["PART"] = function(o, prefix, channel, reason) 41 | local user = parsePrefix(prefix) 42 | if o.track_users then 43 | if user.nick == o.nick then 44 | o.channels[channel] = nil 45 | else 46 | o.channels[channel].users[user.nick] = nil 47 | end 48 | end 49 | o:invoke("OnPart", user, channel, reason) 50 | end 51 | 52 | handlers["QUIT"] = function(o, prefix, msg) 53 | local user = parsePrefix(prefix) 54 | if o.track_users then 55 | for channel, v in pairs(o.channels) do 56 | v.users[user.nick] = nil 57 | end 58 | end 59 | o:invoke("OnQuit", user, msg) 60 | end 61 | 62 | handlers["NICK"] = function(o, prefix, newnick) 63 | local user = parsePrefix(prefix) 64 | if o.track_users then 65 | for channel, v in pairs(o.channels) do 66 | local users = v.users 67 | local oldinfo = users[user.nick] 68 | if oldinfo then 69 | users[newnick] = oldinfo 70 | users[user.nick] = nil 71 | o:invoke("NickChange", user, newnick, channel) 72 | end 73 | end 74 | else 75 | o:invoke("NickChange", user, newnick) 76 | end 77 | if user.nick == o.nick then 78 | o.nick = newnick 79 | end 80 | end 81 | 82 | local function needNewNick(o, prefix, target, badnick) 83 | local newnick = o.nickGenerator(badnick) 84 | o:send("NICK %s", newnick) 85 | end 86 | 87 | -- ERR_ERRONEUSNICKNAME (Misspelt but remains for historical reasons) 88 | handlers["432"] = needNewNick 89 | 90 | -- ERR_NICKNAMEINUSE 91 | handlers["433"] = needNewNick 92 | 93 | --NAMES list 94 | handlers["353"] = function(o, prefix, me, chanType, channel, names) 95 | if o.track_users then 96 | o.channels[channel] = o.channels[channel] or {users = {}, type = chanType} 97 | 98 | local users = o.channels[channel].users 99 | for nick in names:gmatch("(%S+)") do 100 | local access, name = parseNick(nick) 101 | users[name] = {access = access} 102 | end 103 | end 104 | end 105 | 106 | --end of NAMES 107 | handlers["366"] = function(o, prefix, me, channel, msg) 108 | if o.track_users then 109 | o:invoke("NameList", channel, msg) 110 | end 111 | end 112 | 113 | --no topic 114 | handlers["331"] = function(o, prefix, me, channel) 115 | o:invoke("OnTopic", channel, nil) 116 | end 117 | 118 | --new topic 119 | handlers["TOPIC"] = function(o, prefix, channel, topic) 120 | o:invoke("OnTopic", channel, topic) 121 | end 122 | 123 | handlers["332"] = function(o, prefix, me, channel, topic) 124 | o:invoke("OnTopic", channel, topic) 125 | end 126 | 127 | --topic creation info 128 | handlers["333"] = function(o, prefix, me, channel, nick, time) 129 | o:invoke("OnTopicInfo", channel, nick, tonumber(time)) 130 | end 131 | 132 | handlers["KICK"] = function(o, prefix, channel, kicked, reason) 133 | o:invoke("OnKick", channel, kicked, parsePrefix(prefix), reason) 134 | end 135 | 136 | --RPL_UMODEIS 137 | --To answer a query about a client's own mode, RPL_UMODEIS is sent back 138 | handlers["221"] = function(o, prefix, user, modes) 139 | o:invoke("OnUserMode", modes) 140 | end 141 | 142 | --RPL_CHANNELMODEIS 143 | --The result from common irc servers differs from that defined by the rfc 144 | handlers["324"] = function(o, prefix, user, channel, modes) 145 | o:invoke("OnChannelMode", channel, modes) 146 | end 147 | 148 | handlers["MODE"] = function(o, prefix, target, modes, ...) 149 | if o.track_users and target ~= o.nick then 150 | local add = true 151 | local optList = {...} 152 | for c in modes:gmatch(".") do 153 | if c == "+" then add = true 154 | elseif c == "-" then add = false 155 | elseif c == "o" then 156 | local user = table.remove(optList, 1) 157 | o.channels[target].users[user].access.op = add 158 | elseif c == "h" then 159 | local user = table.remove(optList, 1) 160 | o.channels[target].users[user].access.halfop = add 161 | elseif c == "v" then 162 | local user = table.remove(optList, 1) 163 | o.channels[target].users[user].access.voice = add 164 | end 165 | end 166 | end 167 | o:invoke("OnModeChange", parsePrefix(prefix), target, modes, ...) 168 | end 169 | 170 | handlers["ERROR"] = function(o, prefix, message) 171 | o:invoke("OnDisconnect", message, true) 172 | o:shutdown() 173 | error(message, 3) 174 | end 175 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | local socket = require "socket" 2 | 3 | local error = error 4 | local setmetatable = setmetatable 5 | local rawget = rawget 6 | local unpack = unpack 7 | local pairs = pairs 8 | local assert = assert 9 | local require = require 10 | local tonumber = tonumber 11 | local type = type 12 | local pcall = pcall 13 | 14 | module "irc" 15 | 16 | local meta = {} 17 | meta.__index = meta 18 | _META = meta 19 | 20 | require "irc.util" 21 | require "irc.asyncoperations" 22 | require "irc.handlers" 23 | 24 | local meta_preconnect = {} 25 | function meta_preconnect.__index(o, k) 26 | local v = rawget(meta_preconnect, k) 27 | 28 | if not v and meta[k] then 29 | error(("field '%s' is not accessible before connecting"):format(k), 2) 30 | end 31 | return v 32 | end 33 | 34 | function new(data) 35 | local o = { 36 | nick = assert(data.nick, "Field 'nick' is required"); 37 | username = data.username or "lua"; 38 | realname = data.realname or "Lua owns"; 39 | nickGenerator = data.nickGenerator or defaultNickGenerator; 40 | hooks = {}; 41 | track_users = true; 42 | } 43 | assert(checkNick(o.nick), "Erroneous nickname passed to irc.new") 44 | return setmetatable(o, meta_preconnect) 45 | end 46 | 47 | function meta:hook(name, id, f) 48 | f = f or id 49 | self.hooks[name] = self.hooks[name] or {} 50 | self.hooks[name][id] = f 51 | return id or f 52 | end 53 | meta_preconnect.hook = meta.hook 54 | 55 | 56 | function meta:unhook(name, id) 57 | local hooks = self.hooks[name] 58 | 59 | assert(hooks, "no hooks exist for this event") 60 | assert(hooks[id], "hook ID not found") 61 | 62 | hooks[id] = nil 63 | end 64 | meta_preconnect.unhook = meta.unhook 65 | 66 | function meta:invoke(name, ...) 67 | local hooks = self.hooks[name] 68 | if hooks then 69 | for id,f in pairs(hooks) do 70 | if f(...) then 71 | return true 72 | end 73 | end 74 | end 75 | end 76 | 77 | function meta_preconnect:connect(_host, _port) 78 | local host, port, password, secure, timeout 79 | 80 | if type(_host) == "table" then 81 | host = _host.host 82 | port = _host.port 83 | timeout = _host.timeout 84 | password = _host.password 85 | secure = _host.secure 86 | else 87 | host = _host 88 | port = _port 89 | end 90 | 91 | host = host or error("host name required to connect", 2) 92 | port = port or 6667 93 | 94 | local s = socket.tcp() 95 | 96 | s:settimeout(timeout or 30) 97 | assert(s:connect(host, port)) 98 | 99 | if secure then 100 | local work, ssl = pcall(require, "ssl") 101 | if not work then 102 | error("LuaSec required for secure connections", 2) 103 | end 104 | 105 | local params 106 | if type(secure) == "table" then 107 | params = secure 108 | else 109 | params = {mode = "client", protocol = "tlsv1"} 110 | end 111 | 112 | s = ssl.wrap(s, params) 113 | success, errmsg = s:dohandshake() 114 | if not success then 115 | error(("could not make secure connection: %s"):format(errmsg), 2) 116 | end 117 | end 118 | 119 | self.socket = s 120 | setmetatable(self, meta) 121 | 122 | self:send("CAP REQ multi-prefix") 123 | 124 | self:invoke("PreRegister", self) 125 | self:send("CAP END") 126 | 127 | if password then 128 | self:send("PASS %s", password) 129 | end 130 | 131 | self:send("NICK %s", self.nick) 132 | self:send("USER %s 0 * :%s", self.username, self.realname) 133 | 134 | self.channels = {} 135 | 136 | s:settimeout(0) 137 | 138 | repeat 139 | self:think() 140 | socket.select(nil, nil, 0.1) -- Sleep so that we don't eat CPU 141 | until self.authed 142 | end 143 | 144 | function meta:disconnect(message) 145 | message = message or "Bye!" 146 | 147 | self:invoke("OnDisconnect", message, false) 148 | self:send("QUIT :%s", message) 149 | 150 | self:shutdown() 151 | end 152 | 153 | function meta:shutdown() 154 | self.socket:close() 155 | setmetatable(self, nil) 156 | end 157 | 158 | local function getline(self, errlevel) 159 | local line, err = self.socket:receive("*l") 160 | 161 | if not line and err ~= "timeout" and err ~= "wantread" then 162 | self:invoke("OnDisconnect", err, true) 163 | self:shutdown() 164 | error(err, errlevel) 165 | end 166 | 167 | return line 168 | end 169 | 170 | function meta:think() 171 | while true do 172 | local line = getline(self, 3) 173 | if line and #line > 0 then 174 | if not self:invoke("OnRaw", line) then 175 | self:handle(parse(line)) 176 | end 177 | else 178 | break 179 | end 180 | end 181 | end 182 | 183 | local handlers = handlers 184 | 185 | function meta:handle(prefix, cmd, params) 186 | local handler = handlers[cmd] 187 | if handler then 188 | return handler(self, prefix, unpack(params)) 189 | end 190 | end 191 | 192 | local whoisHandlers = { 193 | ["311"] = "userinfo"; 194 | ["312"] = "node"; 195 | ["319"] = "channels"; 196 | ["330"] = "account"; -- Freenode 197 | ["307"] = "registered"; -- Unreal 198 | } 199 | 200 | function meta:whois(nick) 201 | self:send("WHOIS %s", nick) 202 | 203 | local result = {} 204 | 205 | while true do 206 | local line = getline(self, 3) 207 | if line then 208 | local prefix, cmd, args = parse(line) 209 | 210 | local handler = whoisHandlers[cmd] 211 | if handler then 212 | result[handler] = args 213 | elseif cmd == "318" then 214 | break 215 | else 216 | self:handle(prefix, cmd, args) 217 | end 218 | end 219 | end 220 | 221 | if result.account then 222 | result.account = result.account[3] 223 | elseif result.registered then 224 | result.account = result.registered[2] 225 | end 226 | 227 | return result 228 | end 229 | 230 | function meta:topic(channel) 231 | self:send("TOPIC %s", channel) 232 | end 233 | 234 | -------------------------------------------------------------------------------- /push-luadoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$TRAVIS_REPO_SLUG" == "JakobOvrum/LuaIRC" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" ]; then 4 | 5 | echo -e "Generating luadoc...\n" 6 | 7 | git config --global user.email "travis@travis-ci.org" 8 | git config --global user.name "travis-ci" 9 | git clone --quiet --branch=gh-pages https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG} gh-pages > /dev/null 10 | 11 | cd gh-pages 12 | git rm -rf ./doc 13 | sh ./generate.sh 14 | git add -f ./doc 15 | git commit -m "Lastest documentation on successful travis build $TRAVIS_BUILD_NUMBER auto-pushed to gh-pages" 16 | git push -fq origin gh-pages > /dev/null 17 | 18 | echo -e "Published luadoc to gh-pages.\n" 19 | fi 20 | -------------------------------------------------------------------------------- /set.lua: -------------------------------------------------------------------------------- 1 | local select = require "socket".select 2 | 3 | local setmetatable = setmetatable 4 | local insert = table.insert 5 | local remove = table.remove 6 | local ipairs = ipairs 7 | local error = error 8 | 9 | module "irc.set" 10 | 11 | local set = {} 12 | set.__index = set 13 | 14 | function new(t) 15 | t.connections = {} 16 | t.sockets = {} 17 | return setmetatable(t, set) 18 | end 19 | 20 | function set:add(connection) 21 | local socket = connection.socket 22 | insert(self.sockets, socket) 23 | 24 | self.connections[socket] = connection 25 | insert(self.connections, connection) 26 | end 27 | 28 | function set:remove(connection) 29 | local socket = connection.socket 30 | self.connections[socket] = nil 31 | for k, s in ipairs(self.sockets) do 32 | if socket == s then 33 | remove(self.sockets, k) 34 | remove(self.connections, k) 35 | break 36 | end 37 | end 38 | end 39 | 40 | function set:select() 41 | local read, write, err = select(self.sockets, nil, self.timeout) 42 | 43 | if read then 44 | for k, socket in ipairs(read) do 45 | read[k] = self.connections[socket] 46 | end 47 | end 48 | 49 | return read, err 50 | end 51 | 52 | -- Select - but if it times out, it returns all connections. 53 | function set:poll() 54 | local read, err = self:select() 55 | return err == "timeout" and self.connections or read 56 | end 57 | -------------------------------------------------------------------------------- /util.lua: -------------------------------------------------------------------------------- 1 | local setmetatable = setmetatable 2 | local sub = string.sub 3 | local byte = string.byte 4 | local char = string.char 5 | local table = table 6 | local assert = assert 7 | local tostring = tostring 8 | local type = type 9 | local random = math.random 10 | 11 | module "irc" 12 | 13 | --protocol parsing 14 | function parse(line) 15 | local prefix 16 | local lineStart = 1 17 | if line:sub(1,1) == ":" then 18 | local space = line:find(" ") 19 | prefix = line:sub(2, space-1) 20 | lineStart = space 21 | end 22 | 23 | local _, trailToken = line:find("%s+:", lineStart) 24 | local lineStop = line:len() 25 | local trailing 26 | if trailToken then 27 | trailing = line:sub(trailToken + 1) 28 | lineStop = trailToken - 2 29 | end 30 | 31 | local params = {} 32 | 33 | local _, cmdEnd, cmd = line:find("(%S+)", lineStart) 34 | local pos = cmdEnd + 1 35 | while true do 36 | local _, stop, param = line:find("(%S+)", pos) 37 | 38 | if not param or stop > lineStop then 39 | break 40 | end 41 | 42 | pos = stop + 1 43 | params[#params + 1] = param 44 | end 45 | 46 | if trailing then 47 | params[#params + 1] = trailing 48 | end 49 | 50 | return prefix, cmd, params 51 | end 52 | 53 | function parseNick(nick) 54 | local access, name = nick:match("^([%+@]*)(.+)$") 55 | return parseAccess(access or ""), name 56 | end 57 | 58 | function parsePrefix(prefix) 59 | local user = {} 60 | if prefix then 61 | user.access, user.nick, user.username, user.host = prefix:match("^([%+@]*)(.+)!(.+)@(.+)$") 62 | end 63 | user.access = parseAccess(user.access or "") 64 | return user 65 | end 66 | 67 | function parseAccess(accessString) 68 | local access = {op = false, halfop = false, voice = false} 69 | for c in accessString:gmatch(".") do 70 | if c == "@" then access.op = true 71 | elseif c == "%" then access.halfop = true 72 | elseif c == "+" then access.voice = true 73 | end 74 | end 75 | return access 76 | end 77 | 78 | --mIRC markup scheme (de-facto standard) 79 | color = { 80 | black = 1, 81 | blue = 2, 82 | green = 3, 83 | red = 4, 84 | lightred = 5, 85 | purple = 6, 86 | brown = 7, 87 | yellow = 8, 88 | lightgreen = 9, 89 | navy = 10, 90 | cyan = 11, 91 | lightblue = 12, 92 | violet = 13, 93 | gray = 14, 94 | lightgray = 15, 95 | white = 16 96 | } 97 | 98 | local colByte = char(3) 99 | setmetatable(color, {__call = function(_, text, colornum) 100 | colornum = type(colornum) == "string" and assert(color[colornum], "Invalid color '"..colornum.."'") or colornum 101 | return table.concat{colByte, tostring(colornum), text, colByte} 102 | end}) 103 | 104 | local boldByte = char(2) 105 | function bold(text) 106 | return boldByte..text..boldByte 107 | end 108 | 109 | local underlineByte = char(31) 110 | function underline(text) 111 | return underlineByte..text..underlineByte 112 | end 113 | 114 | function checkNick(nick) 115 | return nick:find("^[a-zA-Z_%-%[|%]%^{|}`][a-zA-Z0-9_%-%[|%]%^{|}`]*$") ~= nil 116 | end 117 | 118 | function defaultNickGenerator(nick) 119 | -- LuaBot -> LuaCot -> LuaCou -> ... 120 | -- We change a random charachter rather than appending to the 121 | -- nickname as otherwise the new nick could exceed the ircd's 122 | -- maximum nickname length. 123 | local randindex = random(1, #nick) 124 | local randchar = sub(nick, randindex, randindex) 125 | local b = byte(randchar) 126 | b = b + 1 127 | if b < 65 or b > 125 then 128 | b = 65 129 | end 130 | -- Get the halves before and after the changed character 131 | local first = sub(nick, 1, randindex - 1) 132 | local last = sub(nick, randindex + 1, #nick) 133 | nick = first..char(b)..last -- Insert the new charachter 134 | return nick 135 | end 136 | 137 | --------------------------------------------------------------------------------