separated from webmail mod
This commit is contained in:
commit
ca88374fbd
23
.luacheckrc
Normal file
23
.luacheckrc
Normal file
@ -0,0 +1,23 @@
|
||||
unused_args = false
|
||||
allow_defined_top = true
|
||||
|
||||
globals = {
|
||||
"mail",
|
||||
}
|
||||
|
||||
read_globals = {
|
||||
-- Stdlib
|
||||
string = {fields = {"split"}},
|
||||
table = {fields = {"copy", "getn"}},
|
||||
|
||||
-- Minetest
|
||||
"minetest",
|
||||
"vector", "ItemStack",
|
||||
"dump",
|
||||
|
||||
-- Deps
|
||||
"unified_inventory", "default",
|
||||
|
||||
-- optional mods
|
||||
"xban"
|
||||
}
|
13
LICENSE
Normal file
13
LICENSE
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
The file textures/mail_button.png was created by bas080 and is licensed under the WTFPL.
|
||||
|
||||
Webmail component:
|
||||
WTFPL
|
||||
|
||||
All other files:
|
||||
|
||||
Copyright (c) 2016 Carter Kolwey ("Cheapie Systems")
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and/or any associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
38
README.md
Normal file
38
README.md
Normal file
@ -0,0 +1,38 @@
|
||||
Mail mod for Minetest
|
||||
======
|
||||
|
||||
This is a fork of cheapies mail mod
|
||||
|
||||
It adds a mail-system that allows players to send each other messages in-game and via webmail (optional)
|
||||
|
||||
# Screenshots
|
||||
|
||||
Ingame mail
|
||||
![](pics/ingame.png?raw=true)
|
||||
|
||||
# Installation
|
||||
|
||||
## In-game mail mod
|
||||
|
||||
Install it like any other mod: copy the directory `mail_mod` to your "worldmods" folder
|
||||
|
||||
## Webmail
|
||||
|
||||
See: https://github.com/thomasrudin-mt/mail
|
||||
|
||||
# Commands/Howto
|
||||
|
||||
To access your mail click on the inventory mail button or use the "/mail" command
|
||||
Mails can be deleted, marked as read or unread, replied to and forwarded to another player
|
||||
|
||||
# Dependencies
|
||||
* None
|
||||
|
||||
|
||||
# License
|
||||
|
||||
See the "LICENSE" file
|
||||
|
||||
# Old/Historic stuff
|
||||
* Old forum topic: https://forum.minetest.net/viewtopic.php?t=14464
|
||||
* Old mod: https://cheapiesystems.com/git/mail/
|
67
api.lua
Normal file
67
api.lua
Normal file
@ -0,0 +1,67 @@
|
||||
-- see: mail.md
|
||||
|
||||
mail.registered_on_receives = {}
|
||||
function mail.register_on_receive(func)
|
||||
mail.registered_on_receives[#mail.registered_on_receives + 1] = func
|
||||
end
|
||||
|
||||
mail.receive_mail_message = "You have a new message from %s! Subject: %s\nTo view it, type /mail"
|
||||
mail.read_later_message = "You can read your messages later by using the /mail command"
|
||||
|
||||
--[[
|
||||
mail sending function, can be invoked with one object argument (new api) or
|
||||
all 4 parameters (old compat version)
|
||||
see: "Mail format" api.md
|
||||
--]]
|
||||
function mail.send(sender, receiver, subject, body)
|
||||
local m
|
||||
if receiver == nil and subject == nil and body == nil then
|
||||
-- new format (one object param)
|
||||
m = sender
|
||||
|
||||
else
|
||||
-- old format
|
||||
-- create mail from params
|
||||
|
||||
m = {}
|
||||
m.sender = sender
|
||||
m.receiver = receiver
|
||||
m.subject = subject
|
||||
m.body = body
|
||||
|
||||
end
|
||||
|
||||
m.unread = true
|
||||
|
||||
if not m.time then
|
||||
-- add timestamp
|
||||
m.time = os.time()
|
||||
end
|
||||
|
||||
|
||||
minetest.log("action", "[mail] '" .. m.sender .. "' sends mail to '" .. m.receiver ..
|
||||
"' with subject '" .. m.subject .. "' and body: '" .. m.body .. "'")
|
||||
|
||||
local messages = mail.getMessages(m.receiver)
|
||||
|
||||
table.insert(messages, 1, m)
|
||||
mail.setMessages(m.receiver, messages)
|
||||
|
||||
for _, player in ipairs(minetest.get_connected_players()) do
|
||||
local name = player:get_player_name()
|
||||
if name == m.receiver then
|
||||
if m.subject == "" then m.subject = "(No subject)" end
|
||||
if string.len(m.subject) > 30 then
|
||||
m.subject = string.sub(m.subject,1,27) .. "..."
|
||||
end
|
||||
minetest.chat_send_player(m.receiver,
|
||||
string.format(mail.receive_mail_message, m.sender, m.subject))
|
||||
end
|
||||
end
|
||||
|
||||
for i=1, #mail.registered_on_receives do
|
||||
if mail.registered_on_receives[i](m) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
60
api.md
Normal file
60
api.md
Normal file
@ -0,0 +1,60 @@
|
||||
|
||||
# Mail format
|
||||
The mail format in the api hooks
|
||||
|
||||
```lua
|
||||
mail = {
|
||||
sender = "source name",
|
||||
receiver = "destination name",
|
||||
subject = "subject line",
|
||||
body = "mail body",
|
||||
-- 8 attachments max
|
||||
attachments = {"default:stone 99", "default:gold_ingot 99"}
|
||||
}
|
||||
```
|
||||
|
||||
## Sending mail
|
||||
Old variant (pre-1.1)
|
||||
```lua
|
||||
mail.send("source name", "destination name", "subject line", "mail body")
|
||||
```
|
||||
|
||||
New variant (1.1+)
|
||||
```lua
|
||||
mail.send({
|
||||
sender = "source name",
|
||||
receiver = "destination name",
|
||||
subject = "subject line",
|
||||
body = "mail body"
|
||||
})
|
||||
```
|
||||
|
||||
# Hooks
|
||||
On-receive mail hook:
|
||||
|
||||
```lua
|
||||
mail.register_on_receive(function(m)
|
||||
-- "m" is an object in the form: "Mail format"
|
||||
end)
|
||||
```
|
||||
|
||||
# internal mail format (on-disk)
|
||||
The mail format on-disk
|
||||
|
||||
> (worldfolder)/mails/(playername).json
|
||||
|
||||
```json
|
||||
[{
|
||||
"unread": true,
|
||||
"sender": "sender name",
|
||||
"receiver": "receiver name",
|
||||
"subject": "subject name",
|
||||
"body": "main\nmultiline\nbody",
|
||||
"time": 1551258349,
|
||||
"attachments": [
|
||||
"default:stone 99",
|
||||
"default:gold_ingot 99"
|
||||
]
|
||||
}]
|
||||
|
||||
```
|
27
attachment.lua
Normal file
27
attachment.lua
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
local invmap = {}
|
||||
|
||||
|
||||
mail.getAttachmentInventory = function(playername)
|
||||
return invmap[playername]
|
||||
end
|
||||
|
||||
mail.getAttachmentInventoryName = function(playername)
|
||||
return "mail:" .. playername
|
||||
end
|
||||
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
local inv = minetest.create_detached_inventory(mail.getAttachmentInventoryName(name), {})
|
||||
|
||||
invmap[name] = inv
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
invmap[name] = nil
|
||||
if minetest.remove_detached_inventory then
|
||||
minetest.remove_detached_inventory(mail.getAttachmentInventoryName(name))
|
||||
end
|
||||
end)
|
6
chatcommands.lua
Normal file
6
chatcommands.lua
Normal file
@ -0,0 +1,6 @@
|
||||
minetest.register_chatcommand("mail",{
|
||||
description = "Open the mail interface",
|
||||
func = function(name)
|
||||
mail.show_inbox(name)
|
||||
end
|
||||
})
|
3
depends.txt
Normal file
3
depends.txt
Normal file
@ -0,0 +1,3 @@
|
||||
unified_inventory?
|
||||
default?
|
||||
xban2?
|
250
gui.lua
Normal file
250
gui.lua
Normal file
@ -0,0 +1,250 @@
|
||||
selected_message_idxs = {}
|
||||
|
||||
local theme
|
||||
if minetest.get_modpath("default") then
|
||||
theme = default.gui_bg .. default.gui_bg_img
|
||||
else
|
||||
theme = ""
|
||||
end
|
||||
|
||||
mail.inbox_formspec = "size[8,9;]" .. theme .. [[
|
||||
button_exit[7.5,0;0.5,0.5;quit;X]
|
||||
button[6,1;2,0.5;new;New Message]
|
||||
button[6,2;2,0.5;read;Read]
|
||||
button[6,3;2,0.5;reply;Reply]
|
||||
button[6,4;2,0.5;forward;Forward]
|
||||
button[6,5;2,0.5;delete;Delete]
|
||||
button[6,6;2,0.5;markread;Mark Read]
|
||||
button[6,7;2,0.5;markunread;Mark Unread]
|
||||
button[6,8;2,0.5;about;About]
|
||||
tablecolumns[color;text;text]
|
||||
table[0,0;5.75,9;messages;#999,From,Subject]]
|
||||
|
||||
|
||||
function mail.show_about(name)
|
||||
local formspec = [[
|
||||
size[8,5;]
|
||||
button[7.5,0;0.5,0.5;back;X]
|
||||
label[0,0;Mail]
|
||||
label[0,0.5;By cheapie]
|
||||
label[0,1;http://github.com/cheapie/mail]
|
||||
label[0,1.5;See LICENSE file for license information]
|
||||
label[0,2.5;NOTE: Communication using this system]
|
||||
label[0,3;is NOT guaranteed to be private!]
|
||||
label[0,3.5;Admins are able to view the messages]
|
||||
label[0,4;of any player.]
|
||||
]] .. theme
|
||||
|
||||
minetest.show_formspec(name, "mail:about", formspec)
|
||||
end
|
||||
|
||||
function mail.show_inbox(name)
|
||||
local formspec = { mail.inbox_formspec }
|
||||
local messages = mail.getMessages(name)
|
||||
|
||||
if messages[1] then
|
||||
for idx, message in ipairs(messages) do
|
||||
if message.unread then
|
||||
formspec[#formspec + 1] = ",#FFD700"
|
||||
else
|
||||
formspec[#formspec + 1] = ","
|
||||
end
|
||||
formspec[#formspec + 1] = ","
|
||||
formspec[#formspec + 1] = minetest.formspec_escape(message.sender)
|
||||
formspec[#formspec + 1] = ","
|
||||
if message.subject ~= "" then
|
||||
if string.len(message.subject) > 30 then
|
||||
formspec[#formspec + 1] =
|
||||
minetest.formspec_escape(string.sub(message.subject, 1, 27))
|
||||
formspec[#formspec + 1] = "..."
|
||||
else
|
||||
formspec[#formspec + 1] = minetest.formspec_escape(message.subject)
|
||||
end
|
||||
else
|
||||
formspec[#formspec + 1] = "(No subject)"
|
||||
end
|
||||
end
|
||||
if selected_message_idxs[name] then
|
||||
formspec[#formspec + 1] = ";"
|
||||
formspec[#formspec + 1] = tostring(selected_message_idxs[name] + 1)
|
||||
end
|
||||
formspec[#formspec + 1] = "]"
|
||||
else
|
||||
formspec[#formspec + 1] = "]label[2,4.5;No mail]"
|
||||
end
|
||||
minetest.show_formspec(name, "mail:inbox", table.concat(formspec, ""))
|
||||
end
|
||||
|
||||
function mail.show_message(name, msgnumber)
|
||||
local messages = mail.getMessages(name)
|
||||
local message = messages[msgnumber]
|
||||
local formspec = [[
|
||||
size[8,7.2]
|
||||
button[7,0;1,0.5;back;X]
|
||||
label[0,0;From: %s]
|
||||
label[0,0.5;Subject: %s]
|
||||
textarea[0.25,1.25;8,6.25;body;;%s]
|
||||
button[1,6.7;2,1;reply;Reply]
|
||||
button[3,6.7;2,1;forward;Forward]
|
||||
button[5,6.7;2,1;delete;Delete]
|
||||
]] .. theme
|
||||
|
||||
local sender = minetest.formspec_escape(message.sender)
|
||||
local subject = minetest.formspec_escape(message.subject)
|
||||
local body = minetest.formspec_escape(message.body)
|
||||
formspec = string.format(formspec, sender, subject, body)
|
||||
|
||||
minetest.show_formspec(name,"mail:message",formspec)
|
||||
end
|
||||
|
||||
function mail.show_compose(name, defaulttgt, defaultsubj, defaultbody)
|
||||
local formspec = [[
|
||||
size[8,7.2]
|
||||
field[0.25,0.5;4,1;to;To:;%s]
|
||||
field[0.25,1.7;8,1;subject;Subject:;%s]
|
||||
textarea[0.25,2.4;8,5;body;;%s]
|
||||
button[0.5,6.7;3,1;cancel;Cancel]
|
||||
button[7,0;1,0.5;cancel;X]
|
||||
button[4.5,6.7;3,1;send;Send]
|
||||
]] .. theme
|
||||
|
||||
formspec = string.format(formspec,
|
||||
minetest.formspec_escape(defaulttgt),
|
||||
minetest.formspec_escape(defaultsubj),
|
||||
minetest.formspec_escape(defaultbody))
|
||||
|
||||
minetest.show_formspec(name, "mail:compose", formspec)
|
||||
end
|
||||
|
||||
function mail.handle_receivefields(player, formname, fields)
|
||||
if formname == "" and fields and fields.quit and minetest.get_modpath("unified_inventory") then
|
||||
unified_inventory.set_inventory_formspec(player, "craft")
|
||||
end
|
||||
|
||||
if formname == "mail:about" then
|
||||
minetest.after(0.5, function()
|
||||
mail.show_inbox(player:get_player_name())
|
||||
end)
|
||||
|
||||
elseif formname == "mail:inbox" then
|
||||
local name = player:get_player_name()
|
||||
local messages = mail.getMessages(name)
|
||||
|
||||
if fields.messages then
|
||||
local evt = minetest.explode_table_event(fields.messages)
|
||||
selected_message_idxs[name] = evt.row - 1
|
||||
if evt.type == "DCL" and messages[selected_message_idxs[name]] then
|
||||
messages[selected_message_idxs[name]].unread = false
|
||||
mail.show_message(name, selected_message_idxs[name])
|
||||
end
|
||||
mail.setMessages(name, messages)
|
||||
return true
|
||||
end
|
||||
if fields.read then
|
||||
if messages[selected_message_idxs[name]] then
|
||||
messages[selected_message_idxs[name]].unread = false
|
||||
mail.show_message(name, selected_message_idxs[name])
|
||||
end
|
||||
|
||||
elseif fields.delete then
|
||||
if messages[selected_message_idxs[name]] then
|
||||
table.remove(messages, selected_message_idxs[name])
|
||||
end
|
||||
|
||||
mail.show_inbox(name)
|
||||
elseif fields.reply and messages[selected_message_idxs[name]] then
|
||||
local message = messages[selected_message_idxs[name]]
|
||||
local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body
|
||||
mail.show_compose(name, message.sender, "Re: "..message.subject,replyfooter)
|
||||
|
||||
elseif fields.forward and messages[selected_message_idxs[name]] then
|
||||
local message = messages[selected_message_idxs[name]]
|
||||
local fwfooter = "Type your message here.\n\n--Original message follows--\n" ..message.body
|
||||
mail.show_compose(name, "", "Fw: "..message.subject, fwfooter)
|
||||
|
||||
elseif fields.markread then
|
||||
if messages[selected_message_idxs[name]] then
|
||||
messages[selected_message_idxs[name]].unread = false
|
||||
end
|
||||
mail.show_inbox(name)
|
||||
|
||||
elseif fields.markunread then
|
||||
if messages[selected_message_idxs[name]] then
|
||||
messages[selected_message_idxs[name]].unread = true
|
||||
end
|
||||
mail.show_inbox(name)
|
||||
|
||||
elseif fields.new then
|
||||
mail.show_compose(name,"","","Type your message here.")
|
||||
|
||||
elseif fields.quit then
|
||||
if minetest.get_modpath("unified_inventory") then
|
||||
unified_inventory.set_inventory_formspec(player, "craft")
|
||||
end
|
||||
|
||||
elseif fields.about then
|
||||
mail.show_about(name)
|
||||
|
||||
end
|
||||
|
||||
mail.setMessages(name, messages)
|
||||
return true
|
||||
elseif formname == "mail:message" then
|
||||
local name = player:get_player_name()
|
||||
local messages = mail.getMessages(name)
|
||||
|
||||
if fields.back then
|
||||
mail.show_inbox(name)
|
||||
elseif fields.reply then
|
||||
local message = messages[selected_message_idxs[name]]
|
||||
local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body
|
||||
mail.show_compose(name, message.sender, "Re: "..message.subject, replyfooter)
|
||||
elseif fields.forward then
|
||||
local message = messages[selected_message_idxs[name]]
|
||||
local fwfooter = "Type your message here.\n\n--Original message follows--\n" ..message.body
|
||||
mail.show_compose(name, "", "Fw: "..message.subject, fwfooter)
|
||||
elseif fields.delete then
|
||||
if messages[selected_message_idxs[name]] then
|
||||
table.remove(messages,selected_message_idxs[name])
|
||||
end
|
||||
mail.show_inbox(name)
|
||||
end
|
||||
|
||||
mail.setMessages(name, messages)
|
||||
return true
|
||||
elseif formname == "mail:compose" then
|
||||
if fields.send then
|
||||
mail.send({
|
||||
src = player:get_player_name(),
|
||||
dst = fields.to,
|
||||
subject = fields.subject,
|
||||
body = fields.body
|
||||
})
|
||||
end
|
||||
minetest.after(0.5, function()
|
||||
mail.show_inbox(player:get_player_name())
|
||||
end)
|
||||
return true
|
||||
|
||||
elseif fields.mail then
|
||||
mail.show_inbox(player:get_player_name())
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(mail.handle_receivefields)
|
||||
|
||||
|
||||
if minetest.get_modpath("unified_inventory") then
|
||||
mail.receive_mail_message = mail.receive_mail_message ..
|
||||
" or use the mail button in the inventory"
|
||||
mail.read_later_message = mail.read_later_message ..
|
||||
" or by using the mail button in the inventory"
|
||||
|
||||
unified_inventory.register_button("mail", {
|
||||
type = "image",
|
||||
image = "mail_button.png",
|
||||
tooltip = "Mail"
|
||||
})
|
||||
end
|
59
hud.lua
Normal file
59
hud.lua
Normal file
@ -0,0 +1,59 @@
|
||||
|
||||
local huddata = {}
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
local data = {}
|
||||
|
||||
data.imageid = player:hud_add({
|
||||
hud_elem_type = "image",
|
||||
name = "MailIcon",
|
||||
position = {x=0.52, y=0.52},
|
||||
text="",
|
||||
scale = {x=1,y=1},
|
||||
alignment = {x=0.5, y=0.5},
|
||||
})
|
||||
|
||||
data.textid = player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
name = "MailText",
|
||||
position = {x=0.55, y=0.52},
|
||||
text= "",
|
||||
scale = {x=1,y=1},
|
||||
alignment = {x=0.5, y=0.5},
|
||||
})
|
||||
|
||||
|
||||
huddata[name] = data
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
huddata[name] = nil
|
||||
end)
|
||||
|
||||
|
||||
mail.hud_update = function(playername, messages)
|
||||
local data = huddata[playername]
|
||||
local player = minetest.get_player_by_name(playername)
|
||||
|
||||
if not data or not player then
|
||||
return
|
||||
end
|
||||
|
||||
local unreadcount = 0
|
||||
for _, message in ipairs(messages) do
|
||||
if message.unread then
|
||||
unreadcount = unreadcount + 1
|
||||
end
|
||||
end
|
||||
|
||||
if unreadcount == 0 then
|
||||
player:hud_change(data.imageid, "text", "")
|
||||
player:hud_change(data.textid, "text", "")
|
||||
else
|
||||
player:hud_change(data.imageid, "text", "email_mail.png")
|
||||
player:hud_change(data.textid, "text", unreadcount .. " /mail")
|
||||
end
|
||||
|
||||
end
|
55
init.lua
Normal file
55
init.lua
Normal file
@ -0,0 +1,55 @@
|
||||
mail = {
|
||||
-- mail directory
|
||||
maildir = minetest.get_worldpath().."/mails",
|
||||
|
||||
-- allow item/node attachments
|
||||
allow_attachments = minetest.settings:get("mail.allow_attachments") == "true",
|
||||
|
||||
webmail = {
|
||||
-- disallow banned players in the webmail interface
|
||||
disallow_banned_players = minetest.settings:get("webmail.disallow_banned_players") == "true",
|
||||
|
||||
-- url and key to the webmail server
|
||||
url = minetest.settings:get("webmail.url"),
|
||||
key = minetest.settings:get("webmail.key")
|
||||
},
|
||||
|
||||
tan = {}
|
||||
}
|
||||
|
||||
|
||||
local MP = minetest.get_modpath(minetest.get_current_modname())
|
||||
dofile(MP .. "/chatcommands.lua")
|
||||
dofile(MP .. "/migrate.lua")
|
||||
dofile(MP .. "/attachment.lua")
|
||||
dofile(MP .. "/hud.lua")
|
||||
dofile(MP .. "/storage.lua")
|
||||
dofile(MP .. "/api.lua")
|
||||
dofile(MP .. "/gui.lua")
|
||||
dofile(MP .. "/onjoin.lua")
|
||||
|
||||
-- optional webmail stuff below
|
||||
|
||||
--[[ minetest.conf
|
||||
secure.http_mods = mail
|
||||
webmail.url = http://127.0.0.1:8080
|
||||
webmail.key = myserverkey
|
||||
--]]
|
||||
|
||||
local http = minetest.request_http_api()
|
||||
|
||||
if http then
|
||||
local webmail_url = mail.webmail.url
|
||||
local webmail_key = mail.webmail.key
|
||||
|
||||
if not webmail_url then error("webmail.url is not defined") end
|
||||
if not webmail_key then error("webmail.key is not defined") end
|
||||
|
||||
print("[mail] loading webmail-component with endpoint: " .. webmail_url)
|
||||
dofile(MP .. "/tan.lua")
|
||||
dofile(MP .. "/webmail.lua")
|
||||
mail.webmail_init(http, webmail_url, webmail_key)
|
||||
end
|
||||
|
||||
-- migrate storage
|
||||
mail.migrate()
|
24
migrate.lua
Normal file
24
migrate.lua
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
-- migrate from mail.db to player-file-based mailbox
|
||||
|
||||
mail.migrate = function()
|
||||
|
||||
local file = io.open(minetest.get_worldpath().."/mail.db", "r")
|
||||
if file then
|
||||
print("[mail] migrating to new per-player storage")
|
||||
minetest.mkdir(mail.maildir)
|
||||
|
||||
local data = file:read("*a")
|
||||
local oldmails = minetest.deserialize(data)
|
||||
file:close()
|
||||
|
||||
for name, oldmessages in pairs(oldmails) do
|
||||
mail.setMessages(name, oldmessages)
|
||||
end
|
||||
|
||||
-- rename file
|
||||
print("[mail] migration done, renaming old mail.db")
|
||||
os.rename(minetest.get_worldpath().."/mail.db", minetest.get_worldpath().."/mail.db.old")
|
||||
end
|
||||
|
||||
end
|
19
onjoin.lua
Normal file
19
onjoin.lua
Normal file
@ -0,0 +1,19 @@
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
minetest.after(2, function(name)
|
||||
local messages = mail.getMessages(name)
|
||||
|
||||
local unreadcount = 0
|
||||
|
||||
for _, message in pairs(messages) do
|
||||
if message.unread then
|
||||
unreadcount = unreadcount + 1
|
||||
end
|
||||
end
|
||||
|
||||
if unreadcount > 0 then
|
||||
minetest.chat_send_player(name,
|
||||
"(" .. unreadcount .. ") You have mail! Type /mail to read")
|
||||
|
||||
end
|
||||
end, player:get_player_name())
|
||||
end)
|
32
storage.lua
Normal file
32
storage.lua
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
-- TODO: maybe local cache?
|
||||
|
||||
function getMailFile(playername)
|
||||
local saneplayername = string.gsub(playername, "[.|/]", "")
|
||||
return mail.maildir .. "/" .. saneplayername .. ".json"
|
||||
end
|
||||
|
||||
mail.getMessages = function(playername)
|
||||
local file = io.open(getMailFile(playername), "r")
|
||||
local messages = {}
|
||||
if file then
|
||||
local json = file:read("*a")
|
||||
messages = minetest.parse_json(json or "[]") or {}
|
||||
mail.hud_update(playername, messages)
|
||||
file:close()
|
||||
end
|
||||
|
||||
return messages
|
||||
end
|
||||
|
||||
mail.setMessages = function(playername, messages)
|
||||
local file = io.open(getMailFile(playername),"w")
|
||||
local json = minetest.write_json(messages)
|
||||
if file and file:write(json) and file:close() then
|
||||
mail.hud_update(playername, messages)
|
||||
return true
|
||||
else
|
||||
minetest.log("error","[mail] Save failed - messages may be lost!")
|
||||
return false
|
||||
end
|
||||
end
|
16
tan.lua
Normal file
16
tan.lua
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
|
||||
minetest.register_chatcommand("webmail_tan", {
|
||||
description = "generates a tan (temporary access number) for the webmail access",
|
||||
func = function(name)
|
||||
local tan = "" .. math.random(1000, 9999)
|
||||
mail.tan[name] = tan
|
||||
|
||||
return true, "Your tan is " .. tan .. ", it will expire upon leaving the game"
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
mail.tan[name] = nil
|
||||
end)
|
BIN
textures/email_mail.png
Normal file
BIN
textures/email_mail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 270 B |
BIN
textures/mail_button.png
Normal file
BIN
textures/mail_button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
93
util/channel.lua
Normal file
93
util/channel.lua
Normal file
@ -0,0 +1,93 @@
|
||||
-- bi-directional http-channel
|
||||
-- with long-poll GET and POST on the same URL
|
||||
|
||||
local debug = false
|
||||
|
||||
local function Channel(http, url, cfg)
|
||||
cfg = cfg or {}
|
||||
local extra_headers = cfg.extra_headers or {}
|
||||
local timeout = cfg.timeout or 1
|
||||
local long_poll_timeout = cfg.long_poll_timeout or 30
|
||||
local error_retry = cfg.error_retry or 10
|
||||
|
||||
-- assemble post-header with json content
|
||||
local post_headers = { "Content-Type: application/json" }
|
||||
for _,header in pairs(cfg.extra_headers) do
|
||||
table.insert(post_headers, header)
|
||||
end
|
||||
|
||||
local recv_listeners = {}
|
||||
local run = true
|
||||
|
||||
local recv_loop
|
||||
|
||||
recv_loop = function()
|
||||
assert(run)
|
||||
|
||||
-- long-poll GET
|
||||
http.fetch({
|
||||
url = url,
|
||||
extra_headers = extra_headers,
|
||||
timeout = long_poll_timeout
|
||||
}, function(res)
|
||||
if res.succeeded and res.code == 200 then
|
||||
local data = minetest.parse_json(res.data)
|
||||
|
||||
if debug then
|
||||
minetest.log("action", "[webmail-rx] " .. dump(data))
|
||||
end
|
||||
|
||||
if data then
|
||||
for _,listener in pairs(recv_listeners) do
|
||||
listener(data)
|
||||
end
|
||||
end
|
||||
-- reschedule immediately
|
||||
minetest.after(0, recv_loop)
|
||||
else
|
||||
-- error, retry after some time
|
||||
minetest.after(error_retry, recv_loop)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local send = function(data)
|
||||
assert(run)
|
||||
-- POST
|
||||
|
||||
if debug then
|
||||
minetest.log("action", "[webmail-tx] " .. dump(data))
|
||||
end
|
||||
|
||||
http.fetch({
|
||||
url = url,
|
||||
extra_headers = post_headers,
|
||||
timeout = timeout,
|
||||
post_data = minetest.write_json(data)
|
||||
}, function(res)
|
||||
-- TODO: error-handling
|
||||
end)
|
||||
end
|
||||
|
||||
local receive = function(listener)
|
||||
table.insert(recv_listeners, listener)
|
||||
end
|
||||
|
||||
local close = function()
|
||||
run = false
|
||||
end
|
||||
|
||||
recv_loop();
|
||||
|
||||
return {
|
||||
send = send,
|
||||
receive = receive,
|
||||
close = close
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
return Channel
|
156
webmail.lua
Normal file
156
webmail.lua
Normal file
@ -0,0 +1,156 @@
|
||||
-- false per default
|
||||
local has_xban2_mod = minetest.get_modpath("xban2")
|
||||
|
||||
local MP = minetest.get_modpath(minetest.get_current_modname())
|
||||
local Channel = dofile(MP .. "/util/channel.lua")
|
||||
local channel
|
||||
|
||||
-- auth request from webmail
|
||||
local function auth_handler(data)
|
||||
local auth = data.params
|
||||
local handler = minetest.get_auth_handler()
|
||||
minetest.log("action", "[webmail] auth: " .. auth.playername)
|
||||
|
||||
local success = false
|
||||
local banned = false
|
||||
local message = ""
|
||||
|
||||
if mail.webmail.disallow_banned_players and has_xban2_mod then
|
||||
-- check xban db
|
||||
local xbanentry = xban.find_entry(auth.playername)
|
||||
if xbanentry and xbanentry.banned then
|
||||
banned = true
|
||||
message = "Banned!"
|
||||
end
|
||||
end
|
||||
|
||||
if not banned then
|
||||
-- check tan
|
||||
local tan = mail.tan[auth.playername]
|
||||
if tan ~= nil then
|
||||
success = tan == auth.password
|
||||
end
|
||||
|
||||
-- check auth
|
||||
if not success then
|
||||
local entry = handler.get_auth(auth.playername)
|
||||
if entry and minetest.check_password_entry(auth.playername, entry.password, auth.password) then
|
||||
success = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
channel.send({
|
||||
method = data.method,
|
||||
id = data.id,
|
||||
result = {
|
||||
success = success,
|
||||
message = message
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
-- send request from webmail
|
||||
local function send_handler(data)
|
||||
-- send mail from webclient
|
||||
if not data.params then
|
||||
return
|
||||
end
|
||||
|
||||
minetest.log("action", "[webmail] sending mail from webclient: " .. data.params.sender ..
|
||||
" -> " .. data.params.receiver)
|
||||
|
||||
mail.send(data.params)
|
||||
|
||||
channel.send({
|
||||
method = data.method,
|
||||
id = data.id,
|
||||
result = {
|
||||
success = true
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
-- get player messages request from webmail
|
||||
local function get_player_messages_handler(data)
|
||||
local messages = mail.getMessages(data.params.playername)
|
||||
channel.send({
|
||||
method = data.method,
|
||||
id = data.id,
|
||||
result = messages
|
||||
})
|
||||
end
|
||||
|
||||
-- remove mail
|
||||
local function delete_mail_handler(data)
|
||||
local index = data.params.index
|
||||
local playername = data.params.playername
|
||||
|
||||
local messages = mail.getMessages(playername)
|
||||
if messages[index] then
|
||||
table.remove(messages, index)
|
||||
end
|
||||
mail.setMessages(playername, messages)
|
||||
-- TODO: check subject
|
||||
|
||||
channel.send({
|
||||
method = data.method,
|
||||
id = data.id,
|
||||
result = { success = true }
|
||||
})
|
||||
end
|
||||
|
||||
-- mark mail as read
|
||||
local function mark_mail_read_handler(data)
|
||||
local index = data.params.index
|
||||
local playername = data.params.playername
|
||||
local read = data.params.read
|
||||
|
||||
local messages = mail.getMessages(playername)
|
||||
|
||||
if messages[index] then
|
||||
messages[index].unread = not read
|
||||
end
|
||||
mail.setMessages(playername, messages)
|
||||
-- TODO: check subject
|
||||
|
||||
channel.send({
|
||||
method = data.method,
|
||||
id = data.id,
|
||||
result = { success = true }
|
||||
})
|
||||
end
|
||||
|
||||
function mail.webmail_send_hook(m)
|
||||
channel.send({
|
||||
type = "new-message",
|
||||
data = m
|
||||
})
|
||||
end
|
||||
mail.register_on_receive(mail.webmail_send_hook)
|
||||
|
||||
function mail.webmail_init(http, url, key)
|
||||
channel = Channel(http, url .. "/api/minetest/channel", {
|
||||
extra_headers = { "webmailkey: " .. key }
|
||||
})
|
||||
|
||||
channel.receive(function(data)
|
||||
if data.method == "auth" then
|
||||
auth_handler(data)
|
||||
|
||||
elseif data.method == "get-mails" then
|
||||
get_player_messages_handler(data)
|
||||
|
||||
elseif data.method == "mark-mail-read" then
|
||||
mark_mail_read_handler(data)
|
||||
|
||||
elseif data.method == "delete-mail" then
|
||||
delete_mail_handler(data)
|
||||
|
||||
elseif data.method == "send" then
|
||||
send_handler(data)
|
||||
|
||||
|
||||
end
|
||||
end)
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user