Implement non-player recipients

This commit is contained in:
y5nw 2024-01-05 21:07:30 +01:00
parent 721d882c26
commit 3046f46414
6 changed files with 118 additions and 57 deletions

47
api.lua
View file

@ -10,6 +10,11 @@ function mail.register_on_receive(func)
mail.registered_on_receives[#mail.registered_on_receives + 1] = func mail.registered_on_receives[#mail.registered_on_receives + 1] = func
end end
mail.registered_recipient_handlers = {}
function mail.register_recipient_handler(func)
table.insert(mail.registered_recipient_handlers, func)
end
function mail.send(m) function mail.send(m)
if type(m.from) ~= "string" then return false, "'from' is not a string" end 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.to or "") ~= "string" then return false, "'to' is not a string" end
@ -25,22 +30,22 @@ function mail.send(m)
local recipients = {} local recipients = {}
local undeliverable = {} local undeliverable = {}
m.to = mail.concat_player_list(mail.extractMaillists(m.to, m.from)) 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 if m.cc then
m.cc = mail.concat_player_list(mail.extractMaillists(m.cc, m.from)) 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 end
if m.bcc then if m.bcc then
m.bcc = mail.concat_player_list(mail.extractMaillists(m.bcc, m.from)) 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 end
if next(undeliverable) then -- table is not empty if next(undeliverable) then -- table is not empty
local undeliverable_names = {} local undeliverable_reason = {S("The mail could not be sent:")}
for name in pairs(undeliverable) do for _, reason in pairs(undeliverable) do
undeliverable_names[#undeliverable_names + 1] = '"' .. name .. '"' table.insert(undeliverable_reason, reason)
end end
return false, f("recipients %s don't exist; cannot send mail.", table.concat(undeliverable_names, ", ")) return false, table.concat(undeliverable_reason, "\n")
end end
local extra = {} local extra = {}
@ -85,32 +90,8 @@ function mail.send(m)
mail.set_storage_entry(m.from, entry) mail.set_storage_entry(m.from, entry)
-- add in every receivers inbox -- add in every receivers inbox
for recipient in pairs(recipients) do for _, deliver in pairs(recipients) do
entry = mail.get_storage_entry(recipient) deliver(msg)
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
end end
for i=1, #mail.registered_on_receives do for i=1, #mail.registered_on_receives do

16
api.md
View file

@ -42,6 +42,22 @@ mail.register_on_receive(function(m)
end) 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 # Internals
mod-storage entry for a player (indexed by playername and serialized with json): mod-storage entry for a player (indexed by playername and serialized with json):

View file

@ -49,6 +49,7 @@ dofile(MP .. "/storage.lua")
dofile(MP .. "/api.lua") dofile(MP .. "/api.lua")
dofile(MP .. "/gui.lua") dofile(MP .. "/gui.lua")
dofile(MP .. "/onjoin.lua") dofile(MP .. "/onjoin.lua")
dofile(MP .. "/player_recipients.lua")
-- sub directories -- sub directories
dofile(MP .. "/ui/init.lua") dofile(MP .. "/ui/init.lua")

47
player_recipients.lua Normal file
View file

@ -0,0 +1,47 @@
local S = minetest.get_translator("mail")
local has_canonical_name = minetest.get_modpath("canonical_name")
local function deliver_mail_to_player(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(mail)
deliver_mail_to_player(pname, mail)
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

View file

@ -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, recipients, undeliverable)
for _, name in ipairs(list) do
if not (recipients[name] or undeliverable[name]) 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}, recipients, undeliverable)
elseif vtp == "table" then
recursive_expand_recipient_names(sender, value, 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) return the field normalized (comma separated, single space)
and add individual player names to recipient list 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) local order = mail.parse_player_list(field)
for _, recipient_name in ipairs(order) do recursive_expand_recipient_names(sender, order, recipients, undeliverable)
if not minetest.player_exists(recipient_name) then
undeliverable[recipient_name] = true
else
recipients[recipient_name] = true
end
end
return mail.concat_player_list(order) return mail.concat_player_list(order)
end end
@ -21,23 +46,14 @@ function mail.parse_player_list(field)
return {} return {}
end end
local separator = ", " local separator = ",%s"
local pattern = "([^" .. separator .. "]+)" local pattern = "([^" .. separator .. "]+)"
-- get individual players -- get individual players
local player_set = {}
local order = {} local order = {}
field:gsub(pattern, function(player_name) for name in field:gmatch(pattern) do
local lower = string.lower(player_name) table.insert(order, name)
if not player_set[lower] then end
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)
return order return order
end end

View file

@ -2,7 +2,7 @@
mtt.register("util/normalize_players_and_add_recipients", function(callback) mtt.register("util/normalize_players_and_add_recipients", function(callback)
local recipients = {} local recipients = {}
local undeliverable = {} 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(to == "player1, player2")
assert(not next(undeliverable)) assert(not next(undeliverable))