Загрузить файлы в «/»
This commit is contained in:
commit
e67e2900fc
152
api.lua
Normal file
152
api.lua
Normal file
@ -0,0 +1,152 @@
|
||||
-- see: mail.md
|
||||
|
||||
-- translation
|
||||
local S = mail.S
|
||||
|
||||
local f = string.format
|
||||
|
||||
mail.registered_on_receives = {}
|
||||
function mail.register_on_receive(func)
|
||||
mail.registered_on_receives[#mail.registered_on_receives + 1] = func
|
||||
end
|
||||
|
||||
mail.registered_on_player_receives = {}
|
||||
function mail.register_on_player_receive(func)
|
||||
table.insert(mail.registered_on_player_receives, func)
|
||||
end
|
||||
|
||||
mail.registered_recipient_handlers = {}
|
||||
function mail.register_recipient_handler(func)
|
||||
table.insert(mail.registered_recipient_handlers, func)
|
||||
end
|
||||
|
||||
function mail.send(m)
|
||||
if type(m.from) ~= "string" then return false, "'from' is not a string" end
|
||||
if type(m.to or "") ~= "string" then return false, "'to' is not a string" end
|
||||
if type(m.cc or "") ~= "string" then return false, "'cc' is not a string" end
|
||||
if type(m.bcc or "") ~= "string" then return false, "'bcc' is not a string" end
|
||||
if type(m.subject or "") ~= "string" then return false, "'subject' is not a string" end
|
||||
if type(m.body) ~= "string" then return false, "'body' is not a string" end
|
||||
|
||||
-- defaults
|
||||
m.subject = m.subject or "(No subject)"
|
||||
|
||||
-- normalize to, cc and bcc while compiling a list of all recipients
|
||||
local recipients = {}
|
||||
local undeliverable = {}
|
||||
m.to = mail.concat_player_list(mail.extract_maillists(m.to, m.from))
|
||||
m.to = mail.normalize_players_and_add_recipients(m.from, m.to, recipients, undeliverable)
|
||||
if m.cc then
|
||||
m.cc = mail.concat_player_list(mail.extract_maillists(m.cc, m.from))
|
||||
m.cc = mail.normalize_players_and_add_recipients(mail.from, m.cc, recipients, undeliverable)
|
||||
end
|
||||
if m.bcc then
|
||||
m.bcc = mail.concat_player_list(mail.extract_maillists(m.bcc, m.from))
|
||||
m.bcc = mail.normalize_players_and_add_recipients(m.from, m.bcc, recipients, undeliverable)
|
||||
end
|
||||
|
||||
if next(undeliverable) then -- table is not empty
|
||||
local undeliverable_reason = {S("The mail could not be sent:")}
|
||||
for _, reason in pairs(undeliverable) do
|
||||
table.insert(undeliverable_reason, reason)
|
||||
end
|
||||
return false, table.concat(undeliverable_reason, "\n")
|
||||
elseif not next(recipients) then
|
||||
return false, S("You did not specify any valid recipient.")
|
||||
end
|
||||
|
||||
local extra = {}
|
||||
local extra_log
|
||||
if m.cc then
|
||||
table.insert(extra, "CC: " .. m.cc)
|
||||
end
|
||||
if m.bcc then
|
||||
table.insert(extra, "BCC: " .. m.bcc)
|
||||
end
|
||||
if #extra > 0 then
|
||||
extra_log = f(" (%s)", table.concat(extra, " - "))
|
||||
else
|
||||
extra_log = ""
|
||||
end
|
||||
|
||||
minetest.log("action", f("[mail] %q send mail to %q%s with subject %q and body %q",
|
||||
m.from, m.to, extra_log, m.subject, m.body
|
||||
))
|
||||
|
||||
local id
|
||||
if m.id then
|
||||
mail.delete_mail(m.from, m.id)
|
||||
id = m.id
|
||||
end
|
||||
|
||||
-- form the actual mail
|
||||
local msg = {
|
||||
id = id or mail.new_uuid(),
|
||||
from = m.from,
|
||||
to = m.to,
|
||||
cc = m.cc,
|
||||
bcc = m.bcc,
|
||||
subject = m.subject,
|
||||
body = m.body,
|
||||
time = os.time(),
|
||||
}
|
||||
|
||||
-- add in senders outbox
|
||||
local entry = mail.get_storage_entry(m.from)
|
||||
table.insert(entry.outbox, 1, msg)
|
||||
mail.set_storage_entry(m.from, entry)
|
||||
msg.spam = #mail.check_spam(msg) >= 1
|
||||
|
||||
-- add in every receivers inbox
|
||||
for _, deliver in pairs(recipients) do
|
||||
deliver(msg)
|
||||
end
|
||||
|
||||
for i=1, #mail.registered_on_receives do
|
||||
if mail.registered_on_receives[i](m) then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function mail.save_draft(m)
|
||||
if type(m.from) ~= "string" then return false, "'from' is not a string" end
|
||||
if type(m.to or "") ~= "string" then return false, "'to' is not a string" end
|
||||
if type(m.cc or "") ~= "string" then return false, "'cc' is not a string" end
|
||||
if type(m.bcc or "") ~= "string" then return false, "'bcc' is not a string" end
|
||||
if type(m.subject or "") ~= "string" then return false, "'subject' is not a string" end
|
||||
if type(m.body) ~= "string" then return false, "'body' is not a string" end
|
||||
|
||||
-- defaults
|
||||
m.subject = m.subject or "(No subject)"
|
||||
|
||||
minetest.log("verbose", f("[mail] %q saves draft with subject %q and body %q",
|
||||
m.from, m.subject, m.body
|
||||
))
|
||||
|
||||
-- remove it is an update
|
||||
local id
|
||||
if m.id then
|
||||
mail.delete_mail(m.from, m.id)
|
||||
id = m.id
|
||||
end
|
||||
|
||||
-- add (again ie. update) in sender drafts
|
||||
local entry = mail.get_storage_entry(m.from)
|
||||
table.insert(entry.drafts, 1, {
|
||||
id = id or mail.new_uuid(),
|
||||
from = m.from,
|
||||
to = m.to,
|
||||
cc = m.cc,
|
||||
bcc = m.bcc,
|
||||
subject = m.subject,
|
||||
body = m.body,
|
||||
time = os.time(),
|
||||
})
|
||||
mail.set_storage_entry(m.from, entry)
|
||||
|
||||
return true
|
||||
|
||||
end
|
133
api.md
Normal file
133
api.md
Normal file
@ -0,0 +1,133 @@
|
||||
# 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"
|
||||
}
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
## Sending mail
|
||||
|
||||
```lua
|
||||
local success, error = mail.send({
|
||||
from = "singleplayer",
|
||||
to = "playername",
|
||||
cc = "carbon, copy",
|
||||
bcc = "blind, carbon, copy",
|
||||
subject = "subject line",
|
||||
body = "mail body"
|
||||
})
|
||||
|
||||
-- if "success" is false the error parameter will contain a message
|
||||
```
|
||||
|
||||
# Hooks
|
||||
Generic on-receive mail hook:
|
||||
|
||||
```lua
|
||||
mail.register_on_receive(function(m)
|
||||
-- "m" is an object in the form: "Mail format"
|
||||
end)
|
||||
```
|
||||
|
||||
Player-specific on-receive mail hook:
|
||||
```lua
|
||||
mail.register_on_player_receive(function(player, msg)
|
||||
-- "player" is the name of a recipient; "msg" is a mail object (see "Mail format")
|
||||
end)
|
||||
```
|
||||
|
||||
# Recipient handler
|
||||
Recipient handlers are registered using
|
||||
|
||||
```lua
|
||||
mail.register_recipient_handler(function(sender, name)
|
||||
end)
|
||||
```
|
||||
|
||||
where `name` is the name of a single recipient.
|
||||
|
||||
The recipient handler should return
|
||||
* `nil` if the handler does not handle messages sent to the particular recipient,
|
||||
* `true, player` (where `player` is a string or a list of strings) if the mail should be redirected to `player`,
|
||||
* `true, deliver` if the mail should be delivered by calling `deliver` with the message, or
|
||||
* `false, reason` (where `reason` is optional or, if provided, a string) if the recipient explicitly rejects the mail.
|
||||
|
||||
# Internals
|
||||
|
||||
mod-storage entry for a player (indexed by playername and serialized with json):
|
||||
```lua
|
||||
{
|
||||
|
||||
contacts = {
|
||||
{
|
||||
-- name of the player (unique key in the list)
|
||||
name = "",
|
||||
-- note
|
||||
note = ""
|
||||
},{
|
||||
...
|
||||
}
|
||||
},
|
||||
inbox = {
|
||||
{
|
||||
-- globally unique mail id
|
||||
id = "d6cce35c-487a-458f-bab2-9032c2621f38",
|
||||
-- sending player name
|
||||
from = "",
|
||||
-- receiving player name
|
||||
to = "",
|
||||
-- carbon copy (optional)
|
||||
cc = "playername, playername2",
|
||||
-- blind carbon copy (optional)
|
||||
bcc = "",
|
||||
-- mail subject
|
||||
subject = "",
|
||||
-- mail body
|
||||
body = "",
|
||||
-- timestamp (os.time())
|
||||
time = 1234,
|
||||
-- read-flag (true: player has read the mail, inbox only)
|
||||
read = true,
|
||||
-- spam-flag (true: that mail is noted as a spam)
|
||||
spam = false
|
||||
},{
|
||||
...
|
||||
}
|
||||
},
|
||||
outbox = {
|
||||
-- same format as "inbox"
|
||||
},
|
||||
drafts = {
|
||||
-- same format as "inbox"
|
||||
},
|
||||
trash = {
|
||||
-- same format as "inbox"
|
||||
},
|
||||
lists = {
|
||||
{
|
||||
-- name of the maillist (unique key in the list)
|
||||
name = "",
|
||||
-- description
|
||||
description = "",
|
||||
-- playername list
|
||||
players = {"playername", "playername2"}
|
||||
}
|
||||
},
|
||||
settings = {
|
||||
setting1 = "value",
|
||||
setting2 = true,
|
||||
setting3 = 123
|
||||
}
|
||||
}
|
76
api.spec.lua
Normal file
76
api.spec.lua
Normal file
@ -0,0 +1,76 @@
|
||||
mail.register_recipient_handler(function(_, name)
|
||||
if name:sub(1, 6) == "alias/" then
|
||||
return true, name:sub(7)
|
||||
elseif name == "list/test" then
|
||||
return true, {"alias/player1", "alias/player2"}
|
||||
elseif name == "list/reject" then
|
||||
return false, "It works (?)"
|
||||
end
|
||||
end)
|
||||
mail.update_maillist("player1", {
|
||||
owner = "player1",
|
||||
name = "recursive",
|
||||
desc = "",
|
||||
players = {"@recursive", "player1"},
|
||||
}, "recursive")
|
||||
|
||||
local received_count = {}
|
||||
mail.register_on_player_receive(function(player)
|
||||
received_count[player] = (received_count[player] or 0) + 1
|
||||
end)
|
||||
|
||||
local sent_count = 0
|
||||
mail.register_on_receive(function()
|
||||
sent_count = sent_count+1
|
||||
end)
|
||||
|
||||
local function assert_inbox_count(player_name, count)
|
||||
local entry = mail.get_storage_entry(player_name)
|
||||
assert(entry, player_name .. " has no mail entry")
|
||||
local actual_count = #entry.inbox
|
||||
assert(actual_count == count, ("incorrect mail count: %d expected, got %d"):format(count, actual_count))
|
||||
local player_received = received_count[player_name] or 0
|
||||
assert(player_received == count, ("incorrect receive count: %d expected, got %d"):format(count, player_received))
|
||||
end
|
||||
|
||||
local function assert_send(expected_success, ...)
|
||||
local success, err = mail.send(...)
|
||||
if expected_success then
|
||||
assert(success, ("expected mail to be sent, got error message: %s"):format(err))
|
||||
assert(not err, ("unexpected message after sending mail: %s"):format(err))
|
||||
else
|
||||
assert(not success, "expected mail to be rejected, mail was sent")
|
||||
assert(type(err) == "string", ("expected error message, got datum of type %s"):format(type(err)))
|
||||
end
|
||||
end
|
||||
|
||||
mtt.register("send mail", function(callback)
|
||||
-- local maillists
|
||||
assert_send(true, {from = "player1", to = "@recursive", subject = "hello recursion", body = "blah"})
|
||||
assert_inbox_count("player1", 1)
|
||||
assert(sent_count == 1)
|
||||
|
||||
-- do not allow empty recipients
|
||||
assert_send(false, {from = "player1", to = "@doesnotexist", subject = "should not be sent", body = "blah"})
|
||||
assert(sent_count == 1)
|
||||
|
||||
-- send a mail to a list
|
||||
assert_send(true, {from = "player1", to = "list/test", subject = "something", body = "blah"})
|
||||
assert_inbox_count("player2", 1)
|
||||
assert_inbox_count("player1", 1)
|
||||
assert(sent_count == 2)
|
||||
|
||||
-- send a second mail to the list and also the sender
|
||||
assert_send(true, {from = "player1", to = "list/test, alias/player1", subject = "something", body = "blah"})
|
||||
assert_inbox_count("player2", 2)
|
||||
assert_inbox_count("player1", 2)
|
||||
assert(sent_count == 3)
|
||||
|
||||
-- send a mail to list/reject - the mail should be rejected
|
||||
assert_send(false, {from = "player1", to = "list/reject", subject = "something", body = "NO"})
|
||||
assert_inbox_count("player2", 2)
|
||||
assert_inbox_count("player1", 2)
|
||||
assert(sent_count == 3)
|
||||
|
||||
callback()
|
||||
end)
|
10
chatcommands.lua
Normal file
10
chatcommands.lua
Normal file
@ -0,0 +1,10 @@
|
||||
minetest.register_chatcommand("mail",{
|
||||
description = "Open the mail interface",
|
||||
func = function(name, param)
|
||||
if #param > 0 then -- if param is not empty
|
||||
mail.show_compose(name, param) -- make a new message
|
||||
else
|
||||
mail.show_mail_menu(name) -- show main menu
|
||||
end
|
||||
end
|
||||
})
|
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
@ -0,0 +1,19 @@
|
||||
version: "4.1"
|
||||
|
||||
services:
|
||||
sut:
|
||||
build:
|
||||
context: ./test
|
||||
args:
|
||||
ENGINE_VERSION: ${ENGINE_VERSION:-5.7.0}
|
||||
user: root
|
||||
volumes:
|
||||
- "./:/root/.minetest/worlds/world/worldmods/mail/"
|
||||
- "world_data:/root/.minetest/worlds/world"
|
||||
- "./test/world.mt:/root/.minetest/worlds/world/world.mt"
|
||||
- "./test/minetest.conf:/minetest.conf"
|
||||
ports:
|
||||
- "30000:30000/udp"
|
||||
|
||||
volumes:
|
||||
world_data: {}
|
Loading…
Reference in New Issue
Block a user