Implement non-player recipients (#131)
* Implement non-player recipients * Add API callback specifically for players receiving mail * Exclude sender from (mailing list) recipients * Complement test * Fixup typos in complemented test * Expand aliases at toplevel if the current expansion is at toplevel This should allow players to send mail to their own aliases * Also test on_(player_)receive callbacks * Fix oversight in test case
This commit is contained in:
parent
fcca0b7511
commit
570cf788ec
52
api.lua
52
api.lua
@ -10,6 +10,16 @@ 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
|
||||
@ -25,22 +35,22 @@ function mail.send(m)
|
||||
local recipients = {}
|
||||
local undeliverable = {}
|
||||
m.to = mail.concat_player_list(mail.extractMaillists(m.to, m.from))
|
||||
m.to = mail.normalize_players_and_add_recipients(m.to, recipients, undeliverable)
|
||||
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.extractMaillists(m.cc, m.from))
|
||||
m.cc = mail.normalize_players_and_add_recipients(m.cc, recipients, undeliverable)
|
||||
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.extractMaillists(m.bcc, m.from))
|
||||
m.bcc = mail.normalize_players_and_add_recipients(m.bcc, recipients, undeliverable)
|
||||
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_names = {}
|
||||
for name in pairs(undeliverable) do
|
||||
undeliverable_names[#undeliverable_names + 1] = '"' .. name .. '"'
|
||||
local undeliverable_reason = {S("The mail could not be sent:")}
|
||||
for _, reason in pairs(undeliverable) do
|
||||
table.insert(undeliverable_reason, reason)
|
||||
end
|
||||
return false, f("recipients %s don't exist; cannot send mail.", table.concat(undeliverable_names, ", "))
|
||||
return false, table.concat(undeliverable_reason, "\n")
|
||||
end
|
||||
|
||||
local extra = {}
|
||||
@ -86,32 +96,8 @@ function mail.send(m)
|
||||
msg.spam = #mail.check_spam(msg) >= 1
|
||||
|
||||
-- add in every receivers inbox
|
||||
for recipient in pairs(recipients) do
|
||||
entry = mail.get_storage_entry(recipient)
|
||||
table.insert(entry.inbox, msg)
|
||||
mail.set_storage_entry(recipient, entry)
|
||||
end
|
||||
|
||||
-- notify recipients that happen to be online
|
||||
local mail_alert = S("You have a new message from @1! Subject: @2", m.from, m.subject) ..
|
||||
"\n" .. S("To view it, type /mail")
|
||||
local inventory_alert = S("You could also use the button in your inventory.")
|
||||
for _, player in ipairs(minetest.get_connected_players()) do
|
||||
local name = player:get_player_name()
|
||||
if recipients[name] then
|
||||
if mail.get_setting(name, "chat_notifications") == true then
|
||||
minetest.chat_send_player(name, mail_alert)
|
||||
if minetest.get_modpath("unified_inventory") or minetest.get_modpath("sfinv_buttons") then
|
||||
minetest.chat_send_player(name, inventory_alert)
|
||||
end
|
||||
end
|
||||
if mail.get_setting(name, "sound_notifications") == true then
|
||||
minetest.sound_play("mail_notif", {to_player=name})
|
||||
end
|
||||
local receiver_entry = mail.get_storage_entry(name)
|
||||
local receiver_messages = receiver_entry.inbox
|
||||
mail.hud_update(name, receiver_messages)
|
||||
end
|
||||
for _, deliver in pairs(recipients) do
|
||||
deliver(msg)
|
||||
end
|
||||
|
||||
for i=1, #mail.registered_on_receives do
|
||||
|
25
api.md
25
api.md
@ -34,7 +34,7 @@ local success, error = mail.send({
|
||||
```
|
||||
|
||||
# Hooks
|
||||
On-receive mail hook:
|
||||
Generic on-receive mail hook:
|
||||
|
||||
```lua
|
||||
mail.register_on_receive(function(m)
|
||||
@ -42,6 +42,29 @@ mail.register_on_receive(function(m)
|
||||
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):
|
||||
|
56
api.spec.lua
56
api.spec.lua
@ -1,12 +1,56 @@
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
mtt.register("send mail", function(callback)
|
||||
-- send a mail
|
||||
local success, err = mail.send({from = "player1", to = "player2", subject = "something", body = "blah"})
|
||||
-- send a mail to a list
|
||||
local success, err = mail.send({from = "player1", to = "list/test", subject = "something", body = "blah"})
|
||||
assert(success)
|
||||
assert(not err)
|
||||
assert_inbox_count("player2", 1)
|
||||
assert_inbox_count("player1", 0)
|
||||
assert(sent_count == 1)
|
||||
|
||||
-- send a second mail to the list and also the sender
|
||||
success, err = mail.send({from = "player1", to = "list/test, alias/player1", subject = "something", body = "blah"})
|
||||
assert(success)
|
||||
assert(not err)
|
||||
assert_inbox_count("player2", 2)
|
||||
assert_inbox_count("player1", 1)
|
||||
assert(sent_count == 2)
|
||||
|
||||
-- send a mail to list/reject - the mail should be rejected
|
||||
success, err = mail.send({from = "player1", to = "list/reject", subject = "something", body = "NO"})
|
||||
assert(not success)
|
||||
assert(type(err) == "string")
|
||||
assert_inbox_count("player2", 2)
|
||||
assert_inbox_count("player1", 1)
|
||||
assert(sent_count == 2)
|
||||
|
||||
-- check the receivers inbox
|
||||
local entry = mail.get_storage_entry("player2")
|
||||
assert(entry)
|
||||
assert(#entry.inbox > 0)
|
||||
callback()
|
||||
end)
|
||||
|
1
init.lua
1
init.lua
@ -49,6 +49,7 @@ dofile(MP .. "/storage.lua")
|
||||
dofile(MP .. "/api.lua")
|
||||
dofile(MP .. "/gui.lua")
|
||||
dofile(MP .. "/onjoin.lua")
|
||||
dofile(MP .. "/player_recipients.lua")
|
||||
-- sub directories
|
||||
dofile(MP .. "/ui/init.lua")
|
||||
|
||||
|
51
player_recipients.lua
Normal file
51
player_recipients.lua
Normal file
@ -0,0 +1,51 @@
|
||||
local S = minetest.get_translator("mail")
|
||||
local has_canonical_name = minetest.get_modpath("canonical_name")
|
||||
|
||||
mail.register_on_player_receive(function(name, msg)
|
||||
-- add to inbox
|
||||
local entry = mail.get_storage_entry(name)
|
||||
table.insert(entry.inbox, msg)
|
||||
mail.set_storage_entry(name, entry)
|
||||
|
||||
-- notify recipients that happen to be online
|
||||
local mail_alert = S("You have a new message from @1! Subject: @2", msg.from, msg.subject) ..
|
||||
"\n" .. S("To view it, type /mail")
|
||||
local inventory_alert = S("You could also use the button in your inventory.")
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if player then
|
||||
if mail.get_setting(name, "chat_notifications") == true then
|
||||
minetest.chat_send_player(name, mail_alert)
|
||||
if minetest.get_modpath("unified_inventory") or minetest.get_modpath("sfinv_buttons") then
|
||||
minetest.chat_send_player(name, inventory_alert)
|
||||
end
|
||||
end
|
||||
if mail.get_setting(name, "sound_notifications") == true then
|
||||
minetest.sound_play("mail_notif", {to_player=name})
|
||||
end
|
||||
local receiver_entry = mail.get_storage_entry(name)
|
||||
local receiver_messages = receiver_entry.inbox
|
||||
mail.hud_update(name, receiver_messages)
|
||||
end
|
||||
end)
|
||||
|
||||
mail.register_recipient_handler(function(_, pname)
|
||||
if not minetest.player_exists(pname) then
|
||||
return nil
|
||||
end
|
||||
return true, function(msg)
|
||||
for _, on_player_receive in ipairs(mail.registered_on_player_receives) do
|
||||
if on_player_receive(pname, msg) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
if has_canonical_name then
|
||||
mail.register_recipient_handler(function(_, name)
|
||||
local realname = canonical_name.get(name)
|
||||
if realname then
|
||||
return true, realname
|
||||
end
|
||||
end)
|
||||
end
|
@ -1,18 +1,43 @@
|
||||
local has_canonical_name = minetest.get_modpath("canonical_name")
|
||||
local S = minetest.get_translator("mail")
|
||||
|
||||
local function recursive_expand_recipient_names(sender, list, is_toplevel, recipients, undeliverable)
|
||||
for _, name in ipairs(list) do
|
||||
if not (recipients[name] or undeliverable[name] or (name == sender and not is_toplevel)) then
|
||||
local succ, value
|
||||
for _, handler in ipairs(mail.registered_recipient_handlers) do
|
||||
succ, value = handler(sender, name)
|
||||
if succ ~= nil then
|
||||
break
|
||||
end
|
||||
end
|
||||
local vtp = type(value)
|
||||
if succ then
|
||||
if vtp == "string" then
|
||||
recursive_expand_recipient_names(sender, {value}, is_toplevel, recipients, undeliverable)
|
||||
elseif vtp == "table" then
|
||||
recursive_expand_recipient_names(sender, value, false, recipients, undeliverable)
|
||||
elseif vtp == "function" then
|
||||
recipients[name] = value
|
||||
else
|
||||
undeliverable[name] = S("The method of delivery to @1 is invalid.", name)
|
||||
end
|
||||
elseif succ == nil then
|
||||
undeliverable[name] = S("The recipient @1 could not be identified.", name)
|
||||
else
|
||||
local reason = tostring(value) or S("@1 rejected your mail.", name)
|
||||
undeliverable[name] = reason
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
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, undeliverable)
|
||||
function mail.normalize_players_and_add_recipients(sender, field, recipients, undeliverable)
|
||||
local order = mail.parse_player_list(field)
|
||||
for _, recipient_name in ipairs(order) do
|
||||
if not minetest.player_exists(recipient_name) then
|
||||
undeliverable[recipient_name] = true
|
||||
else
|
||||
recipients[recipient_name] = true
|
||||
end
|
||||
end
|
||||
recursive_expand_recipient_names(sender, order, true, recipients, undeliverable)
|
||||
return mail.concat_player_list(order)
|
||||
end
|
||||
|
||||
@ -21,23 +46,14 @@ function mail.parse_player_list(field)
|
||||
return {}
|
||||
end
|
||||
|
||||
local separator = ", "
|
||||
local separator = ",%s"
|
||||
local pattern = "([^" .. separator .. "]+)"
|
||||
|
||||
-- get individual players
|
||||
local player_set = {}
|
||||
local order = {}
|
||||
field:gsub(pattern, function(player_name)
|
||||
local lower = string.lower(player_name)
|
||||
if not player_set[lower] then
|
||||
if has_canonical_name then
|
||||
player_name = canonical_name.get(player_name) or player_name
|
||||
end
|
||||
|
||||
player_set[lower] = player_name
|
||||
order[#order+1] = player_name
|
||||
end
|
||||
end)
|
||||
for name in field:gmatch(pattern) do
|
||||
table.insert(order, name)
|
||||
end
|
||||
|
||||
return order
|
||||
end
|
||||
|
@ -2,11 +2,11 @@
|
||||
mtt.register("util/normalize_players_and_add_recipients", function(callback)
|
||||
local recipients = {}
|
||||
local undeliverable = {}
|
||||
local to = mail.normalize_players_and_add_recipients("player1,player2", recipients, undeliverable)
|
||||
local to = mail.normalize_players_and_add_recipients("sender", "player1,player2", recipients, undeliverable)
|
||||
|
||||
assert(to == "player1, player2")
|
||||
assert(not next(undeliverable))
|
||||
assert(recipients["player1"])
|
||||
assert(recipients["player2"])
|
||||
callback()
|
||||
end)
|
||||
end)
|
||||
|
Loading…
Reference in New Issue
Block a user