Загрузить файлы в «/»

This commit is contained in:
Nomad Senaxsys 2024-09-28 22:22:06 +03:00
commit e67e2900fc
5 changed files with 390 additions and 0 deletions

152
api.lua Normal file
View 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
View 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
View 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
View 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
View 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: {}