mudgangster

Tiny, scriptable MUD client
Log | Files | Refs | README

chat.lua (4782B)


      1 local lpeg = require( "lpeg" )
      2 
      3 local handleChat
      4 local chatName = "FromDaHood"
      5 
      6 local CommandBytes = {
      7 	nameChange = "\1",
      8 	all = "\4",
      9 	pm = "\5",
     10 	message = "\7",
     11 	version = "\19",
     12 }
     13 
     14 local Chats = { }
     15 
     16 local parser
     17 do
     18 	local command = lpeg.P( 1 )
     19 	local terminator = lpeg.P( "\255" )
     20 	local contents = ( 1 - terminator ) ^ 0
     21 	parser = lpeg.C( command ) * lpeg.C( contents ) * terminator
     22 end
     23 
     24 local function chatFromName( name )
     25 	local idx = tonumber( name )
     26 
     27 	if idx then
     28 		return Chats[ idx ]
     29 	end
     30 
     31 	for _, chat in ipairs( Chats ) do
     32 		if chat.name:startsWith( name ) then
     33 			return chat
     34 		end
     35 	end
     36 
     37 	return nil
     38 end
     39 
     40 local function killChat( chat )
     41 	if chat.state == "killed" then
     42 		return
     43 	end
     44 
     45 	mud.print( "\n#s> Disconnected from %s!", chat.name )
     46 
     47 	chat.state = "killed"
     48 	socket.close( chat.socket )
     49 
     50 	for i, other in ipairs( Chats ) do
     51 		if other == chat then
     52 			table.remove( Chats, i )
     53 			break
     54 		end
     55 	end
     56 end
     57 
     58 local function dataCoro( chat )
     59 	local data = ""
     60 
     61 	while true do
     62 		data = data .. coroutine.yield()
     63 
     64 		if chat.state == "connecting" then
     65 			local name, len = data:match( "^YES:(.-)\n()" )
     66 			if name then
     67 				chat.name = name
     68 				chat.state = "connected"
     69 
     70 				mud.print( "\n#s> Connected to %s@%s:%s", chat.name, chat.address, chat.port )
     71 
     72 				data = data:sub( len )
     73 			end
     74 		end
     75 
     76 		if chat.state == "connected" then
     77 			while true do
     78 				local command, args = parser:match( data )
     79 				if not command then
     80 					break
     81 				end
     82 
     83 				data = data:sub( args:len() + 3 )
     84 
     85 				if command == CommandBytes.all or command == CommandBytes.pm or command == CommandBytes.message then
     86 					local message = args:match( "^\n*(.-)\n*$" )
     87 
     88 					handleChat( message:gsub( "\r", "" ) )
     89 				end
     90 			end
     91 		end
     92 	end
     93 end
     94 
     95 local function dataHandler( chat, loop, watcher )
     96 	local _, err, data = chat.socket:receive( "*a" )
     97 
     98 	if err == "closed" then
     99 		killChat( chat )
    100 		watcher:stop( loop )
    101 
    102 		return
    103 	end
    104 
    105 	assert( coroutine.resume( chat.handler, data ) )
    106 end
    107 
    108 function mud.chat_no_space( form, ... )
    109 	local named = chatName .. form:format( ... )
    110 	local data = CommandBytes.all .. named:parseColours() .. "\n\255"
    111 
    112 	for _, chat in ipairs( Chats ) do
    113 		if chat.state == "connected" then
    114 			socket.send( chat.socket, data )
    115 		end
    116 	end
    117 
    118 	mud.printb( "\n#lr%s", named )
    119 	handleChat()
    120 end
    121 
    122 function mud.chat( form, ... )
    123 	mud.chat_no_space( " " .. form, ... )
    124 end
    125 
    126 local function call( address, port )
    127 	mud.print( "\n#s> Calling %s:%d...", address, port )
    128 
    129 	local chat
    130 	local sock, err = socket.connect( address, port, function( sock, data )
    131 		if data then
    132 			assert( coroutine.resume( chat.handler, data ) )
    133 		else
    134 			killChat( chat )
    135 		end
    136 	end )
    137 
    138 	if not sock then
    139 		mud.print( "\n#s> Connection failed: %s", err )
    140 		return
    141 	end
    142 
    143 	chat = {
    144 		name = address .. ":" .. port,
    145 		socket = sock,
    146 
    147 		address = address,
    148 		port = port,
    149 
    150 		state = "connecting",
    151 		handler = coroutine.create( dataCoro ),
    152 
    153 		called_at = os.time(),
    154 	}
    155 
    156 	assert( coroutine.resume( chat.handler, chat ) )
    157 
    158 	table.insert( Chats, chat )
    159 
    160 	socket.send( sock, "CHAT:%s\n127.0.0.14050 " % chatName )
    161 	socket.send( sock, CommandBytes.version .. "MudGangster\255" )
    162 end
    163 
    164 mud.alias( "/call", {
    165 	[ "^(%S+)$" ] = function( address )
    166 		call( address, 4050 )
    167 	end,
    168 
    169 	[ "^(%S+)[%s:]+(%d+)$" ] = call,
    170 }, "<address> [port]" )
    171 
    172 mud.alias( "/hang", {
    173 	[ "^(%S+)$" ] = function( name )
    174 		local chat = chatFromName( name )
    175 		if chat then
    176 			killChat( chat )
    177 		end
    178 	end,
    179 } )
    180 
    181 local function sendPM( chat, message )
    182 	local named = chatName .. " chats to you, '" .. message .. "'"
    183 	local data = CommandBytes.pm .. named .. "\n\255"
    184 
    185 	socket.send( chat.socket, data )
    186 end
    187 
    188 mud.alias( "/silentpm", {
    189 	[ "^(%S+)%s+(.-)$" ] = function( name, message )
    190 		local chat = chatFromName( name )
    191 
    192 		if chat then
    193 			local coloured = message:parseColours()
    194 			sendPM( chat, coloured )
    195 		end
    196 	end,
    197 } )
    198 
    199 mud.alias( "/pm", {
    200 	[ "^(%S+)%s+(.-)$" ] = function( name, message )
    201 		local chat = chatFromName( name )
    202 
    203 		if chat then
    204 			local coloured = message:parseColours()
    205 			sendPM( chat, coloured )
    206 			mud.printb( "\n#lrYou chat to %s, \"%s\"", chat.name, coloured )
    207 			handleChat()
    208 		end
    209 	end,
    210 } )
    211 
    212 mud.alias( "/chatname", function( name )
    213 	chatName = name
    214 
    215 	local message = CommandBytes.nameChange .. chatName .. "\255"
    216 	for _, chat in ipairs( Chats ) do
    217 		if chat.state == "connected" then
    218 			socket.send( chat.socket, message )
    219 		end
    220 	end
    221 
    222 	mud.print( "\n#s> Chat name changed to %s", chatName )
    223 	handleChat()
    224 end )
    225 
    226 mud.interval( function()
    227 	local now = os.time()
    228 	for i = #Chats, 1, -1 do
    229 		if Chats[ i ].state == "connecting" and now - Chats[ i ].called_at >= 10 then
    230 			mud.print( "\n#s> No response from %s", Chats[ i ].name )
    231 			killChat( Chats[ i ] )
    232 		end
    233 	end
    234 end, 5 )
    235 
    236 return {
    237 	init = function( chatHandler )
    238 		handleChat = chatHandler
    239 	end,
    240 }