Initial commit
This commit is contained in:
commit
33ae414247
13
.cdb.json
Normal file
13
.cdb.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "MOD",
|
||||
"title": "Web chat",
|
||||
"name": "webchat",
|
||||
"short_description": "Makes the in-game chat accessible through a web browser.",
|
||||
"tags": ["chat"],
|
||||
"license": "LGPLv3",
|
||||
"media_license": "LGPLv3",
|
||||
"long_description": "This mod makes the in-game chat accessible through a web browser. The mod itself implements the Minetest server-side functions. It also contains example PHP code for the web chat GUI and the interaction with the Minetest server.",
|
||||
"repo": "https://gitlab.com/seckl/webchat.git",
|
||||
"website": "https://gitlab.com/seckl/webchat/",
|
||||
"issue_tracker": "https://gitlab.com/seckl/webchat/-/issues"
|
||||
}
|
47
README.md
Normal file
47
README.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Minetest Webchat Mod
|
||||
|
||||
This mod makes the [Minetest](https://www.minetest.net/) [in-game chat](https://wiki.minetest.net/Chat) accessible through a web browser. The mod basically:
|
||||
|
||||
* adds some functions to interact with the Minetest in-game chat over a network socket.
|
||||
* modifies the /msg and /me chat commands to work with the web chat.
|
||||
* saves the in-game chat and some player data in local files. A locally installed web server can access the data and send it to the web users.
|
||||
|
||||
## Limitations and known bugs
|
||||
|
||||
* Currently the mod is very simple. It can only be used from a locally running web server, because the web server needs access to some files written by the mod.
|
||||
* The browsers actively poll the chat for new messages. This causes some network and server load when multiple users are logged in at the same time.
|
||||
|
||||
# Installation
|
||||
|
||||
* Install the Webchat mod as described in the [Minetest Wiki](https://wiki.minetest.net/Installing_Mods).
|
||||
* To access the Webchat functions over a network socket you also need to install the [Mineysocket](https://github.com/miney-py/mineysocket) mod.
|
||||
* Set up a web server (e.g. [Apache](https://httpd.apache.org/)) on the same machine and copy the files in the directory `www/` to the DocumentRoot directory (e.g. `/var/www/html/`).
|
||||
* Point $webchatdir in index.php to the Webchat data directory. Of course you can also modify the look & feel of the web pages as you like.
|
||||
* Since the web server user (e.g. `www-data`) needs read access to some files in the data directory, you may need to adjust the access rights accordingly.
|
||||
|
||||
# Usage
|
||||
|
||||
To use the web chat, you just have to log in with your Minetest player name and password, no additional account is needed. You must set a password for your player in the game, empty passwords are not allowed.
|
||||
|
||||
After logging in you see the public chat on the left and your direct messages on the right side. You can hide and unhide both by clicking on the buttons below.
|
||||
|
||||
Type your messages in the input line below the chats. The web chat works just like the [in-game chat](https://wiki.minetest.net/Chat#Sending_messages):
|
||||
|
||||
* Just type your message and hit enter for public messages
|
||||
* `/msg <player> <message>` for a direct message (DM) to a single player
|
||||
* `/me <message>` to prepend your player name in front of a public message
|
||||
|
||||
Your messages will be sent to all players in the game and in the web chat.
|
||||
|
||||
On the bottom of the window the server messages are shown, e.g. when a player joins the game or the web chat.
|
||||
|
||||
## Example
|
||||
|
||||
The following screenshots shows the web chat running on a desktop and a smartphone.
|
||||
|
||||
![Screenshots](screenshots.png)
|
||||
|
||||
# License
|
||||
|
||||
[LGPL-3.0-or-later](https://spdx.org/licenses/LGPL-3.0-or-later.html)
|
||||
|
362
init.lua
Normal file
362
init.lua
Normal file
@ -0,0 +1,362 @@
|
||||
--
|
||||
-- Webchat-Mod
|
||||
-- © 2022-03-12 secklsurvival@gerloni.net
|
||||
-- GPL-3.0-or-later
|
||||
--
|
||||
|
||||
-- To use the functions outside this Mod
|
||||
webchat={}
|
||||
|
||||
-- Mod dir
|
||||
local webchatdir=minetest.get_worldpath().."/"..minetest.get_current_modname()
|
||||
|
||||
-- Player data
|
||||
local pldatadir=webchatdir.."/playerdata"
|
||||
|
||||
-- Files in playerdata/<plname>/ directories:
|
||||
-- lastseen 1611343962 game Contains timestamp (Unix epoch) and type of last activity
|
||||
-- game_is_online webchat_is_online 1609018688 File exists only while player is online and contains the timestamp of his last activity
|
||||
-- game_player.log website_player.log 1611349823 join Log of player game/website events
|
||||
|
||||
minetest.mkdir(webchatdir)
|
||||
|
||||
-- Append new message to webchat log
|
||||
function webchat_send_msg(plname,msg)
|
||||
|
||||
local clog_file=io.open(webchatdir.."/chat.log","a")
|
||||
clog_file:write(os.time().."\t"..plname.."\t"..msg.."\n")
|
||||
clog_file:close()
|
||||
|
||||
end
|
||||
|
||||
-- Append new message to DM log of sender and receiver
|
||||
function dmlog_send_msg(frpl,topl,msg)
|
||||
|
||||
local frpldat_dir=pldatadir.."/"..frpl
|
||||
minetest.mkdir(frpldat_dir)
|
||||
local dmlog_frpl_file=io.open(frpldat_dir.."/dm.log","a")
|
||||
dmlog_frpl_file:write(os.time().."\tto\t"..topl.."\t"..msg.."\n")
|
||||
dmlog_frpl_file:close()
|
||||
|
||||
if topl ~= "+error+" then
|
||||
local topldat_dir=pldatadir.."/"..topl
|
||||
minetest.mkdir(topldat_dir)
|
||||
local dmlog_topl_file=io.open(topldat_dir.."/dm.log","a")
|
||||
dmlog_topl_file:write(os.time().."\tfrom\t"..frpl.."\t"..msg.."\n")
|
||||
dmlog_topl_file:close()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Append new message to server log
|
||||
function server_log(msg)
|
||||
|
||||
local slog_file=io.open(webchatdir.."/server.log","a")
|
||||
slog_file:write(os.time().."\t"..msg.."\n")
|
||||
slog_file:close()
|
||||
|
||||
end
|
||||
|
||||
-- Update playerdata when player tries to join the game
|
||||
local function game_prejoin(plname,plip)
|
||||
|
||||
local pldat_dir=pldatadir.."/"..plname
|
||||
minetest.mkdir(pldat_dir)
|
||||
|
||||
local game_plog_file=io.open(pldat_dir.."/game_player.log","a")
|
||||
game_plog_file:write(os.time().."\tprejoin\t"..plip.."\n")
|
||||
game_plog_file:close()
|
||||
|
||||
end
|
||||
|
||||
-- Update playerdata when player joined the game
|
||||
local function game_join(plname)
|
||||
|
||||
server_log(plname.." joined the game.")
|
||||
|
||||
local pldat_dir=pldatadir.."/"..plname
|
||||
minetest.mkdir(pldat_dir)
|
||||
|
||||
local game_plog_file=io.open(pldat_dir.."/game_player.log","a")
|
||||
game_plog_file:write(os.time().."\tjoin\t"..minetest.get_player_ip(plname).."\n")
|
||||
game_plog_file:close()
|
||||
|
||||
local lastseen_file=io.open(pldat_dir.."/lastseen","w")
|
||||
lastseen_file:write(os.time().."\tgame\n")
|
||||
lastseen_file:close()
|
||||
|
||||
local game_online_file=io.open(pldat_dir.."/game_is_online","w")
|
||||
game_online_file:write(os.time().."\n")
|
||||
game_online_file:close()
|
||||
|
||||
end
|
||||
|
||||
-- Update playerdata when player left the game
|
||||
local function game_leave(action,plname)
|
||||
|
||||
server_log(plname.." left the game.")
|
||||
|
||||
local pldat_dir=pldatadir.."/"..plname
|
||||
minetest.mkdir(pldat_dir)
|
||||
|
||||
local game_plog_file=io.open(pldat_dir.."/game_player.log","a")
|
||||
game_plog_file:write(os.time().."\t"..action.."\n")
|
||||
game_plog_file:close()
|
||||
|
||||
local lastseen_file=io.open(pldat_dir.."/lastseen","w")
|
||||
lastseen_file:write(os.time().."\tgame\n")
|
||||
lastseen_file:close()
|
||||
|
||||
os.remove(pldat_dir.."/game_is_online")
|
||||
|
||||
end
|
||||
|
||||
-- Remove *_is_online files on startup/shutdown in case they were left behind
|
||||
local function cleanup()
|
||||
|
||||
for _,plname in ipairs(minetest.get_dir_list(pldatadir,true)) do
|
||||
local pldat_dir=pldatadir.."/"..plname
|
||||
os.remove(pldat_dir.."/game_is_online")
|
||||
os.remove(pldat_dir.."/webchat_is_online")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Player plname is logged in on the website and has the chat page open in his browser
|
||||
function webchat.ping(plname)
|
||||
|
||||
local pldat_dir=pldatadir.."/"..plname
|
||||
minetest.mkdir(pldat_dir)
|
||||
|
||||
local lastseen_file=io.open(pldat_dir.."/lastseen","w")
|
||||
lastseen_file:write(os.time().."\twebchat\n")
|
||||
lastseen_file:close()
|
||||
|
||||
local webchat_online_file=io.open(pldat_dir.."/webchat_is_online","r")
|
||||
if webchat_online_file then
|
||||
|
||||
webchat_online_file:close()
|
||||
|
||||
else
|
||||
|
||||
minetest.chat_send_all("*** "..plname.." joined the web chat.")
|
||||
server_log(plname.." joined the web chat.")
|
||||
|
||||
local website_plog_file=io.open(pldat_dir.."/website_player.log","a")
|
||||
website_plog_file:write(os.time().."\tchat_join\n")
|
||||
website_plog_file:close()
|
||||
minetest.log("action",plname.." joined the webchat.")
|
||||
|
||||
end
|
||||
|
||||
webchat_online_file=io.open(pldat_dir.."/webchat_is_online","w")
|
||||
webchat_online_file:write(os.time().."\n")
|
||||
webchat_online_file:close()
|
||||
|
||||
-- If 600 sec later the file webchat_is_online is older than 540 sec we suppose that
|
||||
-- the user lost the connection or closed the browser without logging out
|
||||
minetest.after(600,function()
|
||||
local pldat_dir=pldatadir.."/"..plname
|
||||
local webchat_online_file=io.open(pldat_dir.."/webchat_is_online","r")
|
||||
if webchat_online_file and os.time()-webchat_online_file:read() > 540 then
|
||||
|
||||
webchat_online_file:close()
|
||||
|
||||
minetest.chat_send_all("*** "..plname.." left the web chat (timeout).")
|
||||
server_log(plname.." left the web chat (timeout).")
|
||||
|
||||
os.remove(pldat_dir.."/webchat_is_online")
|
||||
|
||||
local website_plog_file=io.open(pldat_dir.."/website_player.log","a")
|
||||
website_plog_file:write(os.time().."\tchat_timeout\n")
|
||||
website_plog_file:close()
|
||||
minetest.log("action",plname.." left the webchat (timeout).")
|
||||
|
||||
end
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
-- Modify /me & /msg chat commands so that they also write to the webchat log (original Minetest code: builtin/game/chat.lua)
|
||||
minetest.register_chatcommand("me",{
|
||||
params="<action>",
|
||||
description="Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')",
|
||||
privs={shout=true},
|
||||
func=function(plname,param)
|
||||
minetest.chat_send_all("* "..plname.." "..param)
|
||||
webchat_send_msg(plname,"* "..plname.." "..param)
|
||||
return true
|
||||
end
|
||||
})
|
||||
minetest.register_chatcommand("msg",{
|
||||
params="<sender> <message>",
|
||||
description="Send a direct message to a player (game and web chat)",
|
||||
privs={shout=true},
|
||||
func=function(sender,param)
|
||||
local sendto,message=param:match("^(%S+)%s(.+)$")
|
||||
if not sendto then
|
||||
return false,"Invalid usage, see /help msg."
|
||||
end
|
||||
if not minetest.player_exists(sendto) then
|
||||
return false,"The player "..sendto.." does not exist."
|
||||
end
|
||||
-- Always send DM to webchat dm log of sender and receiver
|
||||
dmlog_send_msg(sender,sendto,message)
|
||||
-- Send DM to game chat only if the receiver is online
|
||||
if minetest.get_player_by_name(sendto) then
|
||||
minetest.chat_send_player(sendto,"DM from "..sender..": "..message)
|
||||
end
|
||||
minetest.log("action","DM from "..sender.." to "..sendto.." (in game): "..message)
|
||||
return true,"Message sent."
|
||||
end
|
||||
})
|
||||
|
||||
-- Login to website with Minetest credentials
|
||||
function webchat.login(plname,passwd,plip)
|
||||
|
||||
local pl_ah=minetest.get_auth_handler().get_auth(plname)
|
||||
if pl_ah and minetest.check_password_entry(plname,pl_ah['password'],passwd) then
|
||||
|
||||
local pldat_dir=pldatadir.."/"..plname
|
||||
minetest.mkdir(pldat_dir)
|
||||
|
||||
local website_plog_file=io.open(pldat_dir.."/website_player.log","a")
|
||||
website_plog_file:write(os.time().."\tlogin\t"..plip.."\n")
|
||||
website_plog_file:close()
|
||||
|
||||
minetest.log("action",plname.." ["..plip.."] logged in to the website.")
|
||||
return "auth-ok"
|
||||
|
||||
else
|
||||
|
||||
minetest.log("action","Server: User "..plname.." at "..plip.." supplied wrong password on the website.")
|
||||
return "auth-failed"
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Logout from website
|
||||
function webchat.logout(plname)
|
||||
|
||||
local pldat_dir=pldatadir.."/"..plname
|
||||
|
||||
local website_plog_file=io.open(pldat_dir.."/website_player.log","a")
|
||||
|
||||
if os.remove(pldat_dir.."/webchat_is_online") then
|
||||
minetest.chat_send_all("*** "..plname.." left the web chat.")
|
||||
server_log(plname.." left the web chat.")
|
||||
website_plog_file:write(os.time().."\tchat_leave\n")
|
||||
end
|
||||
|
||||
website_plog_file:write(os.time().."\tlogout\n")
|
||||
website_plog_file:close()
|
||||
minetest.log("action",plname.." logged out from the website.")
|
||||
|
||||
end
|
||||
|
||||
-- Receive message from webchat - Partly taken from builtin/game/chat.lua
|
||||
function webchat.receive_msg(sender,msg)
|
||||
|
||||
if msg:sub(1,1) ~= "/" then
|
||||
-- msg does not start with / => chat message
|
||||
|
||||
if minetest.check_player_privs(sender,"shout") then
|
||||
minetest.chat_send_all("<"..sender.."> "..msg)
|
||||
webchat_send_msg(sender,msg)
|
||||
minetest.log("action","Chat message from "..sender.." (in webchat): "..msg)
|
||||
else
|
||||
dmlog_send_msg(sender,"+error+","You don't have permission to shout.")
|
||||
end
|
||||
|
||||
else
|
||||
-- msg starts with a / => command
|
||||
|
||||
local cmd,param=string.match(msg,"^/([^ ]+) *(.*)")
|
||||
param=param or ""
|
||||
|
||||
if not cmd then
|
||||
dmlog_send_msg(sender,"+error+","Empty command.")
|
||||
return false
|
||||
|
||||
elseif cmd=="me" then
|
||||
if minetest.check_player_privs(sender,"shout") then
|
||||
minetest.chat_send_all("* "..sender.." "..param)
|
||||
webchat_send_msg(sender,"* "..sender.." "..param)
|
||||
minetest.log("action","/me message from "..sender.." (in webchat): ".."* "..sender.." "..param)
|
||||
else
|
||||
dmlog_send_msg(sender,"+error+","/me - You don't have permission to run this command (missing privilege: shout).")
|
||||
end
|
||||
|
||||
elseif cmd=="msg" then
|
||||
if minetest.check_player_privs(sender,"shout") then
|
||||
local sendto,message=param:match("^(%S+)%s(.+)$")
|
||||
if not sendto then
|
||||
dmlog_send_msg(sender,"+error+","/msg - Invalid usage.")
|
||||
minetest.log("action","Invalid usage of command /msg by "..sender.." (in webchat).")
|
||||
return false
|
||||
end
|
||||
if not minetest.player_exists(sendto) then
|
||||
dmlog_send_msg(sender,"+error+","/msg - The player "..sendto.." does not exist.")
|
||||
minetest.log("action","DM from "..sender.." to non-existent player "..sendto.." (in webchat).")
|
||||
return false
|
||||
end
|
||||
-- Always send DM to webchat dm log of sender and receiver
|
||||
dmlog_send_msg(sender,sendto,message)
|
||||
-- Send DM to game chat only if the receiver/sender is online
|
||||
if minetest.get_player_by_name(sendto) then
|
||||
minetest.chat_send_player(sendto,"DM from "..sender..": "..message)
|
||||
end
|
||||
if minetest.get_player_by_name(sender) then
|
||||
minetest.chat_send_player(sender,"DM to "..sendto..": "..message)
|
||||
end
|
||||
minetest.log("action","DM from "..sender.." to "..sendto.." (in webchat): "..message)
|
||||
else
|
||||
dmlog_send_msg(sender,"+error+","/msg - You don't have permission to run this command (missing privilege: shout).")
|
||||
end
|
||||
|
||||
else
|
||||
dmlog_send_msg(sender,"+error+","/"..cmd.." - Unknown command.")
|
||||
minetest.log("action","Unknown command "..cmd.." from "..sender.." (in webchat).")
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
webchat.ping(sender)
|
||||
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Game event registration
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- Update playerdata/ on startup
|
||||
cleanup()
|
||||
server_log("Server startup")
|
||||
|
||||
-- Update chatlog on every chat message
|
||||
minetest.register_on_chat_message(webchat_send_msg)
|
||||
|
||||
-- Update playerdata/ on (pre)join and leave
|
||||
minetest.register_on_prejoinplayer(function(plname,plip)
|
||||
game_prejoin(plname,plip)
|
||||
end)
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
game_join(player:get_player_name())
|
||||
end)
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
game_leave("leave",player:get_player_name())
|
||||
end)
|
||||
|
||||
-- Update playerdata/ on shutdown
|
||||
minetest.register_on_shutdown(function()
|
||||
for _,player in ipairs(minetest.get_connected_players()) do
|
||||
game_leave("shutdown",player:get_player_name())
|
||||
end
|
||||
cleanup()
|
||||
server_log("Server shutdown")
|
||||
end)
|
||||
|
||||
--------------------------------------------------------------------------------
|
3
mod.conf
Normal file
3
mod.conf
Normal file
@ -0,0 +1,3 @@
|
||||
name = webchat
|
||||
optional_depends = mineysocket
|
||||
description = Functions to create a Webchat e.g. in PHP.
|
BIN
screenshots.png
Normal file
BIN
screenshots.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 895 KiB |
308
www/index.php
Normal file
308
www/index.php
Normal file
@ -0,0 +1,308 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// Webchat data dir
|
||||
$webchatdir='/var/games/minetest-server/.minetest/worlds/SecklSurvival/webchat';
|
||||
|
||||
// Generate a unique color for every username based on its crc32 hash sum
|
||||
function playercolor($player,$color) {
|
||||
list($r1,$r2,$g1,$g2,$b1,$b2)=str_split(dechex(crc32($player)),1);
|
||||
if ($color=="r") {
|
||||
return 'ff'.$g1.$g2.$b1.$b2;
|
||||
} elseif ($color=="g") {
|
||||
return $r1.$r2.'ff'.$b1.$b2;
|
||||
} elseif ($color=="b") {
|
||||
return $r1.$r2.$g1.$g2.'ff';
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($_SESSION['player'])) {
|
||||
|
||||
// Not logged in => Please login first
|
||||
header('Location: /login/');
|
||||
exit();
|
||||
|
||||
} elseif (isset($_GET['lastchatline']) and isset($_GET['lastservline'])) {
|
||||
|
||||
// Send/update chat history to browser
|
||||
|
||||
$mt_socket=socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
socket_connect($mt_socket,'127.0.0.1','29999');
|
||||
|
||||
$cmd=json_encode(array('lua'=>"webchat.ping('".$_SESSION['player']."')",'id'=>'chat_ping'))."\n";
|
||||
socket_write($mt_socket,$cmd,strlen($cmd));
|
||||
$reply=socket_read($mt_socket,1048576);
|
||||
|
||||
socket_close($mt_socket);
|
||||
|
||||
$chatdata=array();
|
||||
$chatlines=0;
|
||||
$loglines=file($webchatdir.'/chat.log');
|
||||
|
||||
foreach ($loglines as $chatline) {
|
||||
|
||||
$chatlines++;
|
||||
|
||||
// Return all lines after $_GET['lastchatline']
|
||||
if ($chatlines > $_GET['lastchatline']) {
|
||||
|
||||
// Edit line
|
||||
list($timestamp,$user,$text)=explode("\t",htmlspecialchars(trim($chatline),ENT_QUOTES));
|
||||
$date=date('Y-m-d',$timestamp);
|
||||
$time=date('H:i:s T',$timestamp);
|
||||
$color=playercolor($user,"b");
|
||||
$chatline='<tr style="color: #'.$color.'"><td id="u"><'.$user.'></td><td><span id="d"> '.$date.'</span><span id="t"> '.$time.'</span></td></tr><tr style="color: #'.$color.'"><td id="txt" colspan="2">'.$text.'</td></tr>';
|
||||
|
||||
array_push($chatdata,$chatline);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$dmdata=array();
|
||||
$dmlines=0;
|
||||
$loglines=file($webchatdir.'/playerdata/'.$_SESSION['player'].'/dm.log');
|
||||
|
||||
foreach ($loglines as $dmline) {
|
||||
|
||||
$dmlines++;
|
||||
|
||||
// Return all lines after $_GET['lastdmline']
|
||||
if ($dmlines > $_GET['lastdmline']) {
|
||||
|
||||
// Edit line
|
||||
list($timestamp,$fromto,$user,$text)=explode("\t",htmlspecialchars(trim($dmline),ENT_QUOTES));
|
||||
$date=date('Y-m-d',$timestamp);
|
||||
$time=date('H:i:s T',$timestamp);
|
||||
if ($user=="+error+") {
|
||||
if ($timestamp > time()-300) {
|
||||
// Show only error messages from the last 5 min
|
||||
$color='ff0000';
|
||||
$dmline='<tr style="color: #'.$color.'"><td id="err" colspan="2"><dfn class="wchat-tooltip" wchat-tooltip-text="Valid commands are: /msg <player> <message>, /me <message>">Error: '.$text.'</dfn></td></tr>';
|
||||
array_push($dmdata,$dmline);
|
||||
}
|
||||
} else {
|
||||
$color=playercolor($user,"r");
|
||||
$dmline='<tr style="color: #'.$color.'"><td id="u">DM '.$fromto.' '.$user.':</td><td><span id="d"> '.$date.'</span><span id="t"> '.$time.'</span></td></tr><tr style="color: #'.$color.'"><td id="txt" colspan="2">'.$text.'</td></tr>';
|
||||
array_push($dmdata,$dmline);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$servdata=array();
|
||||
$servlines=0;
|
||||
$loglines=file($webchatdir.'/server.log');
|
||||
|
||||
foreach ($loglines as $servline) {
|
||||
|
||||
$servlines++;
|
||||
|
||||
// Return all lines after $_GET['lastservline']
|
||||
if ($servlines > $_GET['lastservline']) {
|
||||
|
||||
// Edit line
|
||||
list($timestamp,$text)=explode("\t",htmlspecialchars(trim($servline),ENT_QUOTES));
|
||||
$date=date('Y-m-d',$timestamp);
|
||||
$time=date('H:i:s T',$timestamp);
|
||||
$servline='<tr><td id="txt">'.$text.'</td><td><span id="d"> '.$date.'</span><span id="t"> '.$time.'</span></td></tr>';
|
||||
|
||||
array_push($servdata,$servline);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
echo json_encode(array("chatlines"=>$chatlines,"servlines"=>$servlines,"dmlines"=>$dmlines,"chatdata"=>$chatdata,"servdata"=>$servdata,"dmdata"=>$dmdata));
|
||||
|
||||
exit();
|
||||
|
||||
} elseif (!empty($_POST['msg'])) {
|
||||
|
||||
// Send chat message
|
||||
|
||||
$mt_socket=socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
socket_connect($mt_socket,'127.0.0.1','29999');
|
||||
|
||||
// Sanitize $message
|
||||
$message=addslashes($_POST['msg']); // Escape some special characters ('"\)
|
||||
$message=trim($message," \n\r\t\v\0\\"); // Remove some special chars from the beginning and end
|
||||
$message=substr($message,0,500); // Max length 500 chars
|
||||
|
||||
$cmd=json_encode(array('lua'=>"webchat.receive_msg('".$_SESSION['player']."','".$message."')",'id'=>'chat_msg'))."\n";
|
||||
socket_write($mt_socket,$cmd,strlen($cmd));
|
||||
$reply=socket_read($mt_socket,1048576);
|
||||
|
||||
socket_close($mt_socket);
|
||||
|
||||
// Send HTTP 204 so that the browser stays on the page
|
||||
ob_start();
|
||||
header("HTTP/1.1 204 NO CONTENT");
|
||||
header("Cache-Control: no-cache, no-store, must-revalidate");
|
||||
header("Pragma: no-cache");
|
||||
header("Expires: 0");
|
||||
ob_end_flush();
|
||||
|
||||
exit();
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Minetest Web Chat</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<script src="jquery.min.js"></script>
|
||||
<script>
|
||||
|
||||
function ToggleChat() {
|
||||
var chlog=document.getElementById("wchat-chatlog");
|
||||
var chbut=document.getElementById("wchat-chswbt-ch");
|
||||
var dmlog=document.getElementById("wchat-dmlog");
|
||||
var dmbut=document.getElementById("wchat-chswbt-dm");
|
||||
if (chlog.style.display==="none") {
|
||||
chlog.style.display="flex";
|
||||
chlog.style.width="50%";
|
||||
chlog.style.float="left";
|
||||
chbut.style.color="#000";
|
||||
chbut.style.background="#2c9cdc";
|
||||
dmlog.style.display="flex";
|
||||
dmlog.style.width="50%";
|
||||
dmbut.style.color="#000";
|
||||
dmbut.style.background="#2c9cdc";
|
||||
} else {
|
||||
chlog.style.display="none";
|
||||
chbut.style.color="inherit";
|
||||
chbut.style.background="inherit";
|
||||
dmlog.style.display="flex";
|
||||
dmlog.style.width="100%";
|
||||
dmbut.style.color="#000";
|
||||
dmbut.style.background="#2c9cdc";
|
||||
}
|
||||
}
|
||||
|
||||
function ToggleDM() {
|
||||
var dmlog=document.getElementById("wchat-dmlog");
|
||||
var dmbut=document.getElementById("wchat-chswbt-dm");
|
||||
var chlog=document.getElementById("wchat-chatlog");
|
||||
var chbut=document.getElementById("wchat-chswbt-ch");
|
||||
if (dmlog.style.display==="none") {
|
||||
chlog.style.display="flex";
|
||||
chlog.style.width="50%";
|
||||
chlog.style.float="left";
|
||||
chbut.style.color="#000";
|
||||
chbut.style.background="#2c9cdc";
|
||||
dmlog.style.display="flex";
|
||||
dmlog.style.width="50%";
|
||||
dmbut.style.color="#000";
|
||||
dmbut.style.background="#2c9cdc";
|
||||
} else {
|
||||
chlog.style.display="flex";
|
||||
chlog.style.width="100%";
|
||||
chlog.style.float="none";
|
||||
chbut.style.color="#000";
|
||||
chbut.style.background="#2c9cdc";
|
||||
dmlog.style.display="none";
|
||||
dmbut.style.color="inherit";
|
||||
dmbut.style.background="inherit";
|
||||
}
|
||||
}
|
||||
|
||||
function playSound(url) {
|
||||
const audio=new Audio(url);
|
||||
audio.play();
|
||||
}
|
||||
|
||||
function scrollDown() {$("html, body").animate({scrollTop: $(document).height()}, "fast")}
|
||||
|
||||
function SelectChatMsg() {document.getElementsByName("msg")[0].select()}
|
||||
|
||||
function Logout() {window.location.replace('/login/?logout')}
|
||||
|
||||
lastChatLine=0;
|
||||
lastDmLine=0;
|
||||
lastServLine=0;
|
||||
scrollOnce=true;
|
||||
|
||||
$(document).ready(function() {
|
||||
// Scroll to bottom once after the page has loaded
|
||||
scrollDown();
|
||||
});
|
||||
|
||||
// Update the chat/dm/server history
|
||||
$.ajaxSetup({timeout: 1500}); // Timeout 1.5 sec for getJSON
|
||||
setInterval("updateHist()",2000); // Update history every 2 sec
|
||||
function updateHist() {
|
||||
$.getJSON('?lastchatline='+lastChatLine+'&lastdmline='+lastDmLine+'&lastservline='+lastServLine,function(data) {
|
||||
if (lastChatLine!=data.chatlines || lastDmLine!=data.dmlines || lastServLine!=data.servlines) {
|
||||
// Play sound and scroll to bottom on incoming message
|
||||
playSound('snd-msg.ogg');
|
||||
scrollDown();
|
||||
}
|
||||
lastChatLine=data.chatlines;
|
||||
lastDmLine=data.dmlines;
|
||||
lastServLine=data.servlines;
|
||||
$.each(data.chatdata,function(key,value) {
|
||||
$("#wchat-chatlog").append(''+value);
|
||||
});
|
||||
$.each(data.dmdata,function(key,value) {
|
||||
$("#wchat-dmlog").append(''+value);
|
||||
});
|
||||
$.each(data.servdata,function(key,value) {
|
||||
$("#wchat-servlog").append(''+value);
|
||||
});
|
||||
// Scroll to bottom once when chat history has been loaded
|
||||
if (scrollOnce) {
|
||||
scrollDown();
|
||||
scrollOnce=false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="wchat-content">
|
||||
|
||||
<h3 id="help">Minetest Web Chat</h3>
|
||||
|
||||
<p>The chat works the same as the Minetest in-game chat:
|
||||
<ul>
|
||||
<li>Chat and /me messages will be sent to all connected players (Minetest and Web Chat).</li>
|
||||
<li>Direct messages (DM) to one player can be sent with /msg <player> <message>.</li>
|
||||
</ul>
|
||||
See the <a href="https://wiki.minetest.net/Chat#Sending_messages" target="_blank">Minetest Wiki</a> for details.
|
||||
Chat and /me messages are shown on the left side, DM on the right.
|
||||
</p>
|
||||
<p>
|
||||
You can hide and unhide the columns by clicking on the green buttons below.
|
||||
</p>
|
||||
|
||||
<table class="wchat-chatout" id="wchat-chatlog"></table>
|
||||
<table class="wchat-chatout" id="wchat-dmlog"></table>
|
||||
|
||||
<table id="wchat-chatsw"><tr><td class="wchat-chswbt" id="wchat-chswbt-ch" onclick="ToggleChat()">Chat</td><td class="wchat-chswbt" id="wchat-chswbt-dm" onclick="ToggleDM()"><span id="long">Direct Messages</span><span id="short">DM</span></td></tr></table>
|
||||
|
||||
<table id="wchat-chatin"><tbody>
|
||||
<tr><td>
|
||||
<form method="POST" action="" onsubmit="SelectChatMsg()">
|
||||
<?php
|
||||
echo ' <p id="u"><'.$_SESSION['player'].'> <a href="#help">?</a></p>'."\n";
|
||||
?>
|
||||
<p><input type="text" required minlength="1" name="msg" placeholder="Type your message here and press enter..." autofocus autocomplete="off"></p>
|
||||
<p><input style="width: 65%; margin-right: 5px" type="submit" value="Send"><input style="width: calc(100% - 65% - 10px); margin-left: 5px" type="button" value="Logout" onclick="Logout()"></p>
|
||||
</form>
|
||||
</td></tr>
|
||||
</tbody></table>
|
||||
|
||||
<table class="wchat-chatout" id="wchat-servlog"></table>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
4
www/jquery.min.js
vendored
Normal file
4
www/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
117
www/login/index.php
Normal file
117
www/login/index.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
function goback() {
|
||||
header('Location: /');
|
||||
exit();
|
||||
}
|
||||
|
||||
if (isset($_GET['logout'])) {
|
||||
|
||||
// Logout
|
||||
|
||||
if (!empty($_SESSION['player'])) {
|
||||
|
||||
$mt_socket=socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
socket_connect($mt_socket,'127.0.0.1','29999');
|
||||
|
||||
$cmd=json_encode(array('lua'=>"webchat.logout('".$_SESSION['player']."')",'id'=>'web_logout'))."\n";
|
||||
socket_write($mt_socket,$cmd,strlen($cmd));
|
||||
$reply=socket_read($mt_socket,1048576);
|
||||
|
||||
socket_close($mt_socket);
|
||||
|
||||
}
|
||||
|
||||
// Delete session cookie
|
||||
$cp=session_get_cookie_params();
|
||||
setcookie(session_name(),'',0,$cp['path'],$cp['domain'],$cp['secure'],isset($cp['httponly']));
|
||||
// Delete and close session
|
||||
session_destroy();
|
||||
session_write_close();
|
||||
|
||||
goback();
|
||||
|
||||
} elseif (!empty($_SESSION['player'])) {
|
||||
|
||||
// Already logged in
|
||||
goback();
|
||||
|
||||
} elseif (!empty($_POST['player']) and !empty($_POST['pass'])) {
|
||||
|
||||
// Login attempt
|
||||
|
||||
// Sanitize $player,$passwd
|
||||
$player=preg_replace('/[^A-Za-z0-9-_]/','',$_POST['player']); // Allow only A-Za-z0-9-_
|
||||
$player=substr($player,0,19); // Max length 19 chars
|
||||
$player=rtrim($player," \n\r\t\v\0\\"); // Remove some trailing special chars
|
||||
$passwd=addslashes($_POST['pass']); // Escape some special characters ('"\)
|
||||
$passwd=substr($passwd,0,99); // Max length 99 chars
|
||||
$passwd=rtrim($passwd,"\n\r\t\v\0\\"); // Remove some trailing special chars
|
||||
|
||||
$mt_socket=socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
socket_connect($mt_socket,'127.0.0.1','29999');
|
||||
|
||||
$cmd=json_encode(array('lua'=>"return webchat.login('".$player."','".$passwd."','".$_SERVER['REMOTE_ADDR']."')",'id'=>'web_login'))."\n";
|
||||
socket_write($mt_socket,$cmd,strlen($cmd));
|
||||
$reply=socket_read($mt_socket,1048576);
|
||||
|
||||
socket_close($mt_socket);
|
||||
|
||||
if (isset(json_decode($reply)->{'result'}[0]) and json_decode($reply)->{'result'}[0]=="auth-ok") {
|
||||
|
||||
// Login successful
|
||||
$_SESSION['player']=$player;
|
||||
goback();
|
||||
|
||||
} else {
|
||||
|
||||
// Wrong player or password
|
||||
header("HTTP/1.1 401 UNAUTHORIZED");
|
||||
$retry=true;
|
||||
|
||||
}
|
||||
|
||||
} elseif (isset($_GET['from'])) {
|
||||
|
||||
header("HTTP/1.1 401 UNAUTHORIZED");
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Minetest Web Chat Login</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="wchat-content">
|
||||
|
||||
<h3>Minetest Web Chat Login</h3>
|
||||
|
||||
<table id="wchat-chatin"><tbody>
|
||||
<tr><td>
|
||||
<form method="POST" action="">
|
||||
<p>Please log in with your Minetest player name and password.</p>
|
||||
<p id="u">In case you did not set a password for your player yet please do it now, empty passwords are not allowed.</p>
|
||||
<p>Player name: </p><p><input type="text" required minlength="1" name="player" placeholder="Enter player name and press enter..." autofocus></p>
|
||||
<p>Password: </p><p><input type="password" required minlength="1" name="pass" placeholder="Enter password and press enter..."></p>
|
||||
<?php
|
||||
if (!empty($retry)) {
|
||||
echo ' <p id="u">Login failed. Make sure you entered the correct player name/password and that you have the shout privilege. Please try again.</p>'."\n";
|
||||
}
|
||||
?>
|
||||
<p><input type="submit" value="Login"></p>
|
||||
</form>
|
||||
</td></tr>
|
||||
</tbody></table>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
BIN
www/snd-msg.ogg
Normal file
BIN
www/snd-msg.ogg
Normal file
Binary file not shown.
60
www/style.css
Normal file
60
www/style.css
Normal file
@ -0,0 +1,60 @@
|
||||
/* Elements */
|
||||
|
||||
html {height: 100%; box-sizing: border-box; font-family: Verdana,sans-serif; font-size: 15px; color: #2c9cdc; background-color: #000}*,*:before,*:after{box-sizing: inherit}
|
||||
footer {display: block; padding-top: 16px; padding-bottom: 16px; border-top: 1px solid; margin-top: 24px; text-align: center}
|
||||
a {color: inherit}
|
||||
b {font-weight: bolder}
|
||||
h3 {font-weight: 400; font-size: 24px; border-bottom: 1px solid; padding-bottom: 4px; margin-top: 5px; margin-bottom: 5px}
|
||||
img {border-style: none; vertical-align: middle; max-width: 100%; height: auto}
|
||||
|
||||
/* Classes */
|
||||
|
||||
.wchat-button {border: none; display: inline-block; height: 36px; padding: 10px 8px; text-decoration: none; cursor: pointer}
|
||||
.wchat-button:hover {color: #000; background-color: #2c9cdc}
|
||||
.wchat-chswbt {color: #000; background-color: #2c9cdc; border: none; display: inline-block; height: 36px; padding: 10px 8px; text-decoration: none; cursor: pointer}
|
||||
.wchat-nobutton {margin-top: 0; margin-bottom: 0; border: none; display: inline-block; height: 36px; padding: 10px 8px; text-decoration: none}
|
||||
.wchat-content {margin-left: auto; margin-right: auto; height: calc(100% - 124px); max-width: 1600px; padding: 8px}
|
||||
|
||||
.wchat-chatout {font-family: monospace; padding: 8px; border-collapse: collapse; line-height: 1; border: 1px solid; overflow: hidden; display: flex; flex-direction: column-reverse; overflow-wrap: anywhere}
|
||||
.wchat-chatout tr {display: block}
|
||||
.wchat-chatout td {white-space: nowrap}
|
||||
.wchat-chatout #u {width: 100%; font-weight: bolder}
|
||||
.wchat-chatout #d, #t {text-align: right; font-size: 0.75em}
|
||||
.wchat-chatout #txt {white-space: normal; width: 100%; padding-left: 10px; padding-top: 0; padding-bottom: 5px}
|
||||
.wchat-chatout #err {white-space: normal; width: 100%; padding: 0; padding-bottom: 5px; display: block}
|
||||
|
||||
.wchat-tooltip {cursor: help; display: block}
|
||||
[wchat-tooltip-text] {position: relative}
|
||||
[wchat-tooltip-text]::after {content: attr(wchat-tooltip-text); pointer-events: none; opacity: 0; transition: opacity 0.5s; display: block; position: absolute; bottom: 1.5em; padding: 0.5em; z-index: 1; background-color: #000; border: solid 1px; border-radius: 0.5em}
|
||||
[wchat-tooltip-text]:hover::after {opacity: 1}
|
||||
|
||||
/* IDs */
|
||||
|
||||
#wchat-chatlog {height: 500px; width: 50%; float: left}
|
||||
#wchat-dmlog {height: 500px; width: 50%}
|
||||
#wchat-servlog {max-height: 100px}
|
||||
|
||||
#wchat-chatsw {width: 100%; border: 1px solid; font-family: monospace; font-weight: bolder; margin-top: 5px; margin-bottom: 5px}
|
||||
#wchat-chatsw td {width: 50%; border: 1px solid; text-align: center}
|
||||
#wchat-chswbt-dm #long {display: inherit}
|
||||
#wchat-chswbt-dm #short {display: none}
|
||||
|
||||
#wchat-chatin {font-family: monospace; border-collapse: collapse; line-height: 1; border: 1px solid; clear: both; display: flex; margin-top: 5px; margin-bottom: 5px}
|
||||
#wchat-chatin tbody {width: 100%}
|
||||
#wchat-chatin tr {display: flex}
|
||||
#wchat-chatin td {width: 100%; padding: 1px 10px}
|
||||
#wchat-chatin p {width: 100%; margin: 10px 0px}
|
||||
#wchat-chatin #u {font-weight: bolder}
|
||||
#wchat-chatin input {width: 100%; font-family: inherit; font-size: inherit; color: inherit; background-color: #000; padding: 12px}
|
||||
|
||||
/* Hide/change some elements on small screens */
|
||||
@media screen and (max-width: 850px){
|
||||
.wchat-chatout #d {display: none}
|
||||
}
|
||||
|
||||
/* Hide/change some elements on very small screens */
|
||||
@media screen and (max-width: 500px){
|
||||
.wchat-chatout #t {display: none}
|
||||
#wchat-chatsw #long {display: none}
|
||||
#wchat-chatsw #short {display: inherit}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user