Added a mail system.
parent
130c824035
commit
d14a8ce1cc
|
@ -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.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
Mail mod for Minetest (ingame mod)
|
||||
======
|
||||
|
||||
![](https://github.com/minetest-mail/mail_mod/workflows/integration-test/badge.svg)
|
||||
![](https://github.com/minetest-mail/mail_mod/workflows/luacheck/badge.svg)
|
||||
|
||||
|
||||
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/minetest-mail/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
|
||||
|
||||
# Textures
|
||||
* textures/email_mail.png (https://github.com/rubenwardy/email.git WTFPL)
|
||||
|
||||
# Contributors
|
||||
|
||||
* Cheapie (initial idea/project)
|
||||
* Rubenwardy (lua/ui improvements)
|
||||
|
||||
# Old/Historic stuff
|
||||
* Old forum topic: https://forum.minetest.net/viewtopic.php?t=14464
|
||||
* Old mod: https://cheapiesystems.com/git/mail/
|
|
@ -0,0 +1,121 @@
|
|||
-- 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
|
||||
|
||||
TODO: refactor this garbage code!
|
||||
--]]
|
||||
function mail.send(src, dst, subject, body)
|
||||
-- figure out format
|
||||
local m
|
||||
if dst == nil and subject == nil and body == nil then
|
||||
-- new format (one object param)
|
||||
m = src
|
||||
else
|
||||
-- old format
|
||||
m = {}
|
||||
m.from = src
|
||||
m.to = dst
|
||||
m.subject = subject
|
||||
m.body = body
|
||||
end
|
||||
|
||||
if m.dst and not m.to then
|
||||
-- populate "to" field
|
||||
m.to = m.dst
|
||||
end
|
||||
|
||||
if m.src and not m.from then
|
||||
-- populate "from" field
|
||||
m.from = m.src
|
||||
end
|
||||
|
||||
-- sane default values
|
||||
m.subject = m.subject or ""
|
||||
m.body = m.body or ""
|
||||
|
||||
local cc
|
||||
local bcc
|
||||
local extra
|
||||
-- log mail send action
|
||||
if m.cc or m.bcc then
|
||||
if m.cc then
|
||||
cc = "CC: " .. m.cc
|
||||
if m.bcc then
|
||||
cc = cc .. " - "
|
||||
end
|
||||
else
|
||||
cc = ""
|
||||
end
|
||||
if m.bcc then
|
||||
bcc = "BCC: " .. m.bcc
|
||||
else
|
||||
bcc = ""
|
||||
end
|
||||
extra = " (" .. cc .. bcc .. ")"
|
||||
else
|
||||
extra = ""
|
||||
end
|
||||
minetest.log("action", "[mail] '" .. m.from .. "' sends mail to '" .. m.to .. "'" ..
|
||||
extra .. "' with subject '" .. m.subject .. "' and body: '" .. m.body .. "'")
|
||||
|
||||
|
||||
-- normalize to, cc and bcc while compiling a list of all recipients
|
||||
local recipients = {}
|
||||
m.to = mail.normalize_players_and_add_recipients(m.to, recipients)
|
||||
if m.cc then
|
||||
m.cc = mail.normalize_players_and_add_recipients(m.cc, recipients)
|
||||
end
|
||||
if m.bcc then
|
||||
m.bcc = mail.normalize_players_and_add_recipients(m.bcc, recipients)
|
||||
end
|
||||
|
||||
-- form the actual mail
|
||||
local msg = {
|
||||
unread = true,
|
||||
sender = m.from,
|
||||
to = m.to,
|
||||
subject = m.subject,
|
||||
body = m.body,
|
||||
time = os.time(),
|
||||
}
|
||||
if m.cc then
|
||||
msg.cc = m.cc
|
||||
end
|
||||
|
||||
-- send the mail to all recipients
|
||||
for _, recipient in pairs(recipients) do
|
||||
local messages = mail.getMessages(recipient)
|
||||
table.insert(messages, 1, msg)
|
||||
mail.setMessages(recipient, messages)
|
||||
end
|
||||
|
||||
-- notify recipients that happen to be online
|
||||
for _, player in ipairs(minetest.get_connected_players()) do
|
||||
local name = player:get_player_name()
|
||||
if recipients[string.lower(name)] ~= nil 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(name,
|
||||
string.format(mail.receive_mail_message, m.from, m.subject))
|
||||
end
|
||||
end
|
||||
|
||||
for i=1, #mail.registered_on_receives do
|
||||
if mail.registered_on_receives[i](m) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
# Mail format
|
||||
The mail format in the api hooks
|
||||
|
||||
```lua
|
||||
mail = {
|
||||
from = "sender name",
|
||||
to = "players, which, are, addressed",
|
||||
cc = "carbon copy",
|
||||
bcc = "players, which, get, a, copy, but, are, not, visible, to, others",
|
||||
subject = "subject line",
|
||||
body = "mail body",
|
||||
-- 8 attachments max
|
||||
attachments = {"default:stone 99", "default:gold_ingot 99"}
|
||||
}
|
||||
```
|
||||
|
||||
The fields `to`, `cc` and `bcc` can contain a player, multiple player names separated by commas, or be empty. Players in `to` are the recipiants, who are addressed directly. `cc` specifies players that get the mail to get notified, but are not immediate part of the conversation. There is no technical difference between `to` and `cc`, it just implies meaning for the players. Players can see all fields making up the mail except `bcc`, which is the only difference to `cc`.
|
||||
|
||||
Attachments need to be provided for each player getting the mail. Until this is implemented, trying to send a mail to multiple players will fail.
|
||||
|
||||
The `from` and `to` fields were renamed from the previous format:
|
||||
|
||||
```lua
|
||||
mail = {
|
||||
src = "source name",
|
||||
dst = "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({
|
||||
from = "sender name",
|
||||
to = "destination name",
|
||||
cc = "carbon copy",
|
||||
bcc = "blind carbon copy",
|
||||
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",
|
||||
"subject": "subject name",
|
||||
"body": "main\nmultiline\nbody",
|
||||
"time": 1551258349,
|
||||
"attachments": [
|
||||
"default:stone 99",
|
||||
"default:gold_ingot 99"
|
||||
]
|
||||
}]
|
||||
|
||||
```
|
|
@ -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)
|
|
@ -0,0 +1,6 @@
|
|||
minetest.register_chatcommand("mail",{
|
||||
description = "Open the mail interface",
|
||||
func = function(name)
|
||||
mail.show_inbox(name)
|
||||
end
|
||||
})
|
|
@ -0,0 +1,694 @@
|
|||
-- refactor these to some proper management thing
|
||||
selected_idxs = {
|
||||
messages = {},
|
||||
contacts = {},
|
||||
to = {},
|
||||
cc = {},
|
||||
bcc = {},
|
||||
}
|
||||
message_drafts = {}
|
||||
|
||||
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[6,0.10;2,0.5;new;New]
|
||||
button[6,0.95;2,0.5;read;Read]
|
||||
button[6,1.70;2,0.5;reply;Reply]
|
||||
button[6,2.45;2,0.5;replyall;Reply All]
|
||||
button[6,3.20;2,0.5;forward;Forward]
|
||||
button[6,3.95;2,0.5;delete;Delete]
|
||||
button[6,4.82;2,0.5;markread;Mark Read]
|
||||
button[6,5.55;2,0.5;markunread;Mark Unread]
|
||||
button[6,6.55;2,0.5;contacts;Contacts]
|
||||
button[6,7.40;2,0.5;about;About]
|
||||
button_exit[6,8.45;2,0.5;quit;Close]
|
||||
|
||||
tablecolumns[color;text;text]
|
||||
table[0,0;5.75,9;messages;#999,From,Subject]]
|
||||
|
||||
mail.contacts_formspec = "size[8,9;]" .. theme .. [[
|
||||
button[6,0.10;2,0.5;new;New]
|
||||
button[6,0.85;2,0.5;edit;Edit]
|
||||
button[6,1.60;2,0.5;delete;Delete]
|
||||
button[6,8.25;2,0.5;back;Back]
|
||||
tablecolumns[color;text;text]
|
||||
table[0,0;5.75,9;contacts;#999,Name,Note]]
|
||||
|
||||
mail.select_contact_formspec = "size[8,9;]" .. theme .. [[
|
||||
tablecolumns[color;text;text]
|
||||
table[0,0;3.5,9;contacts;#999,Name,Note%s]
|
||||
button[3.55,2.00;1.75,0.5;toadd;→ Add]
|
||||
button[3.55,2.75;1.75,0.5;toremove;← Remove]
|
||||
button[3.55,6.00;1.75,0.5;ccadd;→ Add]
|
||||
button[3.55,6.75;1.75,0.5;ccremove;← Remove]
|
||||
tablecolumns[color;text;text]
|
||||
table[5.15,0.0;2.75,4.5;to;#999,TO:,Note%s]
|
||||
tablecolumns[color;text;text]
|
||||
table[5.15,4.6;2.75,4.5;cc;#999,CC:,Note%s]
|
||||
button[3.55,8.25;1.75,0.5;back;Back]
|
||||
]]
|
||||
|
||||
|
||||
function mail.show_about(name)
|
||||
local formspec = [[
|
||||
size[8,5;]
|
||||
button[7.25,0;0.75,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)
|
||||
|
||||
message_drafts[name] = nil
|
||||
|
||||
if messages[1] then
|
||||
for _, message in ipairs(messages) do
|
||||
mail.ensure_new_format(message, name)
|
||||
if message.unread then
|
||||
if not mail.player_in_list(name, message.to) then
|
||||
formspec[#formspec + 1] = ",#FFD788"
|
||||
else
|
||||
formspec[#formspec + 1] = ",#FFD700"
|
||||
end
|
||||
else
|
||||
if not mail.player_in_list(name, message.to) then
|
||||
formspec[#formspec + 1] = ",#CCCCDD"
|
||||
else
|
||||
formspec[#formspec + 1] = ","
|
||||
end
|
||||
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_idxs.messages[name] then
|
||||
formspec[#formspec + 1] = ";"
|
||||
formspec[#formspec + 1] = tostring(selected_idxs.messages[name] + 1)
|
||||
end
|
||||
formspec[#formspec + 1] = "]"
|
||||
else
|
||||
formspec[#formspec + 1] = "]label[2.25,4.5;No mail]"
|
||||
end
|
||||
minetest.show_formspec(name, "mail:inbox", table.concat(formspec, ""))
|
||||
end
|
||||
|
||||
function mail.show_contacts(name)
|
||||
local formspec = mail.contacts_formspec .. mail.compile_contact_list(name, selected_idxs.contacts[name])
|
||||
minetest.show_formspec(name, "mail:contacts", formspec)
|
||||
end
|
||||
|
||||
function mail.show_edit_contact(name, contact_name, note, illegal_name_hint)
|
||||
local formspec = [[
|
||||
size[6,7]
|
||||
button[4,6.25;2,0.5;back;Back]
|
||||
field[0.25,0.5;4,1;name;Player name:;%s]
|
||||
textarea[0.25,1.6;4,6.25;note;Note:;%s]
|
||||
button[4,0.10;2,1;save;Save]
|
||||
]]
|
||||
if illegal_name_hint == "collision" then
|
||||
formspec = formspec .. [[
|
||||
label[4,1;That name]
|
||||
label[4,1.5;is already in]
|
||||
label[4,2;your contacts.]
|
||||
]]
|
||||
elseif illegal_name_hint == "empty" then
|
||||
formspec = formspec .. [[
|
||||
label[4,1;The contact]
|
||||
label[4,1.5;name cannot]
|
||||
label[4,2;be empty.]
|
||||
]]
|
||||
end
|
||||
formspec = formspec .. theme
|
||||
formspec = string.format(formspec,
|
||||
minetest.formspec_escape(contact_name or ""),
|
||||
minetest.formspec_escape(note or ""))
|
||||
minetest.show_formspec(name, "mail:editcontact", formspec)
|
||||
end
|
||||
|
||||
function mail.show_select_contact(name, to, cc)
|
||||
local formspec = mail.select_contact_formspec
|
||||
local contacts = mail.compile_contact_list(name, selected_idxs.contacts[name])
|
||||
|
||||
-- compile lists
|
||||
if to then
|
||||
to = mail.compile_contact_list(name, selected_idxs.to[name], to)
|
||||
else
|
||||
to = ""
|
||||
end
|
||||
if cc then
|
||||
cc = mail.compile_contact_list(name, selected_idxs.cc[name], cc)
|
||||
else
|
||||
cc = ""
|
||||
end
|
||||
--[[if bcc then
|
||||
bcc = table.concat(mail.compile_contact_list(name, selected_idxs.bcc[name], bcc)
|
||||
else
|
||||
bcc = ""
|
||||
end]]--
|
||||
formspec = string.format(formspec, contacts, to, cc)--, bcc()
|
||||
minetest.show_formspec(name, "mail:selectcontact", formspec)
|
||||
end
|
||||
|
||||
function mail.compile_contact_list(name, selected, playernames)
|
||||
-- TODO: refactor this - not just compiles *a* list, but *the* list for the contacts screen (too inflexible)
|
||||
local formspec = {}
|
||||
local contacts = mail.getContacts(name)
|
||||
|
||||
if playernames == nil then
|
||||
local length = 0
|
||||
for k, contact, i, l in mail.pairsByKeys(contacts) do
|
||||
if i == 1 then length = l end
|
||||
formspec[#formspec + 1] = ","
|
||||
formspec[#formspec + 1] = ","
|
||||
formspec[#formspec + 1] = minetest.formspec_escape(contact.name)
|
||||
formspec[#formspec + 1] = ","
|
||||
local note = contact.note
|
||||
-- display an ellipsis if the note spans multiple lines
|
||||
local idx = string.find(note, '\n')
|
||||
if idx ~= nil then
|
||||
note = string.sub(note, 1, idx-1) .. ' ...'
|
||||
end
|
||||
formspec[#formspec + 1] = minetest.formspec_escape(note)
|
||||
if type(selected) == "string" then
|
||||
if string.lower(selected) == k then
|
||||
selected = i
|
||||
end
|
||||
end
|
||||
end
|
||||
if length > 0 then
|
||||
if selected and type(selected) == "number" then
|
||||
formspec[#formspec + 1] = ";"
|
||||
formspec[#formspec + 1] = tostring(selected + 1)
|
||||
end
|
||||
formspec[#formspec + 1] = "]"
|
||||
else
|
||||
formspec[#formspec + 1] = "]label[2,4.5;No contacts]"
|
||||
end
|
||||
else
|
||||
if type(playernames) == "string" then
|
||||
playernames = mail.parse_player_list(playernames)
|
||||
end
|
||||
for i,c in ipairs(playernames) do
|
||||
formspec[#formspec + 1] = ","
|
||||
formspec[#formspec + 1] = ","
|
||||
formspec[#formspec + 1] = minetest.formspec_escape(c)
|
||||
formspec[#formspec + 1] = ","
|
||||
if contacts[string.lower(c)] == nil then
|
||||
formspec[#formspec + 1] = ""
|
||||
else
|
||||
local note = contacts[string.lower(c)].note
|
||||
-- display an ellipsis if the note spans multiple lines
|
||||
local idx = string.find(note, '\n')
|
||||
if idx ~= nil then
|
||||
note = string.sub(note, 1, idx-1) .. ' ...'
|
||||
end
|
||||
formspec[#formspec + 1] = minetest.formspec_escape(note)
|
||||
end
|
||||
if not selected then
|
||||
if type(selected) == "string" then
|
||||
if string.lower(selected) == string.lower(c) then
|
||||
selected = i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if #playernames > 0 and selected and type(selected) == "number" then
|
||||
formspec[#formspec + 1] = ";"
|
||||
formspec[#formspec + 1] = tostring(selected + 1)
|
||||
end
|
||||
formspec[#formspec + 1] = "]"
|
||||
end
|
||||
return table.concat(formspec, "")
|
||||
end
|
||||
|
||||
function mail.show_message(name, msgnumber)
|
||||
local messages = mail.getMessages(name)
|
||||
local message = messages[msgnumber]
|
||||
local formspec = [[
|
||||
size[8,9]
|
||||
button[7.25,0;0.75,0.5;back;X]
|
||||
label[0,0;From: %s]
|
||||
label[0,0.4;To: %s]
|
||||
label[0,0.8;CC: %s]
|
||||
label[0,1.3;Subject: %s]
|
||||
textarea[0.25,1.8;8,7.8;body;;%s]
|
||||
button[0,8.5;2,1;reply;Reply]
|
||||
button[2,8.5;2,1;replyall;Reply All]
|
||||
button[4,8.5;2,1;forward;Forward]
|
||||
button[6,8.5;2,1;delete;Delete]
|
||||
]] .. theme
|
||||
|
||||
local from = minetest.formspec_escape(message.sender)
|
||||
local to = minetest.formspec_escape(message.to)
|
||||
local cc = minetest.formspec_escape(message.cc)
|
||||
local subject = minetest.formspec_escape(message.subject)
|
||||
local body = minetest.formspec_escape(message.body)
|
||||
formspec = string.format(formspec, from, to, cc, subject, body)
|
||||
|
||||
if message.unread then
|
||||
message.unread = false
|
||||
mail.setMessages(name, messages)
|
||||
end
|
||||
|
||||
minetest.show_formspec(name,"mail:message",formspec)
|
||||
end
|
||||
|
||||
function mail.show_compose(name, defaultto, defaultsubj, defaultbody, defaultcc, defaultbcc)
|
||||
local formspec = [[
|
||||
size[8,9]
|
||||
button[0,0;1,1;tocontacts;To:]
|
||||
field[1.1,0.3;3.2,1;to;;%s]
|
||||
button[4,0;1,1;cccontacts;CC:]
|
||||
field[5.1,0.3;3.1,1;cc;;%s]
|
||||
button[4,0.75;1,1;bcccontacts;BCC:]
|
||||
field[5.1,1.05;3.1,1;bcc;;%s]
|
||||
field[0.25,2;8,1;subject;Subject:;%s]
|
||||
textarea[0.25,2.5;8,6;body;;%s]
|
||||
button[0.5,8.5;3,1;cancel;Cancel]
|
||||
button[4.5,8.5;3,1;send;Send]
|
||||
]] .. theme
|
||||
|
||||
defaultto = defaultto or ""
|
||||
defaultsubj = defaultsubj or ""
|
||||
defaultbody = defaultbody or ""
|
||||
defaultcc = defaultcc or ""
|
||||
defaultbcc = defaultbcc or ""
|
||||
|
||||
formspec = string.format(formspec,
|
||||
minetest.formspec_escape(defaultto),
|
||||
minetest.formspec_escape(defaultcc),
|
||||
minetest.formspec_escape(defaultbcc),
|
||||
minetest.formspec_escape(defaultsubj),
|
||||
minetest.formspec_escape(defaultbody))
|
||||
|
||||
minetest.show_formspec(name, "mail:compose", formspec)
|
||||
end
|
||||
|
||||
function mail.reply(name, message)
|
||||
mail.ensure_new_format(message)
|
||||
local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body
|
||||
mail.show_compose(name, message.sender, "Re: "..message.subject, replyfooter)
|
||||
end
|
||||
|
||||
function mail.replyall(name, message)
|
||||
mail.ensure_new_format(message)
|
||||
local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body
|
||||
|
||||
-- new recipients are the sender plus the original recipients, minus ourselves
|
||||
local recipients = message.to or ""
|
||||
if message.sender ~= nil then
|
||||
recipients = message.sender .. ", " .. recipients
|
||||
end
|
||||
recipients = mail.parse_player_list(recipients)
|
||||
for k,v in pairs(recipients) do
|
||||
if v == name then
|
||||
table.remove(recipients, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
recipients = mail.concat_player_list(recipients)
|
||||
|
||||
-- new CC is old CC minus ourselves
|
||||
local cc = mail.parse_player_list(message.cc)
|
||||
for k,v in pairs(cc) do
|
||||
if v == name then
|
||||
table.remove(cc, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
cc = mail.concat_player_list(cc)
|
||||
|
||||
mail.show_compose(name, recipients, "Re: "..message.subject, replyfooter, cc)
|
||||
end
|
||||
|
||||
function mail.forward(name, message)
|
||||
local fwfooter = "Type your message here.\n\n--Original message follows--\n" .. (message.body or "")
|
||||
mail.show_compose(name, "", "Fw: " .. (message.subject or ""), fwfooter)
|
||||
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_idxs.messages[name] = evt.row - 1
|
||||
if evt.type == "DCL" and messages[selected_idxs.messages[name]] then
|
||||
mail.show_message(name, selected_idxs.messages[name])
|
||||
end
|
||||
return true
|
||||
end
|
||||
if fields.read then
|
||||
if messages[selected_idxs.messages[name]] then
|
||||
mail.show_message(name, selected_idxs.messages[name])
|
||||
end
|
||||
|
||||
elseif fields.delete then
|
||||
if messages[selected_idxs.messages[name]] then
|
||||
table.remove(messages, selected_idxs.messages[name])
|
||||
mail.setMessages(name, messages)
|
||||
end
|
||||
|
||||
mail.show_inbox(name)
|
||||
elseif fields.reply and messages[selected_idxs.messages[name]] then
|
||||
local message = messages[selected_idxs.messages[name]]
|
||||
mail.reply(name, message)
|
||||
|
||||
elseif fields.replyall and messages[selected_idxs.messages[name]] then
|
||||
local message = messages[selected_idxs.messages[name]]
|
||||
mail.replyall(name, message)
|
||||
|
||||
elseif fields.forward and messages[selected_idxs.messages[name]] then
|
||||
local message = messages[selected_idxs.messages[name]]
|
||||
mail.forward(name, message)
|
||||
|
||||
elseif fields.markread then
|
||||
if messages[selected_idxs.messages[name]] then
|
||||
messages[selected_idxs.messages[name]].unread = false
|
||||
-- set messages immediately, so it shows up already when updating the inbox
|
||||
mail.setMessages(name, messages)
|
||||
end
|
||||
mail.show_inbox(name)
|
||||
return true
|
||||
|
||||
elseif fields.markunread then
|
||||
if messages[selected_idxs.messages[name]] then
|
||||
messages[selected_idxs.messages[name]].unread = true
|
||||
-- set messages immediately, so it shows up already when updating the inbox
|
||||
mail.setMessages(name, messages)
|
||||
end
|
||||
mail.show_inbox(name)
|
||||
return true
|
||||
|
||||
elseif fields.new then
|
||||
mail.show_compose(name)
|
||||
|
||||
elseif fields.contacts then
|
||||
mail.show_contacts(name)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
return true -- don't uselessly set messages
|
||||
elseif fields.reply then
|
||||
local message = messages[selected_idxs.messages[name]]
|
||||
mail.reply(name, message)
|
||||
elseif fields.replyall then
|
||||
local message = messages[selected_idxs.messages[name]]
|
||||
mail.replyall(name, message)
|
||||
elseif fields.forward then
|
||||
local message = messages[selected_idxs.messages[name]]
|
||||
mail.forward(name, message.subject)
|
||||
elseif fields.delete then
|
||||
if messages[selected_idxs.messages[name]] then
|
||||
table.remove(messages,selected_idxs.messages[name])
|
||||
mail.setMessages(name, messages)
|
||||
end
|
||||
mail.show_inbox(name)
|
||||
end
|
||||
return true
|
||||
|
||||
elseif formname == "mail:compose" then
|
||||
local name = player:get_player_name()
|
||||
if fields.send then
|
||||
mail.send({
|
||||
from = name,
|
||||
to = fields.to,
|
||||
cc = fields.cc,
|
||||
bcc = fields.bcc,
|
||||
subject = fields.subject,
|
||||
body = fields.body,
|
||||
})
|
||||
local contacts = mail.getContacts(name)
|
||||
local recipients = mail.parse_player_list(fields.to)
|
||||
local changed = false
|
||||
for _,v in pairs(recipients) do
|
||||
if contacts[string.lower(v)] == nil then
|
||||
contacts[string.lower(v)] = {
|
||||
name = v,
|
||||
note = "",
|
||||
}
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
if changed then
|
||||
mail.setContacts(name, contacts)
|
||||
end
|
||||
|
||||
minetest.after(0.5, function()
|
||||
mail.show_inbox(name)
|
||||
end)
|
||||
|
||||
elseif fields.tocontacts or fields.cccontacts or fields.bcccontacts then
|
||||
message_drafts[name] = {
|
||||
to = fields.to,
|
||||
cc = fields.cc,
|
||||
bcc = fields.bcc,
|
||||
subject = fields.subject,
|
||||
body = fields.body,
|
||||
}
|
||||
mail.show_select_contact(name, fields.to, fields.cc, fields.bcc)
|
||||
elseif fields.cancel then
|
||||
message_drafts[name] = nil
|
||||
mail.show_inbox(name)
|
||||
end
|
||||
return true
|
||||
|
||||
elseif formname == "mail:selectcontact" then
|
||||
local name = player:get_player_name()
|
||||
local contacts = mail.getContacts(name)
|
||||
local draft = message_drafts[name]
|
||||
|
||||
-- get indexes for fields with selected rows
|
||||
-- execute their default button's actions if double clicked
|
||||
for k,action in pairs({
|
||||
contacts = "toadd",
|
||||
to = "toremove",
|
||||
cc = "ccremove",
|
||||
bcc = "bccremove"
|
||||
}) do
|
||||
if fields[k] then
|
||||
local evt = minetest.explode_table_event(fields[k])
|
||||
selected_idxs[k][name] = evt.row - 1
|
||||
if evt.type == "DCL" and selected_idxs[k][name] then
|
||||
fields[action] = true
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local update = false
|
||||
-- add
|
||||
for _,v in pairs({"to","cc","bcc"}) do
|
||||
if fields[v.."add"] then
|
||||
update = true
|
||||
if selected_idxs.contacts[name] then
|
||||
for k, contact, i in mail.pairsByKeys(contacts) do
|
||||
if k == selected_idxs.contacts[name] or i == selected_idxs.contacts[name] then
|
||||
local list = mail.parse_player_list(draft[v])
|
||||
list[#list+1] = contact.name
|
||||
selected_idxs[v][name] = #list
|
||||
draft[v] = mail.concat_player_list(list)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- remove
|
||||
for _,v in pairs({"to","cc","bcc"}) do
|
||||
if fields[v.."remove"] then
|
||||
update = true
|
||||
if selected_idxs[v][name] then
|
||||
local list = mail.parse_player_list(draft[v])
|
||||
table.remove(list, selected_idxs[v][name])
|
||||
if #list < selected_idxs[v][name] then
|
||||
selected_idxs[v][name] = #list
|
||||
end
|
||||
draft[v] = mail.concat_player_list(list)
|
||||
end
|
||||
end
|
||||
end
|
||||
if update then
|
||||
mail.show_select_contact(name, draft.to, draft.cc, draft.bcc)
|
||||
return true
|
||||
end
|
||||
|
||||
-- delete old idxs
|
||||
for _,v in ipairs({"contacts","to","cc","bcc"}) do
|
||||
selected_idxs[v][name] = nil
|
||||
end
|
||||
mail.show_compose(name, draft.to, draft.subject, draft.body, draft.cc, draft.bcc)
|
||||
return true
|
||||
|
||||
elseif formname == "mail:contacts" then
|
||||
local name = player:get_player_name()
|
||||
local contacts = mail.getContacts(name)
|
||||
|
||||
if fields.contacts then
|
||||
local evt = minetest.explode_table_event(fields.contacts)
|
||||
for k, _, i in mail.pairsByKeys(contacts) do
|
||||
if i == evt.row - 1 then
|
||||
selected_idxs.contacts[name] = k
|
||||
break
|
||||
end
|
||||
end
|
||||
if evt.type == "DCL" and contacts[selected_idxs.contacts[name]] then
|
||||
mail.show_edit_contact(
|
||||
name,
|
||||
contacts[selected_idxs.contacts[name]].name,
|
||||
contacts[selected_idxs.contacts[name]].note
|
||||
)
|
||||
end
|
||||
return true
|
||||
elseif fields.new then
|
||||
selected_idxs.contacts[name] = "#NEW#"
|
||||
mail.show_edit_contact(name, "", "")
|
||||
elseif fields.edit and selected_idxs.contacts[name] and contacts[selected_idxs.contacts[name]] then
|
||||
mail.show_edit_contact(
|
||||
name,
|
||||
contacts[selected_idxs.contacts[name]].name,
|
||||
contacts[selected_idxs.contacts[name]].note
|
||||
)
|
||||
elseif fields.delete then
|
||||
if contacts[selected_idxs.contacts[name]] then
|
||||
-- delete the contact and set the selected to the next in the list,
|
||||
-- except if it was the last. Then determine the new last
|
||||
local found = false
|
||||
local last = nil
|
||||
for k in mail.pairsByKeys(contacts) do
|
||||
if found then
|
||||
selected_idxs.contacts[name] = k
|
||||
break
|
||||
elseif k == selected_idxs.contacts[name] then
|
||||
contacts[selected_idxs.contacts[name]] = nil
|
||||
selected_idxs.contacts[name] = nil
|
||||
found = true
|
||||
else
|
||||
last = k
|
||||
end
|
||||
end
|
||||
if found and not selected_idxs.contacts[name] then
|
||||
-- was the last in the list, so take the previous (new last)
|
||||
selected_idxs.contacts[name] = last
|
||||
end
|
||||
|
||||
mail.setContacts(name, contacts)
|
||||
end
|
||||
|
||||
mail.show_contacts(name)
|
||||
|
||||
elseif fields.back then
|
||||
mail.show_inbox(name)
|
||||
|
||||
end
|
||||
elseif formname == "mail:editcontact" then
|
||||
local name = player:get_player_name()
|
||||
local contacts = mail.getContacts(name)
|
||||
|
||||
if fields.save then
|
||||
if selected_idxs.contacts[name] and selected_idxs.contacts[name] ~= "#NEW#" then
|
||||
local contact = contacts[selected_idxs.contacts[name]]
|
||||
if selected_idxs.contacts[name] ~= string.lower(fields.name) then
|
||||
-- name changed!
|
||||
if #fields.name == 0 then
|
||||
mail.show_edit_contact(name, contact.name, fields.note, "empty")
|
||||
return true
|
||||
elseif contacts[string.lower(fields.name)] ~= nil then
|
||||
mail.show_edit_contact(name, contact.name, fields.note, "collision")
|
||||
return true
|
||||
else
|
||||
contacts[string.lower(fields.name)] = contact
|
||||
contacts[selected_idxs.contacts[name]] = nil
|
||||
end
|
||||
end
|
||||
contact.name = fields.name
|
||||
contact.note = fields.note
|
||||
else
|
||||
local contact = {
|
||||
name = fields.name,
|
||||
note = fields.note,
|
||||
}
|
||||
contacts[string.lower(contact.name)] = contact
|
||||
end
|
||||
mail.setContacts(name, contacts)
|
||||
mail.show_contacts(name)
|
||||
|
||||
elseif fields.back then
|
||||
mail.show_contacts(name)
|
||||
|
||||
end
|
||||
|
||||
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
|
|
@ -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
|
|
@ -0,0 +1,67 @@
|
|||
mail = {
|
||||
|
||||
-- mark webmail fork for other mods
|
||||
fork = "webmail",
|
||||
|
||||
-- api version
|
||||
apiversion = 1.1,
|
||||
|
||||
-- mail directory
|
||||
maildir = minetest.get_worldpath().."/mails",
|
||||
contactsdir = minetest.get_worldpath().."/mails/contacts",
|
||||
|
||||
-- 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 .. "/util/normalize.lua")
|
||||
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")
|
||||
dofile(MP .. "/node.lua")
|
||||
|
||||
-- optional webmail stuff below
|
||||
local http = QoS and QoS(minetest.request_http_api(), 2) or 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)
|
||||
|
||||
mail.handlers = {}
|
||||
dofile(MP .. "/webmail/tan.lua")
|
||||
dofile(MP .. "/webmail/webmail.lua")
|
||||
dofile(MP .. "/webmail/hook.lua")
|
||||
dofile(MP .. "/webmail/handler_auth.lua")
|
||||
dofile(MP .. "/webmail/handler_send.lua")
|
||||
dofile(MP .. "/webmail/handler_messages.lua")
|
||||
dofile(MP .. "/webmail/handler_delete.lua")
|
||||
dofile(MP .. "/webmail/handler_mark_read.lua")
|
||||
dofile(MP .. "/webmail/handler_mark_unread.lua")
|
||||
mail.webmail_init(http, webmail_url, webmail_key)
|
||||
end
|
||||
|
||||
-- migrate storage
|
||||
mail.migrate()
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,50 @@
|
|||
|
||||
-- migrate from mail.db to player-file-based mailbox
|
||||
|
||||
mail.migrate = function()
|
||||
-- create directory, just in case
|
||||
minetest.mkdir(mail.maildir)
|
||||
minetest.mkdir(mail.contactsdir)
|
||||
|
||||
local file = io.open(minetest.get_worldpath().."/mail.db", "r")
|
||||
if file then
|
||||
print("[mail] migrating to new per-player storage")
|
||||
|
||||
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
|
||||
|
||||
|
||||
mail.migrate_contacts = function(playername)
|
||||
local file = io.open(mail.getContactsFile(playername), 'r')
|
||||
if not file then
|
||||
-- file doesn't exist! This is a case for Migrate Man!
|
||||
local messages = mail.getMessages(playername)
|
||||
local contacts = {}
|
||||
|
||||
if messages and not contacts then
|
||||
for _, message in pairs(messages) do
|
||||
mail.ensure_new_format(message)
|
||||
if contacts[string.lower(message.from)] == nil then
|
||||
contacts[string.lower(message.from)] = {
|
||||
name = message.from,
|
||||
note = "",
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
file:close() -- uh, um, nope, let's leave those alone, shall we?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
name = mail
|
||||
description = mail mod
|
||||
optional_depends = unified_inventory,default,xban2,qos
|
|
@ -0,0 +1,143 @@
|
|||
# Blender v2.91.2 OBJ File: 'mailbox.blend'
|
||||
# www.blender.org
|
||||
o Cube.001_Cube
|
||||
v -0.375000 -0.500000 0.437500
|
||||
v 0.375000 0.500000 -0.250000
|
||||
v 0.375000 -0.500000 0.437500
|
||||
v 0.375000 0.500000 0.375000
|
||||
v -0.375000 -0.500000 -0.312500
|
||||
v -0.375000 0.500000 -0.250000
|
||||
v 0.375000 -0.500000 -0.312500
|
||||
v -0.375000 0.500000 0.375000
|
||||
v 0.375000 -0.312500 -0.312500
|
||||
v 0.375000 0.437500 -0.312500
|
||||
v -0.375000 0.437500 -0.312500
|
||||
v -0.375000 -0.312500 -0.312500
|
||||
v 0.375000 0.437500 0.437500
|
||||
v 0.375000 -0.312500 0.437500
|
||||
v -0.375000 -0.312500 0.437500
|
||||
v -0.375000 0.437500 0.437500
|
||||
v -0.437500 0.500000 0.375000
|
||||
v -0.437500 -0.312500 0.375000
|
||||
v -0.437500 -0.312500 -0.250000
|
||||
v -0.437500 0.500000 -0.250000
|
||||
v -0.437500 0.437500 -0.312500
|
||||
v -0.437500 -0.312500 -0.312500
|
||||
v -0.437500 0.437500 0.437500
|
||||
v -0.437500 -0.312500 0.437500
|
||||
v 0.437500 -0.312500 0.375000
|
||||
v 0.437500 -0.312500 -0.250000
|
||||
v 0.437500 0.500000 -0.250000
|
||||
v 0.437500 0.500000 0.375000
|
||||
v 0.437500 -0.312500 -0.312500
|
||||
v 0.437500 0.437500 -0.312500
|
||||
v 0.437500 0.437500 0.437500
|
||||
v 0.437500 -0.312500 0.437500
|
||||
v -0.437500 -0.500000 0.375000
|
||||
v -0.437500 -0.500000 -0.250000
|
||||
v -0.437500 -0.500000 -0.312500
|
||||
v -0.437500 -0.500000 0.437500
|
||||
v 0.437500 -0.500000 0.375000
|
||||
v 0.437500 -0.500000 -0.250000
|
||||
v 0.437500 -0.500000 -0.312500
|
||||
v 0.437500 -0.500000 0.437500
|
||||
vt 0.343750 0.968750
|
||||
vt 0.656250 0.968750
|
||||
vt 0.656250 1.000000
|
||||
vt 0.343750 1.000000
|
||||
vt 0.968750 0.500000
|
||||
vt 0.968750 0.468750
|
||||
vt 1.000000 0.468750
|
||||
vt 1.000000 0.500000
|
||||
vt 0.593750 0.500000
|
||||
vt 0.593750 0.468750
|
||||
vt 0.656250 0.593750
|
||||
vt 0.625000 0.593750
|
||||
vt 0.625000 0.500000
|
||||
vt 0.656250 0.500000
|
||||
vt 0.656250 0.593750
|
||||
vt 0.343750 0.593750
|
||||
vt 0.968750 0.093750
|
||||
vt 0.593750 0.093750
|
||||
vt 0.343750 0.562500
|
||||
vt 0.656250 0.562500
|
||||
vt 1.000000 0.593750
|
||||
vt 0.968750 0.593750
|
||||
vt 0.968750 0.500000
|
||||
vt 1.000000 0.500000
|
||||
vt 0.562500 0.093750
|
||||
vt 0.562500 0.000000
|
||||
vt 0.593750 0.000000
|
||||
vt 0.406250 0.093750
|
||||
vt 0.406250 0.468750
|
||||
vt 0.031250 0.468750
|
||||
vt 0.031250 0.093750
|
||||
vt 0.437500 0.093750
|
||||
vt 0.406250 0.000000
|
||||
vt 0.437500 0.000000
|
||||
vt 0.406250 0.500000
|
||||
vt 0.031250 0.500000
|
||||
vt 0.000000 0.093750
|
||||
vt 0.000000 0.000000
|
||||
vt 0.031250 0.000000
|
||||
vt 0.968750 1.000000
|
||||
vt 1.000000 0.968750
|
||||
vt 0.656250 1.000000
|
||||
vt 0.625000 0.968750
|
||||
vt 1.000000 0.093750
|
||||
vt 0.375000 0.593750
|
||||
vt 0.343750 0.593750
|
||||
vt 0.343750 0.500000
|
||||
vt 0.375000 0.500000
|
||||
vt 0.968750 0.000000
|
||||
vt 1.000000 0.000000
|
||||
vt 0.000000 0.500000
|
||||
vt 0.000000 0.468750
|
||||
vt 0.031250 1.000000
|
||||
vt 0.031250 0.593750
|
||||
vt 0.375000 0.968750
|
||||
vt 0.000000 0.968750
|
||||
vt 0.000000 0.593750
|
||||
vt 0.000000 0.500000
|
||||
vt 0.031250 0.500000
|
||||
vt 0.437500 0.468750
|
||||
vt 0.437500 0.500000
|
||||
vt 0.562500 0.500000
|
||||
vt 0.562500 0.468750
|
||||
vn 0.0000 1.0000 0.0000
|
||||
vn 0.0000 0.7071 -0.7071
|
||||
vn -1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
vn 0.0000 0.0000 1.0000
|
||||
vn 0.0000 0.7071 0.7071
|
||||
vn 1.0000 0.0000 0.0000
|
||||
s off
|
||||
f 2/1/1 4/2/1 28/3/1 27/4/1
|
||||
f 6/5/2 11/6/2 21/7/2 20/8/2
|
||||
f 6/5/2 2/9/2 10/10/2 11/6/2
|
||||
f 19/11/3 22/12/3 35/13/3 34/14/3
|
||||
f 8/15/1 4/2/1 2/1/1 6/16/1
|
||||
f 12/17/4 11/6/4 10/10/4 9/18/4
|
||||
f 8/15/1 6/16/1 20/19/1 17/20/1
|
||||
f 24/21/3 18/22/3 33/23/3 36/24/3
|
||||
f 9/18/4 29/25/4 39/26/4 7/27/4
|
||||
f 14/28/5 13/29/5 16/30/5 15/31/5
|
||||
f 32/32/5 14/28/5 3/33/5 40/34/5
|
||||
f 4/35/6 8/36/6 16/30/6 13/29/6
|
||||
f 15/31/5 24/37/5 36/38/5 1/39/5
|
||||
f 17/40/3 18/22/3 24/21/3 23/41/3
|
||||
f 18/22/3 17/40/3 20/42/3 19/11/3
|
||||
f 19/11/3 20/42/3 21/43/3 22/12/3
|
||||
f 11/6/4 12/17/4 22/44/4 21/7/4
|
||||
f 29/45/7 26/46/7 38/47/7 39/48/7
|
||||
f 22/44/4 12/17/4 5/49/4 35/50/4
|
||||
f 16/30/6 8/36/6 17/51/6 23/52/6
|
||||
f 15/31/5 16/30/5 23/52/5 24/37/5
|
||||
f 26/46/7 27/4/7 28/53/7 25/54/7
|
||||
f 27/4/7 26/46/7 29/45/7 30/55/7
|
||||
f 25/54/7 28/53/7 31/56/7 32/57/7
|
||||
f 25/54/7 32/57/7 40/58/7 37/59/7
|
||||
f 13/29/5 14/28/5 32/32/5 31/60/5
|
||||
f 4/35/6 13/29/6 31/60/6 28/61/6
|
||||
f 10/10/2 2/9/2 27/62/2 30/63/2
|
||||
f 9/18/4 10/10/4 30/63/4 29/25/4
|
|
@ -0,0 +1,14 @@
|
|||
minetest.register_node('mail:mailbox', {
|
||||
description = 'Mail Box',
|
||||
drawtype = 'mesh',
|
||||
mesh = 'mail_mailbox.obj',
|
||||
tiles = {'mail_mailbox_uv.png'},
|
||||
paramtype = 'light',
|
||||
paramtype2 = 'facedir',
|
||||
sunlight_propagates = true,
|
||||
groups = {breakable=1},
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
local name = clicker:get_player_name()
|
||||
mail.show_inbox(name)
|
||||
end
|
||||
})
|
|
@ -0,0 +1,21 @@
|
|||
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,
|
||||
minetest.colorize("#00f529", "(" .. unreadcount .. ") You have mail! Type /mail to read"))
|
||||
|
||||
end
|
||||
end, player:get_player_name())
|
||||
|
||||
mail.migrate_contacts(player:get_player_name())
|
||||
end)
|
|
@ -0,0 +1,87 @@
|
|||
|
||||
-- TODO: maybe local cache?
|
||||
|
||||
function mail.getMailFile(playername)
|
||||
local saneplayername = string.gsub(playername, "[.|/]", "")
|
||||
return mail.maildir .. "/" .. saneplayername .. ".json"
|
||||
end
|
||||
|
||||
function mail.getContactsFile(playername)
|
||||
local saneplayername = string.gsub(playername, "[.|/]", "")
|
||||
return mail.maildir .. "/contacts/" .. saneplayername .. ".json"
|
||||
end
|
||||
|
||||
|
||||
mail.getMessages = function(playername)
|
||||
local messages = mail.read_json_file(mail.getMailFile(playername))
|
||||
if messages then
|
||||
mail.hud_update(playername, messages)
|
||||
end
|
||||
|
||||
return messages
|
||||
end
|
||||
|
||||
mail.setMessages = function(playername, messages)
|
||||
if mail.write_json_file(mail.getMailFile(playername), messages) then
|
||||
mail.hud_update(playername, messages)
|
||||
return true
|
||||
else
|
||||
minetest.log("error","[mail] Save failed - messages may be lost! ("..playername..")")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
mail.getContacts = function(playername)
|
||||
return mail.read_json_file(mail.getContactsFile(playername))
|
||||
end
|
||||
|
||||
function mail.pairsByKeys(t, f)
|
||||
-- http://www.lua.org/pil/19.3.html
|
||||
local a = {}
|
||||
for n in pairs(t) do table.insert(a, n) end
|
||||
table.sort(a, f)
|
||||
local i = 0 -- iterator variable
|
||||
local iter = function() -- iterator function
|
||||
i = i + 1
|
||||
if a[i] == nil then
|
||||
return nil
|
||||
else
|
||||
--return a[i], t[a[i]]
|
||||
-- add the current position and the length for convenience
|
||||
return a[i], t[a[i]], i, #a
|
||||
end
|
||||
end
|
||||
return iter
|
||||
end
|
||||
|
||||
mail.setContacts = function(playername, contacts)
|
||||
if mail.write_json_file(mail.getContactsFile(playername), contacts) then
|
||||
return true
|
||||
else
|
||||
minetest.log("error","[mail] Save failed - contacts may be lost! ("..playername..")")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function mail.read_json_file(path)
|
||||
local file = io.open(path, "r")
|
||||
local content = {}
|
||||
if file then
|
||||
local json = file:read("*a")
|
||||
content = minetest.parse_json(json or "[]") or {}
|
||||
file:close()
|
||||
end
|
||||
return content
|
||||
end
|
||||
|
||||
function mail.write_json_file(path, content)
|
||||
local file = io.open(path,"w")
|
||||
local json = minetest.write_json(content)
|
||||
if file and file:write(json) and file:close() then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
Binary file not shown.
|
@ -0,0 +1,4 @@
|
|||
User,Password
|
||||
-----------------
|
||||
test,enter
|
||||
test2,enter
|
|
@ -0,0 +1,79 @@
|
|||
#!/bin/bash
|
||||
|
||||
MINETEST_VERSION=5.2.0
|
||||
|
||||
# prerequisites
|
||||
jq --version || exit 1
|
||||
curl --version || exit 1
|
||||
|
||||
# ensure proper current directory
|
||||
CWD=$(dirname $0)
|
||||
cd ${CWD}
|
||||
|
||||
# setup
|
||||
unset use_proxy
|
||||
unset http_proxy
|
||||
unset https_proxy
|
||||
unset HTTP_PROXY
|
||||
unset HTTPS_PROXY
|
||||
|
||||
# run mail-server
|
||||
docker pull minetestmail/mail
|
||||
docker run --name mail --rm \
|
||||
-e WEBMAILKEY=myserverkey \
|
||||
-e WEBMAIL_DEBUG=true \
|
||||
--network host \
|
||||
minetestmail/mail &
|
||||
|
||||
# wait for startup
|
||||
bash -c 'while !</dev/tcp/localhost/8080; do sleep 1; done;'
|
||||
|
||||
# start minetest with mail mod
|
||||
docker pull registry.gitlab.com/minetest/minetest/server:${MINETEST_VERSION}
|
||||
docker run --rm --name minetest \
|
||||
-u root:root \
|
||||
-v $(pwd)/minetest.conf:/etc/minetest/minetest.conf:ro \
|
||||
-v $(pwd)/world.mt:/root/.minetest/worlds/world/world.mt \
|
||||
-v $(pwd)/auth.sqlite:/root/.minetest/worlds/world/auth.sqlite \
|
||||
-v $(pwd)/../:/root/.minetest/worlds/world/worldmods/mail \
|
||||
-v $(pwd)/test_mod:/root/.minetest/worlds/world/worldmods/mail_test \
|
||||
-e use_proxy=false \
|
||||
-e http_proxy= \
|
||||
-e HTTP_PROXY= \
|
||||
--network host \
|
||||
registry.gitlab.com/minetest/minetest/server:${MINETEST_VERSION} &
|
||||
|
||||
# prepare cleanup
|
||||
function cleanup {
|
||||
# cleanup
|
||||
docker stop mail
|
||||
docker stop minetest
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
# wait for startup
|
||||
sleep 5
|
||||
|
||||
# Execute calls against mail-server
|
||||
|
||||
# login
|
||||
LOGIN_DATA='{"username":"test","password":"enter"}'
|
||||
RES=$(curl --data "${LOGIN_DATA}" -H "Content-Type: application/json" "http://127.0.0.1:8080/api/login")
|
||||
echo Login response: $RES
|
||||
SUCCESS=$(echo $RES | jq -r .success)
|
||||
TOKEN=$(echo $RES | jq -r .token)
|
||||
|
||||
# login succeeded
|
||||
test "$SUCCESS" == "true" || exit 1
|
||||
# token extracted
|
||||
test -n "$TOKEN" || exit 1
|
||||
|
||||
# fetch mails
|
||||
RES=$(curl -H "Authorization: ${TOKEN}" "http://127.0.0.1:8080/api/inbox")
|
||||
echo Mailbox: ${RES}
|
||||
|
||||
# inbox count is 1
|
||||
test "$(echo $RES | jq '. | length')" == "1" || exit 1
|
||||
|
||||
echo "Test complete!"
|
|
@ -0,0 +1,4 @@
|
|||
secure.http_mods = mail
|
||||
webmail.url = http://localhost:8080
|
||||
webmail.key = myserverkey
|
||||
webmail.disallow_banned_players = true
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
docker run --rm -it \
|
||||
-u root:root \
|
||||
-v $(pwd)/minetest.conf:/etc/minetest/minetest.conf \
|
||||
-v $(pwd)/auth.sqlite:/root/.minetest/worlds/world/auth.sqlite \
|
||||
-v $(pwd)/../:/root/.minetest/worlds/world/worldmods/mail_mod \
|
||||
-v mail_world:/root/.minetest/worlds/world/ \
|
||||
--network host \
|
||||
registry.gitlab.com/minetest/minetest/server:5.2.0
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
minetest.log("warning", "[TEST] integration-test enabled!")
|
||||
|
||||
minetest.register_on_mods_loaded(function()
|
||||
minetest.log("warning", "[TEST] starting tests")
|
||||
mail.send("spammer", "test", "subject", "body");
|
||||
end)
|
|
@ -0,0 +1,2 @@
|
|||
name = mail_test
|
||||
depends = mail
|
|
@ -0,0 +1,6 @@
|
|||
enable_damage = true
|
||||
creative_mode = false
|
||||
gameid = minetest
|
||||
auth_backend = sqlite3
|
||||
backend = sqlite3
|
||||
player_backend = sqlite3
|
Binary file not shown.
After Width: | Height: | Size: 270 B |
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
|
@ -0,0 +1,91 @@
|
|||
-- bi-directional http-channel
|
||||
-- with long-poll GET and POST on the same URL
|
||||
|
||||
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 data then
|
||||
for _,listener in pairs(recv_listeners) do
|
||||
if #data > 0 then
|
||||
-- array received
|
||||
for _, entry in ipairs(data) do
|
||||
listener(entry)
|
||||
end
|
||||
else
|
||||
-- single item received
|
||||
listener(data)
|
||||
end
|
||||
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
|
||||
|
||||
http.fetch({
|
||||
url = url,
|
||||
extra_headers = post_headers,
|
||||
timeout = timeout,
|
||||
post_data = minetest.write_json(data)
|
||||
}, function()
|
||||
-- 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
|
|
@ -0,0 +1,60 @@
|
|||
--[[
|
||||
return the field normalized (comma separated, single space)
|
||||
and add individual player names to recipient list
|
||||
--]]
|
||||
function mail.normalize_players_and_add_recipients(field, recipients)
|
||||
local order = mail.parse_player_list(field)
|
||||
for _, c in ipairs(order) do
|
||||
if recipients[string.lower(c)] == nil then
|
||||
recipients[string.lower(c)] = c
|
||||
end
|
||||
end
|
||||
return mail.concat_player_list(order)
|
||||
end
|
||||
|
||||
|
||||
function mail.parse_player_list(field)
|
||||
if not field then
|
||||
return {}
|
||||
end
|
||||
|
||||
local separator = ", "
|
||||
local pattern = "([^" .. separator .. "]+)"
|
||||
|
||||
-- get individual players
|
||||
local player_set = {}
|
||||
local order = {}
|
||||
field:gsub(pattern, function(c)
|
||||
if player_set[string.lower(c)] == nil then
|
||||
player_set[string.lower(c)] = c
|
||||
order[#order+1] = c
|
||||
end
|
||||
end)
|
||||
|
||||
return order
|
||||
end
|
||||
|
||||
function mail.concat_player_list(order)
|
||||
-- turn list of players back into normalized string
|
||||
return table.concat(order, ", ")
|
||||
end
|
||||
|
||||
function mail.player_in_list(name, list)
|
||||
list = list or {}
|
||||
if type(list) == "string" then
|
||||
list = mail.parse_player_list(list)
|
||||
end
|
||||
for _, c in pairs(list) do
|
||||
if name == c then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function mail.ensure_new_format(message, name)
|
||||
if message.to == nil then
|
||||
message.to = name
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
local has_xban2_mod = minetest.get_modpath("xban2")
|
||||
|
||||
-- auth request from webmail
|
||||
function mail.handlers.auth(auth)
|
||||
local handler = minetest.get_auth_handler()
|
||||
minetest.log("action", "[webmail] auth: " .. auth.name)
|
||||
|
||||
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.name)
|
||||
if xbanentry and xbanentry.banned then
|
||||
banned = true
|
||||
message = "Banned!"
|
||||
end
|
||||
end
|
||||
|
||||
if not banned then
|
||||
-- check tan
|
||||
local tan = mail.tan[auth.name]
|
||||
if tan ~= nil then
|
||||
success = tan == auth.password
|
||||
end
|
||||
|
||||
-- check auth
|
||||
if not success then
|
||||
local entry = handler.get_auth(auth.name)
|
||||
if entry and minetest.check_password_entry(auth.name, entry.password, auth.password) then
|
||||
success = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mail.channel.send({
|
||||
type = "auth",
|
||||
data = {
|
||||
name = auth.name,
|
||||
success = success,
|
||||
message = message
|
||||
}
|
||||
})
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
-- remove mail
|
||||
function mail.handlers.delete(playername, index)
|
||||
local messages = mail.getMessages(playername)
|
||||
if messages[index] then
|
||||
table.remove(messages, index)
|
||||
end
|
||||
mail.setMessages(playername, messages)
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
-- mark mail as read
|
||||
function mail.handlers.mark_read(playername, index)
|
||||
local messages = mail.getMessages(playername)
|
||||
if messages[index] then
|
||||
messages[index].unread = false
|
||||
end
|
||||
mail.setMessages(playername, messages)
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
-- mark mail as unread
|
||||
function mail.handlers.mark_unread(playername, index)
|
||||
local messages = mail.getMessages(playername)
|
||||
if messages[index] then
|
||||
messages[index].unread = true
|
||||
end
|
||||
mail.setMessages(playername, messages)
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
-- get player messages request from webmail
|
||||
function mail.handlers.messages(playername)
|
||||
local messages = mail.getMessages(playername)
|
||||
mail.channel.send({
|
||||
type = "player-messages",
|
||||
playername = playername,
|
||||
data = messages
|
||||
})
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
-- send request from webmail
|
||||
function mail.handlers.send(sendmail)
|
||||
-- send mail from webclient
|
||||
minetest.log("action", "[webmail] sending mail from webclient: " .. sendmail.from .. " -> " .. sendmail.to)
|
||||
mail.send(sendmail)
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
function mail.webmail_send_hook(m)
|
||||
mail.channel.send({
|
||||
type = "new-message",
|
||||
data = m
|
||||
})
|
||||
end
|
||||
|
||||
mail.register_on_receive(mail.webmail_send_hook)
|
|
@ -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)
|
|
@ -0,0 +1,31 @@
|
|||
local MP = minetest.get_modpath(minetest.get_current_modname())
|
||||
local Channel = dofile(MP .. "/util/channel.lua")
|
||||
|
||||
|
||||
function mail.webmail_init(http, url, key)
|
||||
mail.channel = Channel(http, url .. "/api/minetest/channel", {
|
||||
extra_headers = { "webmailkey: " .. key }
|
||||
})
|
||||
|
||||
mail.channel.receive(function(data)
|
||||
if data.type == "auth" then
|
||||
mail.handlers.auth(data.data)
|
||||
|
||||
elseif data.type == "send" then
|
||||
mail.handlers.send(data.data) -- { src, dst, subject, body }
|
||||
|
||||
elseif data.type == "delete-mail" then
|
||||
mail.handlers.delete(data.playername, data.index) -- index 1-based
|
||||
|
||||
elseif data.type == "mark-mail-read" then
|
||||
mail.handlers.mark_read(data.playername, data.index) -- index 1-based
|
||||
|
||||
elseif data.type == "mark-mail-unread" then
|
||||
mail.handlers.mark_unread(data.playername, data.index) -- index 1-based
|
||||
|
||||
elseif data.type == "player-messages" then
|
||||
mail.handlers.messages(data.data)
|
||||
|
||||
end
|
||||
end)
|
||||
end
|
Loading…
Reference in New Issue