123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- mailgui = mailgui or {}
- mailgui.modpath = minetest.get_modpath("mailgui")
- -- State management table. Used for GUIs.
- mailgui.players = mailgui.players or {}
- -- List of players who currently have open inbox formspecs.
- mailgui.open_inboxes = mailgui.open_inboxes or {}
- -- API function for alerting a player that they have mail.
- mailgui.alert_player = function(from, pname)
- -- If target player's inbox is open, alert them.
- minetest.after(5, function() -- The delay is useful if player sends mail to themselves.
- minetest.chat_send_player(pname, "# Server: <" .. rename.gpn(from) .. "> sent you mail! A Key is needed to view it.")
- if mailgui.open_inboxes[pname] then
- if mailgui.players[pname] then -- Sanity check.
- local intlive = mailgui.players[pname].live or 'true'
- if intlive == 'true' then
- mailgui.players[pname].infotext = "You received new mail! Press 'Get Mail'."
- mailgui.show_formspec(pname, true) -- Formspec should already be open.
- end
- end
- end
- end)
- end
- -- Called from the GUI to actually send a message.
- -- (This is done by interfacing with the email mod, which handles the database.)
- mailgui.send_mail_single = function(from, subject, message, mailto)
- local success, errstr = email.send_mail_single(from, mailto, subject, message)
- if success == true then
- -- This is so the player has a log of their message in chat.
- minetest.chat_send_player(from, "# Server: Email sent to <" .. rename.gpn(mailto) .. ">!")
- minetest.chat_send_player(from, "# Server: Subject: " .. subject)
- local tb = string.split(message, "\n")
- for k, v in ipairs(tb) do
- minetest.chat_send_player(from, "# Server: Message: " .. v)
- end
-
- mailgui.alert_player(from, mailto) -- GUI alert.
- else
- minetest.chat_send_player(from, "# Server: Failed to send mail to <" .. rename.gpn(mailto) .. ">!")
- easyvend.sound_error(from)
- end
- return success, errstr
- end
- mailgui.send_mail_multi = function(from, subject, message, mailto)
- local success, failure = email.send_mail_multi(from, mailto, subject, message)
- for k, v in ipairs(success) do
- minetest.chat_send_player(from, "# Server: Mail sent to <" .. rename.gpn(v.name) .. ">.")
- mailgui.alert_player(from, v.name) -- GUI alert.
- end
-
- for k, v in ipairs(failure) do
- local reason = "Reason unknown."
- if v.error == "boxfull" then
- reason = "Player's inbox is full."
- elseif v.error == "badplayer" then
- reason = "Player does not exist."
- elseif v.error == "toobig" then
- reason = "Email is too large."
- end
- minetest.chat_send_player(from, "# Server: Failed to send mail to <" .. rename.gpn(v.name) .. ">! " .. reason)
- easyvend.sound_error(from)
- end
-
- -- This is so the player has a log of their message in chat.
- minetest.chat_send_player(from, "# Server: Subject: " .. subject)
- local tb = string.split(message, "\n")
- for k, v in ipairs(tb) do
- minetest.chat_send_player(from, "# Server: Message: " .. v)
- end
-
- return #success, #failure -- Return number of successes and failures.
- end
- -- Called from the GUI to obtain a player's inbox for display.
- mailgui.get_inbox = function(pname)
- -- For purposes of the GUI, I need to make sure the table is ordered.
- local tb = email.get_inbox(pname)
- local out = {}
- for k, v in pairs(tb) do
- out[#out+1] = table.copy(v)
- end
- return out
- end
- -- Helper function to compose the display body of the currently selected mail.
- mailgui.compose_inbox_body = function(pname)
- local pstate = mailgui.players[pname]
- local inboxes = pstate.inboxes or {}
- local idx = pstate.selected
-
- -- Selection can be nil.
- if type(idx) ~= "number" then return "", "" end
-
- if idx >= 1 and idx <= #inboxes then
- local frm = inboxes[idx].from or ""
- local dat = inboxes[idx].date or ""
- local sub = inboxes[idx].sub or ""
- if sub == "" then sub = "No Subject" end
- local msg = inboxes[idx].msg or ""
-
- local body =
- "From: <" .. rename.gpn(frm) .. ">\n" ..
- "To: <" .. rename.gpn(pname) .. ">\n" ..
- "Date: " .. dat .. "\n" ..
- "Subject: " .. sub .. "\n" ..
- "------------------------------" ..
- "\n" .. msg
-
- return body, msg -- Display body, original body.
- end
- return "", ""
- end
- mailgui.compose_outbox_body = function(pname)
- local pstate = mailgui.players[pname]
-
- local frm = pname
- local to = pstate.mailto or ""
- local dat = os.date("%Y-%m-%d")
- local sub = pstate.subject or ""
- if sub == "" then sub = "No Subject" end
- local msg = pstate.message or ""
-
- local body =
- "From: <" .. rename.gpn(frm) .. ">\n" ..
- "To: <" .. rename.gpn(to) .. ">\n" ..
- "Date: " .. dat .. "\n" ..
- "Subject: " .. sub .. "\n" ..
- "------------------------------" ..
- "\n" .. msg
-
- return body
- end
- mailgui.compose_formspec = function(pname)
- -- Get state table for this player.
- local pstate = mailgui.players[pname]
- local inboxes = pstate.inboxes or {}
- local intlive = pstate.live or 'true'
- local formspec = "size[14,8]" ..
- default.gui_bg ..
- default.gui_bg_img ..
- default.gui_slots ..
- "item_image[0,0;1,1;passport:passport_adv]" ..
- "label[1,0;==| Send and Receive Mail |==]" ..
-
- -- Inbox panel.
- "label[1,0.5;Inbox (" .. #inboxes .. " / " .. email.maxsize .. ")]" ..
- "checkbox[3.8,0.32;enable_interupt;Enable Live Interrupt;" ..
- intlive .. "]" ..
- "textlist[0,1.1;6.31,1.8;headers;"
- -- Compose list of mail headers.
- local strs = ""
- for k, v in pairs(inboxes) do
- local frm = v.from or "N/A"
- local sub = v.sub or ""
- local dat = v.date or "N/A"
- if sub == "" then sub = "No Subject" end
- strs = strs .. minetest.formspec_escape(
- dat .. ": <" .. rename.gpn(frm) .. ">: " .. sub) .. ","
- end
- strs = string.gsub(strs, ",$", "") -- Remove trailing comma.
- formspec = formspec .. strs
-
- -- Finalize mail headers.
- local idx = pstate.selected -- May be nil.
- if type(idx) ~= "number" then idx = 1 end -- Must always be valid number.
- formspec = formspec .. ";" .. tostring(idx) .. ";false]"
-
- -- Compose display of currently selected message body.
- formspec = formspec .. "textarea[0.3,3.2;6.5,4;inbox;;"
-
- -- By composing the inbox body near the same code that composes the header
- -- list, using the same index value, I ensure the two GUI elements can never
- -- somehow get out of sync. This is important, because mail deletion is done
- -- by index, and I don't want players to be able to accidentally delete the
- -- wrong mail because the selected email and displayed email weren't the same.
- local body = mailgui.compose_inbox_body(pname)
- formspec = formspec .. minetest.formspec_escape(body)
- formspec = formspec .. "]" -- End textarea element.
-
- -- Compose message panel.
- formspec = formspec .. "label[7,0.5;Compose Message"
-
- if intlive == 'true' then
- formspec = formspec .. " (Interrupt Enabled)"
- end
-
- formspec = formspec .. "]" ..
- "label[7,1.0;Subject:]" ..
- "field[8.4,1.3;5.9,1;subject;;" ..
- minetest.formspec_escape(pstate.subject or "") .. "]" ..
- "label[7,2.0;Message:]" ..
- "textarea[8.4,2.0;5.9,4.3;message;;" ..
- minetest.formspec_escape(pstate.message or "") .. "]" ..
- "label[7,5.84;Send To:]" ..
- "field[8.4,6.14;5.9,1;mailto;;" ..
- minetest.formspec_escape(pstate.mailto or "") .. "]" ..
-
- "button[0,6.8;2,1;delall;Delete All]" ..
- "button[2,6.8;2,1;delone;Delete One]" ..
- "button[4,6.8;2,1;inboxcopy;Get Copy]" ..
- "label[0,7.7;Message: " ..
- minetest.formspec_escape(pstate.infotext or "") .. "]" ..
-
- "button[8,6.8;2,1;getmail;Get Mail]" ..
- "button[10,6.8;2,1;sendmail;Send Mail]" ..
- "button[12,6.8;2,1;outcopy;Get Copy]" ..
-
- -- Close button.
- "button[12,0;2,1;done;Close]"
-
- return formspec
- end
- -- API function.
- mailgui.show_formspec = function(pname, reshow)
- -- Make sure player has entry in state table.
- mailgui.players[pname] = mailgui.players[pname] or {
- -- State defaults.
- infotext = "", -- For operation messages.
- message = "", -- Current contents of draft area.
- subject = "", -- Current contents of subject line.
- mailto = "", -- Current conents of mailto line.
- selected = nil, -- Inbox selection index.
- inboxes = {}, -- Email records (badly named, I know).
- live = 'true', -- Whether interupting is allowed.
- }
-
- -- Do stuff if opening the GUI (not simply redrawing it).
- if not reshow then
- mailgui.players[pname].infotext = "Mail management opened!"
- mailgui.players[pname].inboxes = mailgui.get_inbox(pname)
- end
- mailgui.open_inboxes[pname] = true -- Player has open formspec.
- local formspec = mailgui.compose_formspec(pname)
- minetest.show_formspec(pname, "mailgui:mailgui", formspec)
- end
- -- GUI callback handler.
- mailgui.gui_handler = function(pref, fname, fields)
- if fname ~= "mailgui:mailgui" then return end -- Not valid for this callback.
-
- local pname = pref:get_player_name()
- if not mailgui.players[pname] then return true end -- No state, abort!
-
- if fields.quit then
- mailgui.open_inboxes[pname] = nil
- return true
- end
- -- Obtain state table for this player.
- local pstate = mailgui.players[pname]
- -- Don't update message composing state unless told to.
- if fields.message then pstate.message = fields.message end
- if fields.subject then pstate.subject = fields.subject end
- if fields.mailto then pstate.mailto = fields.mailto end
-
- if fields.done then
- mailgui.open_inboxes[pname] = nil
- passport.show_formspec(pname)
- return true
- end
- if fields.delall then
- email.clear_inbox(pname, pstate.inboxes)
- pstate.inboxes = {}
- pstate.infotext = "Inbox cleared!"
- pstate.selected = nil -- Unselect.
-
- local sz = email.get_inbox_size(pname)
- if sz > 0 then
- pstate.infotext = "Inbox cleared! (There are items remaining, press 'Get Mail'.)"
- end
- end
-
- if fields.delone then
- local idx = pstate.selected -- Can be nil.
- if type(idx) == "number" then
- if idx >= 1 and idx <= #pstate.inboxes then
- local frm = pstate.inboxes[idx].from or ""
- local sub = pstate.inboxes[idx].sub or ""
- if sub == "" then sub = "No Subject" end
-
- -- Delete email.
- local tb = {pstate.inboxes[idx]} -- Get table ref.
- email.clear_inbox(pname, tb)
- table.remove(pstate.inboxes, idx)
-
- pstate.infotext = "Message from <" .. rename.gpn(frm)
- .. "> (subject: " .. sub .. ") deleted!"
- else
- pstate.infotext = "Invalid selection index. Please select an email first."
- pstate.selected = nil
- end
- else
- pstate.infotext = "No message selected."
- end
- end
-
- if fields.enable_interupt then
- pstate.live = fields.enable_interupt
- if pstate.live == 'true' then
- pstate.infotext = "Live interrupt enabled. " ..
- "(Live mail updates will interfere with composing.)"
- elseif pstate.live == 'false' then
- pstate.infotext = "Live interrupt disabled. " ..
- "(You may compose mails without interruption.)"
- else
- pstate.infotext = "Invalid value!"
- end
- end
-
- if fields.sendmail then
- local subject = string.trim(pstate.subject or "")
- local message = string.trim(pstate.message or "")
- local mailto = pstate.mailto or ""
-
- -- Allow for no subject.
- if string.len(subject) <= email.max_subject_length then
- if message ~= "" then
- if mailto ~= "" then
- if not string.find(mailto, ",") then -- Singlename.
- mailto = string.trim(mailto)
- if mailto:lower() == "server" then
- if minetest.check_player_privs(pname, {server=true}) then
- -- Mail should be sent to all registered players.
- -- We use special keyword 'server' for this.
- -- No player can have this name.
- local nums, numf = mailall.send_mail(pname, subject, message)
- pstate.infotext = "Email sent to " .. nums ..
- " registered players(s). " .. numf .. " failure(s)."
- if numf == 0 then
- -- Clear fields on success.
- pstate.subject = ""
- pstate.message = ""
- pstate.mailto = ""
- end
- else
- pstate.infotext = "Cannot post server-wide mailing. Your privileges are insufficient!"
- end
- else
- local b, e = mailgui.send_mail_single(pname, subject, message, mailto)
- if b == true then
- pstate.infotext = "Mail sent to <" .. mailto .. ">!"
- -- Clear fields on success.
- pstate.subject = ""
- pstate.message = ""
- pstate.mailto = ""
- else
- if e == "boxfull" then
- pstate.infotext = "Recipient <" .. mailto .. ">'s inbox is full."
- elseif e == "badplayer" then
- pstate.infotext = "Recipient <" .. mailto .. "> does not exist!"
- elseif e == "toobig" then
- pstate.infotext = "Email is too big to send!"
- else
- pstate.infotext = "Unknown error!"
- end
- end
- end
- elseif string.find(mailto, ",") then -- Multiname.
- local strtab = string.split(mailto)
- -- Trim all names.
- for i=1, #strtab do
- strtab[i] = string.trim(strtab[i])
- end
- local nums, numf = mailgui.send_mail_multi(pname, subject, message, strtab)
- pstate.infotext = #strtab .. " recipient(s) listed. " ..
- nums .. " message(s) sent. " .. numf .. " failure(s)."
-
- if numf == 0 then
- -- Clear fields on all success.
- pstate.subject = ""
- pstate.message = ""
- pstate.mailto = ""
- end
- else
- pstate.infotext = "Unknown error!" -- Can we ever reach here?
- end
- else
- pstate.infotext = "Please enter a recipient!"
- end
- else
- pstate.infotext = "Please enter a message!"
- end
- else
- pstate.infotext = "Subject line is too long! Max is " .. email.max_subject_length .. " bytes."
- end
- end
-
- if fields.getmail then
- pstate.inboxes = mailgui.get_inbox(pname)
- pstate.infotext = "Inbox refreshed!"
- -- Not really useful to reset the selection?
- --pstate.selected = nil -- Unselect.
- end
-
- if fields.headers then
- assert(type(fields.headers) == "string")
- local event = minetest.explode_textlist_event(fields.headers)
- if event.type == "CHG" then
- local idx = event.index
- if idx >= 1 and idx <= #pstate.inboxes then
- pstate.selected = idx
- pstate.infotext = "Selected mail #" .. tostring(idx)
- else
- pstate.selected = nil
- pstate.infotext = "No mail currently selected."
- end
- elseif event.type == "DCL" then
- local idx = event.index
- if idx >= 1 and idx <= #pstate.inboxes then
- local mail = pstate.inboxes[idx]
- local sub = mail.sub or ""
- if sub == "" then sub = "No Subject" end
- pstate.subject = "RE: " .. sub
- pstate.mailto = rename.gpn(mail.from or "")
-
- pstate.selected = idx
- pstate.infotext = "Initialized response to mail #" .. tostring(idx)
- else
- pstate.selected = nil
- pstate.infotext = "No mail currently selected!"
- end
- end
- end
-
- if fields.inboxcopy then
- local idx = pstate.selected
- if type(idx) == "number" and idx >= 1 and idx <= #pstate.inboxes then
- local inv = pref:get_inventory()
- if inv then
- if inv:contains_item("main", "default:paper") then
- local fulltext = mailgui.compose_inbox_body(pname)
- local serialized = memorandum.compose_metadata({
- text = fulltext,
- signed = pname,
- })
- local itemstack = inv:add_item("main", {
- name="memorandum:letter",
- count=1, wear=0,
- metadata=serialized,
- })
- if itemstack:is_empty() then
- inv:remove_item("main", "default:paper")
- pstate.infotext = "Message printed! You should find it in your inventory."
- else
- pstate.infotext = "You must have space in your inventory for the printed mail."
- end
- else
- pstate.infotext = "You must have blank memorandum in your inventory in order to copy mail."
- end
- else
- pstate.infotext = "Cannot get inventory access!"
- end
- else
- pstate.infotext = "No mail currently selected!"
- end
- end
-
- if fields.outcopy then
- local subject = pstate.subject or ""
- local message = pstate.message or ""
- local mailto = pstate.mailto or ""
-
- if string.len(subject) <= email.max_subject_length then
- if message ~= "" then
- if mailto ~= "" then
- -- Attempt to copy outbox message to player's inventory.
- local inv = pref:get_inventory()
- if inv then
- if inv:contains_item("main", "default:paper") then
- local fulltext = mailgui.compose_outbox_body(pname)
- local serialized = memorandum.compose_metadata({
- text = fulltext,
- signed = pname,
- })
- local itemstack = inv:add_item("main", {
- name="memorandum:letter",
- count=1, wear=0,
- metadata=serialized,
- })
- if itemstack:is_empty() then
- inv:remove_item("main", "default:paper")
- pstate.infotext = "Message printed! You should find it in your inventory."
- else
- pstate.infotext = "You must have space in your inventory for the printed draft."
- end
- else
- pstate.infotext = "You must have blank memorandum in your inventory in order to copy a draft."
- end
- else
- pstate.infotext = "Cannot get inventory access!"
- end
- else
- pstate.infotext = "Please enter a recipient!"
- end
- else
- pstate.infotext = "Please enter a message!"
- end
- else
- pstate.infotext = "Subject line is too long! (Max " .. email.max_subject_length .. " bytes.)"
- end
- end
-
- -- Keep displaying formspec until explicitly told to quit.
- mailgui.show_formspec(pname, true)
- return true
- end
-
-
- if not mailgui.run_once then
- minetest.register_on_player_receive_fields(function(...)
- return mailgui.gui_handler(...)
- end)
-
- local c = "mailgui:core"
- local f = mailgui.modpath .. "/init.lua"
- reload.register_file(c, f, false)
-
- mailgui.run_once = true
- end
|