working mod
This commit is contained in:
commit
f9fb013ae0
165
LICENSE
Normal file
165
LICENSE
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
222
README.md
Normal file
222
README.md
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
# Luanti Network API MOD
|
||||||
|
|
||||||
|
It's Luanti MOD adding a Network API.
|
||||||
|
|
||||||
|
*It is an updated project originally started [here](https://github.com/miney-py/mineysocket)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# mineysocket - A network api mod for minetest
|
||||||
|
|
||||||
|
The goal of this mod is to open minetest for other scripting languages.
|
||||||
|
|
||||||
|
For that this mod opens a TCP network port where it receives lua snippets to execute these inside the minetest.
|
||||||
|
That allows you to write a socket client in your favorite language to wrap api functions over network/internet.
|
||||||
|
|
||||||
|
**The reference implementation is [Miney](https://github.com/miney-py/miney), a python interface to minetest.**
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* luasockets
|
||||||
|
|
||||||
|
### Running with docker
|
||||||
|
|
||||||
|
Look here: [Documentation](docker/README.md)
|
||||||
|
|
||||||
|
### Installation with Windows
|
||||||
|
|
||||||
|
It's more complicated, because you can't just put some dlls in the right place.
|
||||||
|
You have to recompile minetest together with luasocket.
|
||||||
|
|
||||||
|
Luckily there are some scripts to do that for you or you just download a precompiled binary that includes all you need.
|
||||||
|
|
||||||
|
* **Miney windows distribution: https://github.com/miney-py/miney_distribution**
|
||||||
|
|
||||||
|
The miney distribution is an all-in-one bundle of minetest, mineysocket, python, miney and a launcher for quickstart.
|
||||||
|
|
||||||
|
* **Precompiled binary: https://github.com/miney-py/minetest_windows/releases**
|
||||||
|
|
||||||
|
Just minetest with mineysocket, nothing else
|
||||||
|
|
||||||
|
* **Build script: https://github.com/miney-py/minetest_windows**
|
||||||
|
|
||||||
|
Use the build script yourself, to compile binaries.
|
||||||
|
|
||||||
|
### Installation with Debian Buster
|
||||||
|
|
||||||
|
The latest minetest version is in the backport repository for buster, so it's very easy to install: https://wiki.minetest.net/Setting_up_a_server/Debian
|
||||||
|
```
|
||||||
|
apt install lua5.1-socket
|
||||||
|
cd /var/games/minetest-server/.minetest/mods
|
||||||
|
git clone git@github.com:miney-py/mineysocket.git
|
||||||
|
```
|
||||||
|
* Edit /var/games/minetest-server/.minetest/worlds/\<your_world\>/world.mt and add:
|
||||||
|
```
|
||||||
|
load_mod_mineysocket = true
|
||||||
|
```
|
||||||
|
* Edit /etc/minetest/minetest.conf
|
||||||
|
* name = \<your_playername\> # This gives you all privileges on your server
|
||||||
|
* secure.trusted_mods = mineysocket # This is needed for luasocket
|
||||||
|
* Optional but recommended:
|
||||||
|
* enable_rollback_recording = true # This allows you to clean up your world
|
||||||
|
* Connect at least once with minetest to your server and login with a username + password, to get you registered.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
Default is, that mineysocket listens on 127.0.0.1 on port 29999.
|
||||||
|
|
||||||
|
You can change this in the minetest menu (Settings -> All Settings -> Mods -> mineysocket) or in the minetest.conf.
|
||||||
|
|
||||||
|
```
|
||||||
|
mineysocket.host_ip = 127.0.0.1
|
||||||
|
```
|
||||||
|
The IP mineysocket is listening on.
|
||||||
|
With "127.0.0.1" only you can connect, with "*" or "0.0.0.0" anyone in the network or internet can connect.
|
||||||
|
|
||||||
|
**WARNING: It could be dangerous to open this to everyone in the internet! Only change if you know what you are doing! If you don't know, let it at "127.0.0.1".**
|
||||||
|
```
|
||||||
|
mineysocket.host_port = 29999
|
||||||
|
```
|
||||||
|
The TCP port mineysocket is listening.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Clients can only run code after authentication and if the user has "server" privilege (or if connected from 127.0.0.1).
|
||||||
|
|
||||||
|
This may change, but currently authenticated users can do anything in the minetest api, also change their own and other users privileges!
|
||||||
|
|
||||||
|
**You use this at your own risk!**
|
||||||
|
|
||||||
|
## Todo
|
||||||
|
|
||||||
|
- [ ] Authentication without sending cleartext password
|
||||||
|
- [ ] Implement limited user rights with a fixed set of available commands
|
||||||
|
|
||||||
|
## Protocol description
|
||||||
|
|
||||||
|
mineysocket is a simple JSON-based TCP protocol. Send a valid JSON-String with a tailing linebreak (`\n`) to the port
|
||||||
|
and mineysocket responds a JSON string with a tailing linebreak.
|
||||||
|
|
||||||
|
### Ping
|
||||||
|
|
||||||
|
A simple alive check, and the only command implemented without json.
|
||||||
|
|
||||||
|
```
|
||||||
|
>>> ping\n
|
||||||
|
<<< pong\n
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
```
|
||||||
|
>>> {"playername": "player", "password": "my_password"}\n
|
||||||
|
<<< {"result": ["auth_ok", "127.0.0.1:31928"], "id": "auth"}\n
|
||||||
|
```
|
||||||
|
Send playername and password and you get auth_ok with your clientid.
|
||||||
|
|
||||||
|
On error you get a error object:
|
||||||
|
```
|
||||||
|
<<< {"error": "authentication error"}\n
|
||||||
|
```
|
||||||
|
Btw: All errors look like this, with different error descriptions.
|
||||||
|
|
||||||
|
Connections from 127.0.0.1 don't need to authenticate.
|
||||||
|
|
||||||
|
### Run lua code
|
||||||
|
|
||||||
|
After authentication, you are ready to send a command. An JSON object key is a command, in this example
|
||||||
|
"lua" to run lua code.
|
||||||
|
```
|
||||||
|
>>> {"lua": "return 12 + 2, \"something\"", id="myrandomstring"}\n
|
||||||
|
<<< {"result": [14, "something"], id="myrandomstring"}\n
|
||||||
|
```
|
||||||
|
Lua code runs inside a function definition, so you need to return a value to get a result send back to you.
|
||||||
|
As you see, you can return multiple values.
|
||||||
|
Optional you can send a (random) id to identify your result, if you run multiple codes parallel.
|
||||||
|
|
||||||
|
More commands will be added later.
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
Mineysocket can send JSON objects on global events.
|
||||||
|
|
||||||
|
To receive events, you need to register for this event. This example registers for `chat_message`:
|
||||||
|
```
|
||||||
|
>>> {"register_event": "chat_message"}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
To unregister, do this:
|
||||||
|
```
|
||||||
|
>>> {"unregister_event": "chat_message"}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
You can register for the following events.
|
||||||
|
|
||||||
|
##### The server was gracefully stopped
|
||||||
|
```
|
||||||
|
<<< { "event" = "shutdown" }\n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### A player's health points changed
|
||||||
|
```
|
||||||
|
<<< {"event" = "player_hpchanged", params = ["player_hpchanged", "<playername>", "<hp change>", {'type': '<reason>', 'from': '<player or engine>'}]}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### A player died
|
||||||
|
```
|
||||||
|
<<< {"event" = "player_died", params = ["<playername>", "<reason>"]}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### A player respawned
|
||||||
|
```
|
||||||
|
<<< {"event" = "player_respawned", params = ["<playername>"]}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### A player joined
|
||||||
|
```
|
||||||
|
<<< {"event" = "player_joined", params = ["<playername>"]}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### A player left
|
||||||
|
```
|
||||||
|
<<< {"event"= "player_left", params = ["<playername>"]}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### An authentication failed
|
||||||
|
```
|
||||||
|
<<< {"event" = "auth_failed", params = ["<name>", "<ip>"]}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### A player cheated
|
||||||
|
|
||||||
|
With one of the following types
|
||||||
|
|
||||||
|
* `moved_too_fast`
|
||||||
|
* `interacted_too_far`
|
||||||
|
* `interacted_while_dead`
|
||||||
|
* `finished_unknown_dig`
|
||||||
|
* `dug_unbreakable`
|
||||||
|
* `dug_too_fast`
|
||||||
|
```
|
||||||
|
<<< {"event" = "player_cheated", params = ["<playername>", {"type": "<type>"}]}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### A new chat message
|
||||||
|
```
|
||||||
|
<<< {"event" = "chat_message", params = ["<name>", "<message>"]}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### A node was placed
|
||||||
|
```
|
||||||
|
<<< {"event" = "node_placed", params = [<pos>, <newnode>, <playername>, <oldnode>, <itemstack>, <pointed_thing>]}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### A node was dug
|
||||||
|
```
|
||||||
|
<<< {"event" = "node_dug", params = [<pos>, <oldnode>, <playername>]}\n
|
||||||
|
```
|
||||||
|
|
||||||
|
##### A node was punched
|
||||||
|
```
|
||||||
|
<<< {"event" = "node_punched", params = [<pos>, <node>, <playername>, <pointed_thing>]}\n
|
||||||
|
```
|
468
init.lua
Normal file
468
init.lua
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
--[[
|
||||||
|
Mineysocket
|
||||||
|
Copyright (C) 2019 Robert Lieback <robertlieback@zetabyte.de>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
--]]
|
||||||
|
|
||||||
|
-- Load external libs
|
||||||
|
local ie
|
||||||
|
if minetest.request_insecure_environment then
|
||||||
|
ie = minetest.request_insecure_environment()
|
||||||
|
end
|
||||||
|
if not ie then
|
||||||
|
error("mineysocket has to be added to the secure.trusted_mods in minetest.conf")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
mineysocket = {} -- global namespace
|
||||||
|
|
||||||
|
-- just a logging function
|
||||||
|
mineysocket.log = function(level, text, ip, port)
|
||||||
|
-- if mineysocket.debug or level ~= "action" then
|
||||||
|
if text then
|
||||||
|
if ip and port then
|
||||||
|
minetest.log(level, "mineysocket: " .. text .. " from " .. ip .. ":" .. port)
|
||||||
|
else
|
||||||
|
minetest.log(level, "mineysocket: " .. ": " .. text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- end
|
||||||
|
end
|
||||||
|
|
||||||
|
mineysocket.log("action", "Starting to load mod")
|
||||||
|
|
||||||
|
--[[
|
||||||
|
mineysocket.capture = function(cmd, raw)
|
||||||
|
local f = assert(io.popen(cmd, 'r'))
|
||||||
|
local s = assert(f:read('*a'))
|
||||||
|
f:close()
|
||||||
|
if raw then return s end
|
||||||
|
s = string.gsub(s, '^%s+', '')
|
||||||
|
s = string.gsub(s, '%s+$', '')
|
||||||
|
s = string.gsub(s, '[\n\r]+', ' ')
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
local ttt = mineysocket.capture("ifconfig", false)
|
||||||
|
mineysocket.log("action", "TEST: " .. ttt)
|
||||||
|
]]--
|
||||||
|
|
||||||
|
-- local sh = require('sh')
|
||||||
|
mineysocket.log("action", os.getenv("HOSTNAME"))
|
||||||
|
mineysocket.log("action", os.getenv("IPV4"))
|
||||||
|
|
||||||
|
-- configuration
|
||||||
|
mineysocket.host_ip = minetest.settings:get("mineysocket.host_ip")
|
||||||
|
mineysocket.host_port = minetest.settings:get("mineysocket.host_port")
|
||||||
|
|
||||||
|
-- Workaround for bug, where default values return only nil
|
||||||
|
if not mineysocket.host_ip then
|
||||||
|
mineysocket.host_ip = os.getenv("IPV4")
|
||||||
|
end
|
||||||
|
if not mineysocket.host_port then
|
||||||
|
mineysocket.host_port = 29999
|
||||||
|
end
|
||||||
|
|
||||||
|
mineysocket.debug = false -- set to true to show all log levels
|
||||||
|
mineysocket.max_clients = 10
|
||||||
|
|
||||||
|
local luasocket = ie.require("socket.core")
|
||||||
|
if not luasocket then
|
||||||
|
error("luasocket is not installed or was not found...")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- setup network server
|
||||||
|
local server, err = luasocket.tcp()
|
||||||
|
if not server then
|
||||||
|
mineysocket.log("action", err)
|
||||||
|
error("exit")
|
||||||
|
end
|
||||||
|
|
||||||
|
local bind, err = server:bind(mineysocket.host_ip, mineysocket.host_port)
|
||||||
|
if not bind then
|
||||||
|
error("mineysocket: " .. err)
|
||||||
|
end
|
||||||
|
local listen, err = server:listen(mineysocket.max_clients)
|
||||||
|
if not listen then
|
||||||
|
error("mineysocket: Socket listen error: " .. err)
|
||||||
|
end
|
||||||
|
mineysocket.log("action", "listening on " .. mineysocket.host_ip .. ":" .. tostring(mineysocket.host_port))
|
||||||
|
|
||||||
|
server:settimeout(0)
|
||||||
|
mineysocket.host_ip, mineysocket.host_port = server:getsockname()
|
||||||
|
if not mineysocket.host_ip or not mineysocket.host_port then
|
||||||
|
error("mineysocket: Couldn't open secrver port!")
|
||||||
|
end
|
||||||
|
|
||||||
|
mineysocket["socket_clients"] = {} -- a table with all connected clients with there options
|
||||||
|
|
||||||
|
-- receive network data and process them
|
||||||
|
mineysocket.log("action", "Installing socket receiver...")
|
||||||
|
minetest.register_globalstep(function(dtime)
|
||||||
|
mineysocket.receive()
|
||||||
|
end)
|
||||||
|
mineysocket.log("action", "Installing socket receiver...DONE")
|
||||||
|
|
||||||
|
|
||||||
|
-- Clean shutdown
|
||||||
|
mineysocket.log("action", "Installing shutdown cleaning...")
|
||||||
|
minetest.register_on_shutdown(function()
|
||||||
|
mineysocket.log("action", "mineysocket: Closing port...")
|
||||||
|
for clientid, client in pairs(mineysocket["socket_clients"]) do
|
||||||
|
mineysocket["socket_clients"][clientid].socket:close()
|
||||||
|
end
|
||||||
|
server:close()
|
||||||
|
end)
|
||||||
|
mineysocket.log("action", "Installing shutdown cleaning...DONE")
|
||||||
|
|
||||||
|
|
||||||
|
-- receive data from clients
|
||||||
|
mineysocket.log("action", "Describing socket receiving function...")
|
||||||
|
mineysocket.receive = function()
|
||||||
|
local data, ip, port, clientid, client, err
|
||||||
|
local result = false
|
||||||
|
|
||||||
|
-- look for new client connections
|
||||||
|
client, err = server:accept()
|
||||||
|
if client then
|
||||||
|
ip, port = client:getpeername()
|
||||||
|
clientid = ip .. ":" .. port
|
||||||
|
mineysocket.log("action", "New connection from " .. ip .. " " .. port)
|
||||||
|
|
||||||
|
client:settimeout(0)
|
||||||
|
-- register the new client
|
||||||
|
if not mineysocket["socket_clients"][clientid] then
|
||||||
|
mineysocket["socket_clients"][clientid] = {}
|
||||||
|
mineysocket["socket_clients"][clientid].socket = client
|
||||||
|
mineysocket["socket_clients"][clientid].last_message = minetest.get_server_uptime()
|
||||||
|
mineysocket["socket_clients"][clientid].buffer = ""
|
||||||
|
mineysocket["socket_clients"][clientid].eom = nil
|
||||||
|
|
||||||
|
if ip == "127.0.0.1" then -- skip authentication for 127.0.0.1
|
||||||
|
mineysocket["socket_clients"][clientid].auth = true
|
||||||
|
mineysocket["socket_clients"][clientid].playername = "localhost"
|
||||||
|
mineysocket["socket_clients"][clientid].events = {}
|
||||||
|
else
|
||||||
|
mineysocket["socket_clients"][clientid].auth = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if err ~= "timeout" then
|
||||||
|
mineysocket.log("error", "Connection error \"" .. err .. "\"")
|
||||||
|
client:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- receive data
|
||||||
|
for clientid, client in pairs(mineysocket["socket_clients"]) do
|
||||||
|
local complete_data, err, data = mineysocket["socket_clients"][clientid].socket:receive("*a")
|
||||||
|
-- there are never complete_data, cause we don't receive lines
|
||||||
|
-- Note: err is "timeout" every time when there are no client data, cause we set timeout to 0 and
|
||||||
|
-- we don't want to wait and block lua/minetest for clients to send data
|
||||||
|
if err ~= "timeout" then
|
||||||
|
mineysocket["socket_clients"][clientid].socket:close()
|
||||||
|
-- cleanup
|
||||||
|
if err == "closed" then
|
||||||
|
mineysocket["socket_clients"][clientid] = nil
|
||||||
|
mineysocket.log("action", "Connection to ".. clientid .." was closed")
|
||||||
|
return
|
||||||
|
else
|
||||||
|
mineysocket.log("action", err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if data and data ~= "" then
|
||||||
|
-- store time of the last message for cleanup of old connection
|
||||||
|
mineysocket["socket_clients"][clientid].last_message = minetest.get_server_uptime()
|
||||||
|
|
||||||
|
if not string.find(data, "\n") then
|
||||||
|
-- fill a buffer and wait for the linebreak
|
||||||
|
if not mineysocket["socket_clients"][clientid].buffer then
|
||||||
|
mineysocket["socket_clients"][clientid].buffer = data
|
||||||
|
else
|
||||||
|
mineysocket["socket_clients"][clientid].buffer = mineysocket["socket_clients"][clientid].buffer .. data
|
||||||
|
end
|
||||||
|
if mineysocket["socket_clients"][clientid].auth == false then -- limit buffer size for unauthenticated connections
|
||||||
|
if mineysocket["socket_clients"][clientid].buffer and string.len(mineysocket["socket_clients"][clientid].buffer) + string.len(data) > 10 then
|
||||||
|
mineysocket["socket_clients"][clientid].buffer = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mineysocket.receive()
|
||||||
|
return
|
||||||
|
else
|
||||||
|
-- get data from buffer and reset em
|
||||||
|
if mineysocket["socket_clients"][clientid]["buffer"] then
|
||||||
|
data = mineysocket["socket_clients"][clientid].buffer .. data
|
||||||
|
mineysocket["socket_clients"][clientid].buffer = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
mineysocket.log("action", "Received: \n" .. data)
|
||||||
|
|
||||||
|
-- we try to find the eom message terminator for this session
|
||||||
|
if mineysocket["socket_clients"][clientid].eom == nil then
|
||||||
|
if string.sub(data, -2) == "\r\n" then
|
||||||
|
mineysocket["socket_clients"][clientid].eom = "\r\n"
|
||||||
|
else
|
||||||
|
mineysocket["socket_clients"][clientid].eom = "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- simple alive check
|
||||||
|
if data == "ping" .. mineysocket["socket_clients"][clientid].eom then
|
||||||
|
mineysocket["socket_clients"][clientid].socket:send("pong" .. mineysocket["socket_clients"][clientid].eom)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- parse data as json
|
||||||
|
local status, input = pcall(minetest.parse_json, data)
|
||||||
|
if not status then
|
||||||
|
mineysocket.log("error", minetest.write_json({ error = input }))
|
||||||
|
mineysocket.log("error", "JSON-Error: " .. input, ip, port)
|
||||||
|
mineysocket.send(clientid, minetest.write_json({ error = "JSON decode error - " .. input }))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- is it a known client, or do we need authentication?
|
||||||
|
if mineysocket["socket_clients"][clientid].auth == true then
|
||||||
|
----------------------------
|
||||||
|
-- commands:
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
-- we run lua code
|
||||||
|
if input["lua"] then
|
||||||
|
result = run_lua(input, clientid, ip, port)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- append event to callback list
|
||||||
|
if input["register_event"] then
|
||||||
|
result = mineysocket.register_event(clientid, input["register_event"])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- append event to callback list
|
||||||
|
if input["unregister_event"] then
|
||||||
|
result = mineysocket.unregister_event(clientid, input["unregister_event"])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle reauthentication
|
||||||
|
if input["playername"] and input["password"] then
|
||||||
|
result = mineysocket.authenticate(input, clientid, ip, port, mineysocket["socket_clients"][clientid].socket)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- reattach id
|
||||||
|
if input["id"] and result ~= false then
|
||||||
|
result["id"] = input["id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- send result
|
||||||
|
if result ~= false then
|
||||||
|
mineysocket.send(clientid, minetest.write_json(result))
|
||||||
|
else
|
||||||
|
mineysocket.send(clientid, minetest.write_json({ error = "Unknown command" }))
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
-- we need authentication
|
||||||
|
if input["playername"] and input["password"] then
|
||||||
|
mineysocket.send(clientid, minetest.write_json(mineysocket.authenticate(input, clientid, ip, port, mineysocket["socket_clients"][clientid].socket)))
|
||||||
|
else
|
||||||
|
mineysocket.send(clientid, minetest.write_json({ error = "Unknown command" }))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mineysocket.log("action", "Describing socket receiving function...DONE")
|
||||||
|
|
||||||
|
|
||||||
|
-- run lua code send by the client
|
||||||
|
mineysocket.log("action", "Function to run lua code...")
|
||||||
|
function run_lua(input, clientid, ip, port)
|
||||||
|
local start_time, err
|
||||||
|
local output = {}
|
||||||
|
|
||||||
|
start_time = minetest.get_server_uptime()
|
||||||
|
|
||||||
|
-- log the (shortend) code
|
||||||
|
if string.len(input["lua"]) > 120 then
|
||||||
|
mineysocket.log("action", "execute: " .. string.sub(input["lua"], 0, 120) .. " ...", ip, port)
|
||||||
|
else
|
||||||
|
mineysocket.log("action", "execute: " .. input["lua"], ip, port)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- run
|
||||||
|
local f, syntaxError = loadstring(input["lua"])
|
||||||
|
-- todo: is there a way to get also warning like "Undeclared global variable ... accessed at ..."?
|
||||||
|
|
||||||
|
if f then
|
||||||
|
local status, result1, result2, result3, result4, result5 = pcall(f, clientid) -- Get the clientid with "...". Example: "mineysocket.send(..., output)"
|
||||||
|
-- is there a more elegant way for unlimited results?
|
||||||
|
|
||||||
|
if status then
|
||||||
|
output["result"] = { result1, result2, result3, result4, result5 }
|
||||||
|
if mineysocket.debug then
|
||||||
|
local json_output = minetest.write_json(output)
|
||||||
|
if string.len(json_output) > 120 then
|
||||||
|
mineysocket.log("action", string.sub(json_output, 0, 120) .. " ..." .. " in " .. (minetest.get_server_uptime() - start_time) .. " seconds", ip, port)
|
||||||
|
else
|
||||||
|
mineysocket.log("action", json_output .. " in " .. (minetest.get_server_uptime() - start_time) .. " seconds", ip, port)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return output
|
||||||
|
else
|
||||||
|
err = result1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
err = syntaxError
|
||||||
|
end
|
||||||
|
|
||||||
|
-- send lua errors
|
||||||
|
if err then
|
||||||
|
output["error"] = err
|
||||||
|
mineysocket.log("error", "Error " .. err .. " in command", ip, port)
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mineysocket.log("action", "Function to run lua code...DONE")
|
||||||
|
|
||||||
|
|
||||||
|
-- authenticate clients
|
||||||
|
mineysocket.log("action", "Function to authenticate...")
|
||||||
|
mineysocket.authenticate = function(input, clientid, ip, port, socket)
|
||||||
|
local player = minetest.get_auth_handler().get_auth(input["playername"])
|
||||||
|
|
||||||
|
-- we skip authentication for 127.0.0.1 and just accept everything
|
||||||
|
if ip == "127.0.0.1" then
|
||||||
|
mineysocket.log("action", "Player '" .. input["playername"] .. "' connected successful", ip, port)
|
||||||
|
mineysocket["socket_clients"][clientid].playername = input["playername"]
|
||||||
|
return { result = { "auth_ok", clientid }, id = "auth" }
|
||||||
|
else
|
||||||
|
-- others need a valid playername and password
|
||||||
|
if player and minetest.check_password_entry(input["playername"], player['password'], input["password"]) and minetest.check_player_privs(input["playername"], { server = true }) then
|
||||||
|
mineysocket.log("action", "Player '" .. input["playername"] .. "' authentication successful", ip, port)
|
||||||
|
mineysocket["socket_clients"][clientid].auth = true
|
||||||
|
mineysocket["socket_clients"][clientid].playername = input["playername"]
|
||||||
|
mineysocket["socket_clients"][clientid].events = {}
|
||||||
|
return { result = { "auth_ok", clientid }, id = "auth" }
|
||||||
|
else
|
||||||
|
mineysocket.log("error", "Wrong playername ('" .. input["playername"] .. "') or password", ip, port)
|
||||||
|
mineysocket["socket_clients"][clientid].auth = false
|
||||||
|
return { error = "authentication error" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mineysocket.log("action", "Function to authenticate...DONE")
|
||||||
|
|
||||||
|
|
||||||
|
-- send data to the client
|
||||||
|
mineysocket.log("action", "Function to send data to client...")
|
||||||
|
mineysocket.send = function(clientid, data)
|
||||||
|
local data = data .. mineysocket["socket_clients"][clientid]["eom"] -- eom is the terminator
|
||||||
|
local size = string.len(data)
|
||||||
|
|
||||||
|
local chunk_size = 4096
|
||||||
|
|
||||||
|
if size < chunk_size then
|
||||||
|
-- we send in one package
|
||||||
|
mineysocket["socket_clients"][clientid].socket:send(data)
|
||||||
|
else
|
||||||
|
-- we split into multiple packages
|
||||||
|
for i = 0, math.floor(size / chunk_size) do
|
||||||
|
mineysocket["socket_clients"][clientid].socket:send(
|
||||||
|
string.sub(data, i * chunk_size, chunk_size + (i * chunk_size) - 1)
|
||||||
|
)
|
||||||
|
luasocket.sleep(0.001) -- Or buffer fills to fast
|
||||||
|
-- todo: Protocol change, that every chunked message needs a response before sending the next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mineysocket.log("action", "Function to send data to client...")
|
||||||
|
|
||||||
|
|
||||||
|
-- register for event
|
||||||
|
mineysocket.log("action", "Function to register events...")
|
||||||
|
mineysocket.register_event = function(clientid, eventname)
|
||||||
|
mineysocket["socket_clients"][clientid].events[#mineysocket["socket_clients"][clientid].events+1] = eventname
|
||||||
|
return { result = "ok" }
|
||||||
|
end
|
||||||
|
mineysocket.log("action", "Function to register events...DONE")
|
||||||
|
|
||||||
|
|
||||||
|
-- unregister for event
|
||||||
|
mineysocket.log("action", "Function to register unevents...")
|
||||||
|
mineysocket.unregister_event = function(clientid, eventname)
|
||||||
|
for index, value in pairs(mineysocket["socket_clients"][clientid].events) do
|
||||||
|
if value == eventname then
|
||||||
|
table.remove( mineysocket["socket_clients"][clientid].events, index )
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return { result = "ok" }
|
||||||
|
end
|
||||||
|
mineysocket.log("action", "Function to register unevents...DONE")
|
||||||
|
|
||||||
|
|
||||||
|
-- send event data to clients, who are registered for this event
|
||||||
|
mineysocket.log("action", "Function to send events...")
|
||||||
|
mineysocket.send_event = function(data)
|
||||||
|
for clientid, values in pairs(mineysocket["socket_clients"]) do
|
||||||
|
local client_events = mineysocket["socket_clients"][clientid].events
|
||||||
|
|
||||||
|
for _, event_data in ipairs(client_events) do
|
||||||
|
local registered_event_name = event_data["event"]
|
||||||
|
local received_event_name = data["event"][1]
|
||||||
|
|
||||||
|
if registered_event_name == received_event_name then
|
||||||
|
mineysocket.log("action", "Sending event: " .. received_event_name)
|
||||||
|
mineysocket.send(clientid, minetest.write_json(data))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mineysocket.log("action", "Function to send events...DONE")
|
||||||
|
|
||||||
|
|
||||||
|
-- BEGIN global event registration
|
||||||
|
mineysocket.log("action", "Registering global fucntions...")
|
||||||
|
minetest.register_on_shutdown(function()
|
||||||
|
mineysocket.send_event({ event = { "shutdown" } })
|
||||||
|
end)
|
||||||
|
minetest.register_on_player_hpchange(function(player, hp_change, reason)
|
||||||
|
mineysocket.send_event({ event = { "player_hpchanged", player:get_player_name(), hp_change, reason } })
|
||||||
|
end, false)
|
||||||
|
minetest.register_on_dieplayer(function(player, reason)
|
||||||
|
mineysocket.send_event({ event = { "player_died", player:get_player_name(), reason } })
|
||||||
|
end)
|
||||||
|
minetest.register_on_respawnplayer(function(player)
|
||||||
|
mineysocket.send_event({ event = { "player_respawned", player:get_player_name() } })
|
||||||
|
end)
|
||||||
|
minetest.register_on_joinplayer(function(player)
|
||||||
|
mineysocket.send_event({ event = { "player_joined", player:get_player_name() } })
|
||||||
|
end)
|
||||||
|
minetest.register_on_leaveplayer(function(player, timed_out)
|
||||||
|
mineysocket.send_event({ event = { "player_left", player:get_player_name(), timed_out } })
|
||||||
|
end)
|
||||||
|
minetest.register_on_authplayer(function(name, ip)
|
||||||
|
mineysocket.send_event({ event = { "auth_failed", name, ip } })
|
||||||
|
end)
|
||||||
|
minetest.register_on_cheat(function(player, cheat)
|
||||||
|
mineysocket.send_event({ event = { "player_cheated", player:get_player_name(), cheat } })
|
||||||
|
end)
|
||||||
|
minetest.register_on_chat_message(function(name, message)
|
||||||
|
mineysocket.send_event({ event = { "chat_message", name, message } })
|
||||||
|
end)
|
||||||
|
mineysocket.log("action", "Registering global fucntions...DONE")
|
||||||
|
-- END global event registration
|
||||||
|
|
||||||
|
minetest.log("action", "Initialization - DONE")
|
Loading…
Reference in New Issue
Block a user