Compare commits

..

77 commits

Author SHA1 Message Date
TarasArt
f7cc435f6b
Add Ukrainian localization (#158)
Some checks failed
luacheck / luacheck (push) Has been cancelled
test / build (5.0.1) (push) Has been cancelled
test / build (5.1.1) (push) Has been cancelled
test / build (5.10.0) (push) Has been cancelled
test / build (5.2.0) (push) Has been cancelled
test / build (5.3.0) (push) Has been cancelled
test / build (5.4.1) (push) Has been cancelled
test / build (5.5.1) (push) Has been cancelled
test / build (5.6.1) (push) Has been cancelled
test / build (5.7.0) (push) Has been cancelled
test / build (5.8.0) (push) Has been cancelled
test / build (5.9.1) (push) Has been cancelled
test / build (latest) (push) Has been cancelled
2025-04-21 23:14:57 +02:00
Bapt-tech
9fe6885f90 Update hud.lua
Some checks failed
luacheck / luacheck (push) Has been cancelled
test / build (5.0.1) (push) Has been cancelled
test / build (5.1.1) (push) Has been cancelled
test / build (5.10.0) (push) Has been cancelled
test / build (5.2.0) (push) Has been cancelled
test / build (5.3.0) (push) Has been cancelled
test / build (5.4.1) (push) Has been cancelled
test / build (5.5.1) (push) Has been cancelled
test / build (5.6.1) (push) Has been cancelled
test / build (5.7.0) (push) Has been cancelled
test / build (5.8.0) (push) Has been cancelled
test / build (5.9.1) (push) Has been cancelled
test / build (latest) (push) Has been cancelled
2025-04-09 15:18:28 +02:00
Athozus
1cc18b840c
Add 5.10.0 and replace 5.9.0 by 5.9.1 in workflows 2024-12-15 12:41:14 +01:00
randomei
87b24bae5d Russian translation 2024-12-15 12:39:21 +01:00
Athozus
0d8f7cc445
Fix luacheck namespaces 2024-10-26 12:40:01 +02:00
Athozus
030a7a3fe8
Use core namespace instead of minetest 2024-10-25 23:00:27 +02:00
Athozus
8a992b7a29
Bump version to 1.5.0-dev in about.lua 2024-09-01 16:41:39 +02:00
Maksym H.
1bffd98132 Localize get_translator call 2024-09-01 16:36:55 +02:00
Athozus
59667bd35c
Add 5.9.0 to workflows 2024-08-12 00:33:36 +02:00
Athozus
09b233b039
Update credits and bump to version 1.4.1 2024-08-09 00:35:42 +02:00
Athozus
b9982f11e6
Add support for a get_keys() equivalent for lower than 5.7 Minetest versions (#153)
* Add support for a get_keys() equivalent for lower than 5.7 Minetest versions

* Do not call the function itself to check if it exists

Co-authored-by: luk3yx <luk3yx@users.noreply.github.com>

* Do not call the function itself to check if it exists (2)

Co-authored-by: luk3yx <luk3yx@users.noreply.github.com>

* Fix an occurrence of get_keys() in is_uuid_existing()

---------

Co-authored-by: luk3yx <luk3yx@users.noreply.github.com>
2024-08-09 00:31:28 +02:00
Athozus
4f15c2fe65
Update version to 1.4.1-dev in about.lua 2024-08-06 02:26:10 +02:00
1F616EMO
4cd06c5f5f Optimize performance of the inbox
Moves the `mail.get_setting(name, "mute_list")` call out of the message loop. Previously, this is called repeated, causing disastrous lag when using mail with beerchat.
2024-08-06 02:21:20 +02:00
Athozus
036d37695a
Update files to 1.4 (#149)
* Update README and screenshots

* Update version number in about.lua
2024-08-05 17:23:01 +02:00
Athozus
5cfec3a92a
Update api.md docs (#148)
* Update player entry format in API documentation

* Add spam flag
2024-08-05 17:01:20 +02:00
Athozus
a347a79e6a
Update actions workflows 2024-08-05 16:59:51 +02:00
Niklp
48b632fba2
Update German translations (#147) 2024-04-20 23:35:32 +02:00
Athozus
2938b74039
Update translation files 2024-04-20 18:23:52 +02:00
Athozus
ed6f36ab6d Move date and time settings to a sub-group of Other 2024-04-20 17:41:59 +02:00
Athozus
1dab26f0bf Add timezone offset setting
Clarify timezone offset tooltip

Co-authored-by: y5nw <37980625+y5nw@users.noreply.github.com>
2024-04-20 17:41:59 +02:00
Athozus
9a52c1c181 Add support for number type settings
Fix reset issue
2024-04-20 17:41:59 +02:00
Athozus
19be2d46a2 Move sorting fields settings to new Fields settings group 2024-04-20 17:30:42 +02:00
Athozus
dc9c4f86b7 Add support for tree-hierarchized settings groups
Compute once the ordered settings groups list
2024-04-20 17:30:42 +02:00
Athozus
1f208c6a21 Add table.insert_all() to luacheck 2024-04-20 17:30:42 +02:00
Athozus
30b9a0fba4
Copy search icon into textures
In order to keep compatibility to 5.0.0, as the search icon was introduced for main menu in 5.3.0
2024-04-15 17:46:21 +02:00
Athozus
dfaa34c8c5
Ignore mtt tests and screenshot at release 2024-04-10 18:33:30 +02:00
Athozus
a3af9ee389
Repair UUIDs duplicates (storage 3.1 upgrade) (#143)
* Check if candidate uuid does not already exist before returning it

* Use get_keys() to iterate through entries

Else it can't be ran with mtt

* Do not give an initial value to candidate_uuid

Triggers luacheck because the blank value is unused. Just give it nil value.

* Add repairing storage script in migrate.lua

* Remove goto statements

Due to uncompatibility with LuaJIT

* Optimize functions

* Merge duplicated functions between migrate.lua and util/uuid.lua

* Mark repair has been done by incremeting version to 3.1

* Remove checking all UUIDs for generating new one

The risk is ridiculous compared to the performance lost, even for several millions of messages.

* Revert util/uuid.lua changes to master

* Rename repair_box() to fix_duplicate_uuids()
2024-04-09 21:54:39 +02:00
Athozus
fc7b438de3
Harmonize options widgets 2024-04-06 18:10:41 +02:00
Athozus
c6d8543c85
Move contributor_grouping to selected_idxs 2024-04-06 12:00:30 +02:00
Athozus
3670fe79cc
Add 5.8.0 to workflows 2024-04-06 11:22:26 +02:00
Athozus
f57473abd0
Adjusted options UI (#141)
* Optimize settings layout

* Optimize about layout

* Use codes for contributors list instead of overflowing texts

* Integrate version id in its translation string

Using @1 within the string

Co-authored-by: y5nw <37980625+y5nw@users.noreply.github.com>

* Use contributions labels instead of code

* Fix formspec syntax error

Missing ]

* Implement grouping by contribution type (#142)

* Implement grouping by contribution type

* minor

---------

Co-authored-by: y5nw <37980625+y5nw@users.noreply.github.com>
2024-03-30 23:21:18 +01:00
Athozus
e516fe04c4
Use main menu search icon
Instead of a subject to confusion Q
2024-03-24 17:42:51 +01:00
Athozus
1dde4097f9
Fix empty mailing lists 2024-03-24 17:38:54 +01:00
Athozus
ccf07b50e2
Simplify marking mail property with a local function in storage.lua 2024-03-22 23:04:31 +01:00
Athozus
1c5e4b6cd6
Harmonize function names with snake case 2024-03-22 22:43:26 +01:00
Athozus
6f7ccc77bd
Add patch files to gitignore 2024-03-22 22:06:31 +01:00
Athozus
163467379f
Fix nil player list concatenating 2024-03-22 22:05:48 +01:00
y5nw
2694ffa2dc
Cleanly handle player mailling lists (#140)
* Fix server hanging for certain recipient names

* Disallow recursive maillist inclusion

* Disallow sending to empty recipient

* Complement testcases
2024-03-22 21:59:04 +01:00
y5nw
851fa9f12a
Update zh translations (#139)
* Update zh translations

* zh_TW: 命令 -> 指令
2024-02-18 19:06:18 +01:00
Athozus
1eb3b7668d
Correct dev version 2024-02-03 16:13:54 +01:00
Athozus
ea7773730e
Minor improvements and fixes to message view layout 2024-02-03 01:07:25 +01:00
Athozus
0e06718a51
Update translations 2024-02-03 01:03:00 +01:00
Athozus
ae238ba143
Add mod_translation_updater.py to gitignore 2024-02-03 00:58:32 +01:00
y5nw
570cf788ec
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
2024-02-01 20:46:26 +01:00
Singularis
fcca0b7511
fix for bugs #135 and #136 (#137)
* fix for bugs #135 and #136

* Fix indentation in ui/compose.lua

Co-authored-by: SX <50966843+S-S-X@users.noreply.github.com>

---------

Co-authored-by: Athozus <athozus@gmail.com>
Co-authored-by: SX <50966843+S-S-X@users.noreply.github.com>
2024-02-01 14:33:33 +01:00
Niklp
c5fd218415 Fix luacheck
Fix luacheck warnings in spam.lua

Fix global variable initialization (was needed only locally)

Add beerchat to luacheck
2024-02-01 14:23:03 +01:00
Athozus
103c4ae441 Add mute list setting
Add sharing between mute_list and beerchat

Add check for mute list
2024-02-01 14:23:03 +01:00
Athozus
e45d56439f Add list setting type 2024-02-01 14:23:03 +01:00
Athozus
cd9e9ec8e1 Add (Un)mark spam buttons
Resize boxes view, and modify the layout of message view
2024-02-01 14:23:03 +01:00
Athozus
48fc8470f5 Add storage function to (un)mark message as spams 2024-02-01 14:23:03 +01:00
Athozus
bfe0ef2711 Initial implementation of spam check
It checks during the sends if there are spam warnings then give to the message an attribute spam=true (only for receivers)
2024-02-01 14:23:03 +01:00
Athozus
75510d2551 Add checks utils 2024-02-01 14:23:03 +01:00
Athozus
83842a8861 Add beerchat in optional depends 2024-02-01 14:23:03 +01:00
Athozus
aab7acf2b5 Add warning color
Based on Minetest main menu orange color
2024-02-01 14:23:03 +01:00
Athozus
46012a2b28
Add support for shared settings (#127)
* Add support for shared settings

* Optimize stored setting value getting

Co-authored-by: SX <50966843+S-S-X@users.noreply.github.com>

* Avoid false for default transfer value

In set_setting, to eventually transfer to another mod. Suggested by S-S-X in #127

---------

Co-authored-by: SX <50966843+S-S-X@users.noreply.github.com>
2024-02-01 10:18:55 +01:00
BuckarooBanzay
721d882c26 evict cached storage entries periodically
fixes synchronization issues if other applications write directly to the mail-entries in the mod-storage
2024-01-13 18:18:16 +01:00
aBlueShadow
94f37da092 add sfinv_buttons compatibility 2024-01-13 16:28:14 +01:00
Athozus
3eafeb4cf8
Remove selected_idxs for settings after saving 2024-01-01 19:19:51 +01:00
Niklp
8d80e4c709 Update translation templates 2023-12-30 17:26:14 +01:00
Athozus
720315a22c
Move settings-related to util/settings.lua 2023-12-27 18:07:23 +01:00
Athozus
77de24e467
Use interleaved style (#124)
* Generate interleaved style when replying/forwarding a message (#120)

* Simplify interleaving function

Co-authored-by: SX <50966843+S-S-X@users.noreply.github.com>

---------

Co-authored-by: SX <50966843+S-S-X@users.noreply.github.com>
2023-12-09 00:01:21 +01:00
Athozus
3bad371353
Add colors utilities (#121)
* Add colors utilities

Local function get_base_color(), conversions hex <=> rgb, rgb color mixer, and global function get_color()

* Round numbers to avoid eventual float in string.format

* Simplify inbox/outbox mixing of color

Use a single if statement for each property and concatenate to displayed_color then execute mail.get_color(displayed_color) instead of making many combined if statements

* Convert 3-chars hex colors to 6-chars hex colors

Could break the code, the hex convert to rgb always run on 6-chars

* Rework color utility using tables

Instead of one-letter symbols, it now supports tables of identifiers or single strings
2023-12-06 10:11:12 +01:00
Athozus
802f9f727b
Add labels for index-type settings 2023-12-05 22:46:52 +01:00
Muhammad Rifqi Priyo Susanto
bebb7a8702
Add Indonesian translations (#123)
* Add Indonesian translations

* Fix few strings for all translation files

* Update credits

---------

Co-authored-by: Athozus <athozus@gmail.com>
2023-12-05 22:31:40 +01:00
Github is a non-free platform owned by Microsoft. Reasonable alternatives exist, such as Gitea, Sourcehut. We need a federated, mastodon-like forge based on ForgeFed. See: https://forgefed.org
8137e9405d
es_ES locale update (#117) 2023-10-23 19:35:28 -04:00
BuckarooBanzay
2111db2ff9 check if message is found in mail.show_message 2023-10-24 00:34:12 +02:00
Athozus
f8f500ed76
Do not call show_message() if selected message is empty (fix #115) 2023-10-24 00:33:14 +02:00
Athozus
7754e7e361
Add check for mailing list nil description (fix #112) 2023-10-16 20:36:05 +02:00
Athozus
80ae9c3342
Add message id in player selections storage (fix #110, fix #114) 2023-10-16 20:32:06 +02:00
OgelGames
8d7c8c63cf
step names in luacheck workflow 2023-09-28 18:58:25 +10:00
David Leal
88a276f4cb
Use a faster and newer LuaCheck (#113) 2023-09-27 21:02:49 +02:00
Athozus
e5996469fb
Reworked settings (#111)
* Store globally settings (type and default value)

* Add settings groups

* Generate settings pages with global storage

Add saving, generate selection idxs from settings list, order settings via index value in each group of settings

* Rewrite setting store

No code change, only format

Co-authored-by: SX <50966843+S-S-X@users.noreply.github.com>

* Remove tabs from settings groups

Use spaces instead

Co-authored-by: SX <50966843+S-S-X@users.noreply.github.com>

* Use table.copy() minetest api function

* Better formatting for setting tooltip

Using inline instead of multiples lines to add tooltip attached to setting into formspec

Co-authored-by: SX <50966843+S-S-X@users.noreply.github.com>

---------

Co-authored-by: SX <50966843+S-S-X@users.noreply.github.com>
2023-09-14 20:35:30 +02:00
savilli
fe533eeb4d
Fix crash if non-existent mail was selected (#109)
* Fix crash if non-existent mail was selected

* Fix outbox too
2023-08-06 13:12:46 +02:00
Athozus
2a18322cdb
Remove unexcepted translation from base directory 2023-07-20 00:50:10 +02:00
Athozus
7ae5bda5ab
Quick restructuration of files (create sub-init.lua) 2023-07-20 00:50:10 +02:00
Athozus
a9a4c8c4dc
Redesign settings and about layout 2023-07-20 00:50:09 +02:00
Athozus
9990d56004
Add checks for cc/bcc/subject in mail.send() and mail.save_draft() api functions
Fix api new checks failed builds
2023-07-20 00:49:58 +02:00
59 changed files with 2458 additions and 1171 deletions

5
.gitattributes vendored Normal file
View file

@ -0,0 +1,5 @@
mtt.lua export-ignore
docker-compose.yml export-ignore
*.spec.lua export-ignore
test/* export-ignore
screenshot_* export-ignore

View file

@ -1,17 +1,10 @@
name: luacheck
on: [push, pull_request]
jobs:
build:
luacheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: apt
run: sudo apt-get install -y luarocks
- name: luacheck install
run: luarocks install --local luacheck
- name: luacheck run
run: $HOME/.luarocks/bin/luacheck ./
- name: Checkout
uses: actions/checkout@master
- name: Luacheck
uses: lunarmodules/luacheck@master

View file

@ -9,9 +9,9 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
ENGINE_VERSION: [5.0.1, 5.1.1, 5.2.0, 5.3.0, 5.4.1, 5.5.1, 5.6.1, 5.7.0, latest]
ENGINE_VERSION: [5.0.1, 5.1.1, 5.2.0, 5.3.0, 5.4.1, 5.5.1, 5.6.1, 5.7.0, 5.8.0, 5.9.1, 5.10.0, latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: test
run: docker-compose up --exit-code-from sut
run: docker compose up --exit-code-from sut

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
i18n.py
mod_translation_updater.py
locale/*.tr.old
*.patch

View file

@ -5,15 +5,16 @@ globals = {
read_globals = {
-- Stdlib
string = {fields = {"split"}},
table = {fields = {"copy", "getn"}},
table = {fields = {"copy", "getn", "indexof", "insert_all"}},
beerchat = {fields = {"has_player_muted_player", "execute_callbacks"}},
-- Minetest
"minetest",
-- Luanti
"core",
"vector", "ItemStack",
"dump",
-- Deps
"unified_inventory", "default",
"unified_inventory", "default", "sfinv_buttons",
-- optional mods
"mtt", "canonical_name"

View file

@ -11,7 +11,8 @@ It adds a mail-system that allows players to send each other messages in-game an
# Screenshot
![](screenshot_1.2.0.png)
![Main view](screenshot_1.4.0_1.png)
![Message view](screenshot_1.4.0_2.png)
# Installation
@ -43,7 +44,8 @@ Mails can be deleted, marked as read or unread, replied to and forwarded to anot
* Multiple selection (new in 1.1.0)
* Settings
* Chat, on join, HUD and sound notifications
* Translated in : English, French, German, Chinese (both traditional and simplified), Spanish, Brazilian Portuguese.
* Anti-spam detection
* Translated in : English, French, German, Chinese (both traditional and simplified), Spanish, Brazilian Portuguese, Hungarian, Indonesian.
# Compatibility / Migration
@ -51,8 +53,10 @@ Overview:
* `v1` all the data is in the `<worldfolder>/mails.db` file
* `v2` every player has its own (in-) mailbox in the `<worldfolder>/mails/<playername>.json` file
* `v3` every player has an entry in the `<playername>` `mod_storage/` (inbox, outbox, drafts, contacts, mailing lists, settings)
* `v3.1` database fix after the message id mess
# Dependencies
* None
# License
@ -72,7 +76,7 @@ See the "LICENSE" file
* fluxionary (Minor fixups)
* Toby1710 (UX fixes)
* Peter Nerlich (CC, BCC)
* Emojigit (Traditional Chinese translation)
* Emojigit (Performance, Traditional Chinese translation)
* Niklp09 (German translation)
* Dennis Jenkins (UX fixes)
* Thomas Rudin (Maintenance)
@ -84,6 +88,12 @@ See the "LICENSE" file
* nyomi (Hungarian translation)
* whosit (UI fixes)
* Wuzzy (German translation)
* savilli (UX fixes)
* Panquesito7 (Maintenance)
* Eredin (Spanish translation)
* Muhammad Rifqi Priyo Susanto (Indonesian translation)
* aBlueShadow (sfinv compatibility)
* Singularis (UX and storage fixes)
# Contribute

77
api.lua
View file

@ -1,7 +1,7 @@
-- see: mail.md
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local f = string.format
@ -10,9 +10,22 @@ 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) ~= "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
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
@ -21,23 +34,25 @@ function mail.send(m)
-- normalize to, cc and bcc while compiling a list of all recipients
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.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.extractMaillists(m.cc, m.from))
m.cc = mail.normalize_players_and_add_recipients(m.cc, recipients, undeliverable)
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.extractMaillists(m.bcc, m.from))
m.bcc = mail.normalize_players_and_add_recipients(m.bcc, recipients, undeliverable)
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_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")
elseif not next(recipients) then
return false, S("You did not specify any valid recipient.")
end
local extra = {}
@ -54,7 +69,7 @@ function mail.send(m)
extra_log = ""
end
minetest.log("action", f("[mail] %q send mail to %q%s with subject %q and body %q",
core.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
))
@ -80,34 +95,11 @@ function mail.send(m)
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 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 unified_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") then
minetest.chat_send_player(name, unified_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
@ -121,13 +113,16 @@ end
function mail.save_draft(m)
if type(m.from) ~= "string" then return false, "'from' is not a string" end
if type(m.to) ~= "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
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",
core.log("verbose", f("[mail] %q saves draft with subject %q and body %q",
m.from, m.subject, m.body
))

42
api.md
View file

@ -1,4 +1,3 @@
# Mail format
The mail format in the api hooks
@ -34,7 +33,7 @@ local success, error = mail.send({
```
# Hooks
On-receive mail hook:
Generic on-receive mail hook:
```lua
mail.register_on_receive(function(m)
@ -42,11 +41,35 @@ 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):
```lua
{
contacts = {
{
-- name of the player (unique key in the list)
@ -76,7 +99,9 @@ mod-storage entry for a player (indexed by playername and serialized with json):
-- timestamp (os.time())
time = 1234,
-- read-flag (true: player has read the mail, inbox only)
read = true
read = true,
-- spam-flag (true: that mail is noted as a spam)
spam = false
},{
...
}
@ -84,6 +109,12 @@ mod-storage entry for a player (indexed by playername and serialized with json):
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)
@ -93,5 +124,10 @@ mod-storage entry for a player (indexed by playername and serialized with json):
-- playername list
players = {"playername", "playername2"}
}
},
settings = {
setting1 = "value",
setting2 = true,
setting3 = 123
}
}

View file

@ -1,12 +1,76 @@
mtt.register("send mail", function(callback)
-- send a mail
local success, err = mail.send({from = "player1", to = "player2", subject = "something", body = "blah"})
assert(success)
assert(not err)
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)
-- check the receivers inbox
local entry = mail.get_storage_entry("player2")
assert(entry)
assert(#entry.inbox > 0)
callback()
end)

View file

@ -1,4 +1,4 @@
minetest.register_chatcommand("mail",{
core.register_chatcommand("mail",{
description = "Open the mail interface",
func = function(name, param)
if #param > 0 then -- if param is not empty

View file

@ -1,4 +1,4 @@
version: "3.6"
version: "4.1"
services:
sut:

12
gui.lua
View file

@ -1,5 +1,5 @@
if minetest.get_modpath("unified_inventory") then
if core.get_modpath("unified_inventory") then
unified_inventory.register_button("mail", {
type = "image",
@ -10,3 +10,13 @@ if minetest.get_modpath("unified_inventory") then
end
})
end
if core.get_modpath("sfinv_buttons") then
sfinv_buttons.register_button("mail", {
title = "Mail",
image = "mail_button.png",
action = function(player)
mail.show_mail_menu(player:get_player_name())
end
})
end

10
hud.lua
View file

@ -1,12 +1,12 @@
local huddata = {}
minetest.register_on_joinplayer(function(player)
core.register_on_joinplayer(function(player)
local name = player:get_player_name()
local data = {}
data.imageid = player:hud_add({
hud_elem_type = "image",
type = "image",
name = "MailIcon",
position = {x=0.52, y=0.52},
text="",
@ -15,7 +15,7 @@ minetest.register_on_joinplayer(function(player)
})
data.textid = player:hud_add({
hud_elem_type = "text",
type = "text",
name = "MailText",
position = {x=0.55, y=0.52},
text= "",
@ -27,7 +27,7 @@ minetest.register_on_joinplayer(function(player)
huddata[name] = data
end)
minetest.register_on_leaveplayer(function(player)
core.register_on_leaveplayer(function(player)
local name = player:get_player_name()
huddata[name] = nil
end)
@ -35,7 +35,7 @@ end)
function mail.hud_update(playername, messages)
local data = huddata[playername]
local player = minetest.get_player_by_name(playername)
local player = core.get_player_by_name(playername)
if not data or not player then
return

View file

@ -3,7 +3,10 @@ mail = {
version = 3,
-- mod storage
storage = minetest.get_mod_storage(),
storage = core.get_mod_storage(),
-- translation
S = core.get_translator(core.get_current_modname()),
-- ui theme prepend
theme = "",
@ -17,6 +20,7 @@ mail = {
outbox = {},
drafts = {},
trash = {},
message = {},
contacts = {},
maillists = {},
to = {},
@ -28,42 +32,20 @@ mail = {
filter = {},
multipleselection = {},
optionstab = {},
chat_notifications = {},
onjoin_notifications = {},
hud_notifications = {},
sound_notifications = {},
unreadcolorenable = {},
cccolorenable = {},
trash_move_enable = {},
auto_marking_read = {},
},
colors = {
header = "#999",
selected = "#72FF63",
important = "#FFD700",
additional = "#CCCCDD",
imp_sel = "#B9EB32",
add_sel = "#9FE6A0",
imp_add = "#E6D26F",
imp_add_sel = "#BFE16B",
highlighted = "#608631",
new = "#00F529"
settings_group = {},
contributor_grouping = {},
},
message_drafts = {}
}
if minetest.get_modpath("default") then
if core.get_modpath("default") then
mail.theme = default.gui_bg .. default.gui_bg_img
end
-- sub files
local MP = minetest.get_modpath(minetest.get_current_modname())
dofile(MP .. "/util/normalize.lua")
dofile(MP .. "/util/contact.lua")
dofile(MP .. "/util/uuid.lua")
dofile(MP .. "/util/time_ago.lua")
local MP = core.get_modpath(core.get_current_modname())
dofile(MP .. "/util/init.lua")
dofile(MP .. "/chatcommands.lua")
dofile(MP .. "/migrate.lua")
dofile(MP .. "/hud.lua")
@ -71,28 +53,14 @@ dofile(MP .. "/storage.lua")
dofile(MP .. "/api.lua")
dofile(MP .. "/gui.lua")
dofile(MP .. "/onjoin.lua")
dofile(MP .. "/ui/mail.lua")
dofile(MP .. "/ui/inbox.lua")
dofile(MP .. "/ui/outbox.lua")
dofile(MP .. "/ui/drafts.lua")
dofile(MP .. "/ui/trash.lua")
dofile(MP .. "/ui/message.lua")
dofile(MP .. "/ui/receivers.lua")
dofile(MP .. "/ui/events.lua")
dofile(MP .. "/ui/contacts.lua")
dofile(MP .. "/ui/edit_contact.lua")
dofile(MP .. "/ui/select_contact.lua")
dofile(MP .. "/ui/maillists.lua")
dofile(MP .. "/ui/edit_maillists.lua")
dofile(MP .. "/ui/compose.lua")
dofile(MP .. "/ui/options.lua")
dofile(MP .. "/ui/settings.lua")
dofile(MP .. "/ui/about.lua")
dofile(MP .. "/player_recipients.lua")
-- sub directories
dofile(MP .. "/ui/init.lua")
-- migrate storage
mail.migrate()
if minetest.get_modpath("mtt") then
if core.get_modpath("mtt") then
dofile(MP .. "/mtt.lua")
dofile(MP .. "/api.spec.lua")
dofile(MP .. "/migrate.spec.lua")

View file

@ -1,93 +1,118 @@
# textdomain: mail
Provided by mt-mods=Bereitgestellt von mt-mods
Version=Version
Licenses=Lizenzen
Expat (code), WTFPL (textures)=Expat (Code), WTFPL (Texturen)
Communication using this system is NOT guaranteed to be private!=Die Kommunikation über dieses System ist NICHT garantiert privat!
Admins are able to view the messages of any player.=Admins können die Nachrichten aller Spieler sehen.
Contributors=Mitwirkende
The mail could not be sent:=Die Mail konnte nicht gesendet werden:
You did not specify any valid recipient.=Sie haben keinen gültigen Empfänger angegeben.
You have mail! Type /mail to read=Sie haben Post! „/mail“ zum Lesen eingeben
You have a new message from @1! Subject: @2=Sie haben eine neue Nachricht von @1! Betreff: @2
To view it, type /mail=Geben Sie zum Anzeigen „/mail“ ein
You could also use the button in your inventory.=Sie können auch die Schaltfläche in Ihrem Inventar verwenden.
Original author=Ursprünglicher Autor
Code=Code
Internationalization=Internationalisierung
Textures=Texturen
Audio=Audio
Provided by mt-mods=Bereitgestellt von mt-mods
Version: @1=Version: @1
Licenses=Lizenzen
Expat (code), WTFPL (textures)=Expat (Code), WTFPL (Texturen)
Communication using this system is NOT guaranteed to be private!=Die Kommunikation über dieses System ist NICHT garantiert privat!
Admins are able to view the messages of any player.=Admins können die Nachrichten aller Spielenden sehen.
Contributors=Mitwirkende
Group by name=Nach Name gruppieren
Group by contribution=Nach Beitrag gruppieren
Note=Anmerkung
Settings=Einstellungen
About=Über
BCC=BCC
Cancel=Abbrechen
Save draft=Entwurf speichern
Send=Senden
No contacts=Keine Kontakte
Subject=Betreff
To=An
CC=CC
Delete=Löschen
New=Neu
Edit=Bearbeiten
Back=Zurück
Name=Name
No drafts=Keine Entwürfe
Edit=Bearbeiten
To=An
Player name=Spielername
That name is already in your contacts=Dieser Name ist bereits in Ihren Kontakten
The contact name cannot be empty.=Der Kontaktname kann nicht leer sein.
Note=Anmerkung
Maillist name=Verteilerlistenname
Desc=Beschreibung
Players=Spieler
That name is already in your mailing lists.=Dieser Name ist bereits in Ihren Verteilerlisten.
The mailing list name cannot be empty.=Der Verteilerlistenname kann nicht leer sein.
Back=Zurück
Mark Read=Als gelesen makieren
Mark Unread=Als ungelesen makieren
Trash=Papierkorb
Inbox=Posteingang
Outbox=Senden
Outbox=Gesendet
Drafts=Entwürfe
Contacts=Kontakte
Mail lists=Verteilerlisten
Options=Einstellungen
Close=Schließen
(No subject)=(Kein Betreff)
Subject=Betreff
Delete=Löschen
New=Neu
Player name=Spielername
That name is already in your contacts=Dieser Name ist bereits in Ihren Kontakten
The contact name cannot be empty.=Der Kontaktname kann nicht leer sein.
Save=Speichern
Maillist name=Verteilerlistenname
Desc=Beschreibung
Players=Spieler
That name is already in your mailing lists.=Dieser Name ist bereits in Ihren Verteilerlisten.
The mailing list name cannot be empty.=Der Verteilerlistenname kann nicht leer sein.
Mark Read=Als gelesen makieren
Mark Unread=Als ungelesen makieren
Mark Spam=Als Spam markieren
Unmark Spam=Kein Spam
Reply=Antworten
Reply all=Allen antworten
Forward=Weiterleiten
Reply only to the sender=Nur dem Absender antworten
Reply to all involved people=Allen beteiligten Personen antworten
Transfer message to other people=Nachricht an andere Personen weiterleiten
Date=Datum
From=Von
Filter=Filter
Allow multiple selection=Mehrfachauswahl zulassen
@1 of @2 selected=@1 von @2 ausgewählt
(Un)select all=Alle aus-/abwählen
No mail=Keine Nachrichten
Reply=Antworten
Reply all=Allen antworten
Forward=Weiterleiten
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Read=Lesen
From=Von
Ascending=Aufsteigend
Descending=Absteigend
(No description)=(Keine Beschreibung)
No maillist=Keine Verteilerliste
Receivers=Empfänger
You have mail! Type /mail to read=Sie haben Post! „/mail“ eingeben zum Lesen
(Un)mute sender=Absender stummschalten/entstummen
Add=Hinzufügen
Remove=Entfernen
CC=CC
Notifications=Benachrichtigungen
Reset=Zurücksetzen
Restore=Wiederherstellen
Empty=Leer
Trash is empty=Papierkorb ist leer
From/To=Von/An
No contacts=Keine Kontakte
The method of delivery to @1 is invalid.=Die Zustellmethode an @1 ist ungültig.
The recipient @1 could not be identified.=Der Empfänger @1 konnte nicht identifiziert werden.
@1 rejected your mail.=@1 hat Ihre Mail abgewiesen.
Chat notifications=Chat-Benachrichtigungen
On join notifications=Bei-Eintritt-Benachrichtigungen
Receive a message in the chat when there is a new message=Eine Nachricht im Chat erhalten, wenn es eine neue Mail gibt
On join notifications=Login-Benachrichtigungen
Receive a message at login when inbox isn't empty=Bei der Anmeldung eine Nachricht erhalten, wenn der Posteingang nicht leer ist
HUD notifications=HUD-Benachrichtigungen
Show an HUD notification when inbox isn't empty=Eine HUD-Benachrichtigung anzeigen, wenn der Posteingang nicht leer ist
Sound notifications=Klang-Benachrichtigungen
Message list=Nachrichtenliste
Play a sound when there is a new message=Einen Ton abspielen, wenn eine neue Mail eingeht
Show unread in different color=Ungelesenes in anderer Farbe anzeigen
Show CC/BCC in different color=CC/BCC in anderer Farbe anzeigen
Default sorting fields=Standardsortierfelder
Other=Anderes
Default sorting field=Standardsortierfeld
Default sorting direction=Standardmäßige Sortierrichtung
Move deleted messages to trash=Gelöschte Nachrichten in den Papierkorb verschieben
Automatic marking read=
Date format:=Datumsformat:
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=Zurücksetzen
Settings=Einstellungen
About=Über
Save=Speichern
Ascending=Aufsteigend
Descending=Absteigend
From/To=Von/An
Automatic marking read=Automatisch als gelesen markieren
Mark a message as read when opened=Nachrichten beim Öffnen als gelesen markieren
Date format=Datumsformat
Timezone offset=Zeitverschiebung
Offset to add to server time.=Verschiebung, die zur Serverzeit addiert wird.
Mute list=Stummgeschaltet-Liste
Notifications=Benachrichtigungen
Message list=Nachrichtenliste
Fields=Felder
Spam=Spam
Other=Anderes
Date and Time=Datum und Uhrzeit
years=Jahren
months=Monaten
weeks=Wochen
@ -96,6 +121,3 @@ hours=Stunden
minuts=Minuten
seconds=Sekunden
@1 ago=Vor @1
Restore=Wiederherstellen
Empty=Leer
Trash is empty=Papierkorb ist leer

View file

@ -1,55 +1,40 @@
# textdomain: mail
The mail could not be sent:=
You did not specify any valid recipient.=
You have mail! Type /mail to read=¡Tienes correo! Escribe /mail para leerlo
You have a new message from @1! Subject: @2=¡Tienes un nuevo mensaje de @1! Asunto: @2
To view it, type /mail=Para verlo, escribe /mail
You could also use the button in your inventory.=También puedes usar el botón de tu inventario.
Original author=
Code=
Internationalization=
Textures=
Audio=
Provided by mt-mods=Proporcionado por mt-mods
Version=Versión
Version: @1=
Licenses=Licencias
Expat (code), WTFPL (textures)=Expat (código), WTFPL (texturas)
Communication using this system is NOT guaranteed to be private!=¡NO se garantiza que la comunicación mediante este sistema sea privada!
Admins are able to view the messages of any player.=Los administradores pueden ver los mensajes de cualquier jugador.
Contributors=Colaboradores
You have a new message from @1! Subject: @2=¡Tienes un nuevo mensaje de @1! Asunto: @2
To view it, type /mail=Para verlo, escribe /mail
You could also use the button in your inventory.=También puedes usar el botón de tu inventario.
Group by name=
Group by contribution=
Note=Nota
Settings=Ajustes
About=Acerca de
BCC=CCO
Cancel=Cancelar
Save draft=Guardar borrador
Send=Enviar
No contacts=Sin contactos
Edit=Editar
No drafts=No hay borradores
Player name=Nombre del jugador
That name is already in your contacts=Ese nombre ya está en tus contactos
The contact name cannot be empty.=El nombre de contacto no puede estar vacío.
Note=Nota
Back=Volver
Save=Guardar
Maillist name=Nombre de la lista de correo
Desc=Desc
Players=Jugadores
That name is already in your mailing lists.=Ese nombre ya está en tus listas de correo.
The mailing list name cannot be empty.=El nombre de lista de correo no puede estar vacío.
Mark Read=Marcar como leído
Mark Unread=Marcar como no leído
Reply=Responder
Reply all=Responder a todos
Forward=Reenviar
Reply only to the sender=Responder solo al remitente
Reply to all involved people=Responder a todas las personas implicadas
Transfer message to other people=Transferir el mensaje a otras personas
From=De
Filter=Filtrar
Allow multiple selection=Permitir selección múltiple
@1 of @2 selected=@1 de @2 seleccionado(s)
(Un)select all=(Des)seleccionar todos
No mail=Sin correo
Subject=Asunto
New=Nuevo
To=Para
CC=CC
Delete=Borrar
Date=Fecha
(No description)=(Sin descripción)
No maillist=Sin lista de correo
Receivers=Recipientes
You have mail! Type /mail to read=¡Tienes correo! Escribe /mail para leerlo
Read=Leído
New=Nuevo
Edit=Editar
Back=Volver
Name=Nombre
No drafts=No hay borradores
Trash=Papelera
Inbox=Entrada
Outbox=Enviados
@ -59,34 +44,75 @@ Mail lists=Listas de correo
Options=Opciones
Close=Cerrar
(No subject)=(Sin asunto)
To=Para
Add=Añadir
Remove=Quitar
Name=Nombre
CC=CC
Notifications=Notificaciones
Chat notifications=Notificaciones de chat
On join notifications=Notificaciones al unirse
HUD notifications=Notificaciones de interfaz
Sound notifications=Notificaciones de sonido
Message list=Lista de mensajes
Show unread in different color=Mostrar no-leídos en diferente color
Show CC/BCC in different color=Mostrar CC/CCO en diferente color
Default sorting fields=Campos a ordenar por defecto
Other=Otros
Move deleted messages to trash=Mover mensajes borrados a la papelera
Automatic marking read=Marcar como leído automáticamente
Receive a message in the chat when there is a new message=Recibir un mensaje en el chat cuando hay correo nuevo
Receive a message at login when inbox isn't empty=Recibir mensaje al conectarse si la bandeja de entrada no está vacía
Show an HUD notification when inbox isn't empty=Mostrar una notificación en la interfaz cuando la bandeja de entrada no está vacía
Play a sound when there is a new message=Emitir un sonido cuando hay un correo nuevo
Mark a message as read when opened=Marcar un mensaje como leído al abrirlo
Reset=Restablecer
Settings=Ajustes
About=Acerca de
Player name=Nombre del jugador
That name is already in your contacts=Ese nombre ya está en tus contactos
The contact name cannot be empty.=El nombre de contacto no puede estar vacío.
Save=Guardar
Maillist name=Nombre de la lista de correo
Desc=Desc
Players=Jugadores
That name is already in your mailing lists.=Ese nombre ya está en tus listas de correo.
The mailing list name cannot be empty.=El nombre de lista de correo no puede estar vacío.
Mark Read=Marcar como leído
Mark Unread=Marcar como no leído
Mark Spam=
Unmark Spam=
Reply=Responder
Reply all=Responder a todos
Forward=Reenviar
Reply only to the sender=Responder solo al remitente
Reply to all involved people=Responder a todas las personas implicadas
Transfer message to other people=Transferir el mensaje a otras personas
Date=Fecha
From=De
Filter=Filtro
Allow multiple selection=Permitir selección múltiple
@1 of @2 selected=@1 de @2 seleccionado(s)
(Un)select all=(Des)seleccionar todos
No mail=Sin correo
Read=Leer
Ascending=Ascendente
Descending=Descendente
(No description)=(Sin descripción)
No maillist=Sin lista de correo
Receivers=Recipientes
(Un)mute sender=
Add=Añadir
Remove=Quitar
Reset=Restablecer
Restore=Restaurar
Empty=Vacío
Trash is empty=La papelera está vacía
From/To=De/Para
No contacts=Sin contactos
The method of delivery to @1 is invalid.=
The recipient @1 could not be identified.=
@1 rejected your mail.=
Chat notifications=Notificaciones de chat
Receive a message in the chat when there is a new message=Recibir un mensaje en el chat cuando hay correo nuevo
On join notifications=Notificaciones al unirse
Receive a message at login when inbox isn't empty=Recibir mensaje al conectarse si la bandeja de entrada no está vacía
HUD notifications=Notificaciones de interfaz
Show an HUD notification when inbox isn't empty=Mostrar una notificación en la interfaz cuando la bandeja de entrada no está vacía
Sound notifications=Notificaciones de sonido
Play a sound when there is a new message=Emitir un sonido cuando hay un correo nuevo
Show unread in different color=Mostrar no-leídos en diferente color
Show CC/BCC in different color=Mostrar CC/CCO en diferente color
Default sorting field=Campo a ordenar por defecto
Default sorting direction=
Move deleted messages to trash=Mover mensajes borrados a la papelera
Automatic marking read=Marcar como leído automáticamente
Mark a message as read when opened=Marcar un mensaje como leído al abrirlo
Date format=Formato de fecha
Timezone offset=
Offset to add to server time.=
Mute list=
Notifications=Notificaciones
Message list=Lista de mensajes
Fields=
Spam=
Other=Otros
Date and Time=
years=años
months=meses
weeks=semanas
@ -95,7 +121,8 @@ hours=horas
minuts=minutos
seconds=segundos
@1 ago=hace @1
Restore=Restaurar
Empty=Vacío
Trash is empty=La papelera está vacía
Date format:=Formato de fecha:
##### not used anymore #####
Version=Versión

View file

@ -1,35 +1,40 @@
# textdomain: mail
The mail could not be sent:=Le mail ne peut pas être envoyé :
You did not specify any valid recipient.=Vous n'avez pas spécifié de destinataire valide.
You have mail! Type /mail to read=Vous avez reçu un mail ! Entrez /mail pour le consulter
You have a new message from @1! Subject: @2=Vous avez un nouveau message de @1 ! Objet : @2
To view it, type /mail=Pour le consulter, entrez /mail
You could also use the button in your inventory.=Vous pouvez également utiliser le bouton dans votre inventaire
Original author=Auteur original
Code=Code
Internationalization=Traduction
Textures=Textures
Audio=Audio
Provided by mt-mods=Fourni par mt-mods
Version=Version
Version: @1=Version : @1
Licenses=Licences
Expat (code), WTFPL (textures)=Expat (code), WTFPL (textures)
Communication using this system is NOT guaranteed to be private!=La communication par ce système n'est pas garantie d'être privée !
Admins are able to view the messages of any player.=Les administrateurs peuvent voir les messages de chaque joueur.
Contributors=Contributeurs
You have a new message from @1! Subject: @2=Vous avez un nouveau message de @1 ! Objet : @2
To view it, type /mail=Pour le consulter, entrez /mail
You could also use the button in your inventory.=Vous pouvez également utiliser le bouton dans votre inventaire
Group by name=Grouper par nom
Group by contribution=Grouper par contribution
Note=Note
Settings=Paramètres
About=À propos
BCC=Cci
Cancel=Annuler
Save draft=Enregistrer le brouillon
Send=Envoyer
No contacts=Aucun contact
Subject=Objet
To=À
CC=Cc
Delete=Supprimer
New=Nouveau
Edit=Modifier
Back=Retour
Name=Nom
No drafts=Aucun brouillon
Edit=Modifier
To=À
Player name=Nom du joueur
That name is already in your contacts=Ce nom est déjà dans vos contacts
The contact name cannot be empty.=Le nom du contact ne peut pas être vide.
Note=Note
Maillist name=Nom de la liste de diffusion
Desc=Desc
Players=Joueurs
That name is already in your mailing lists.=Ce nom est déjà présent dans vos listes de diffusion.
The mailing list name cannot be empty.=Le nom de la liste de diffusion ne peut pas être vide.
Back=Retour
Mark Read=Marquer comme lu
Mark Unread=Marquer non lu
Trash=Corbeille
Inbox=Boîte de réception
Outbox=Envoyés
@ -39,55 +44,75 @@ Mail lists=Listes de diffusion
Options=Options
Close=Fermer
(No subject)=(Sans objet)
Subject=Objet
Delete=Supprimer
New=Nouveau
Date=Date
Filter=Filtre
Allow multiple selection=Autoriser la sélection multiple
@1 of @2 selected=@1 sur @2 sélectionnés
(Un)select all=Tout (dé)sélectionner
No mail=Aucun mail
Player name=Nom du joueur
That name is already in your contacts=Ce nom est déjà dans vos contacts
The contact name cannot be empty.=Le nom du contact ne peut pas être vide.
Save=Sauvegarder
Maillist name=Nom de la liste de diffusion
Desc=Desc
Players=Joueurs
That name is already in your mailing lists.=Ce nom est déjà présent dans vos listes de diffusion.
The mailing list name cannot be empty.=Le nom de la liste de diffusion ne peut pas être vide.
Mark Read=Marquer comme lu
Mark Unread=Marquer non lu
Mark Spam=Marquer comme spam
Unmark Spam=Marquer non-spam
Reply=Répondre
Reply all=Répondre à tous
Forward=Transférer
Reply only to the sender=Répondre uniquement à l'expéditeur
Reply to all involved people=Répondre à toutes les personnes concernées
Transfer message to other people=Transférer le message à d'autres personnes
Read=Lire
Date=Date
From=De
Filter=Filtre
Allow multiple selection=Autoriser la sélection multiple
@1 of @2 selected=@1 sur @2 sélectionnés
(Un)select all=Tout (dé)sélectionner
No mail=Aucun mail
Read=Lire
Ascending=Croissant
Descending=Décroissant
(No description)=(Sans description)
No maillist=Aucune liste de diffusion
Receivers=Destinataires
You have mail! Type /mail to read=Vous avez reçu un mail ! Entrez /mail pour le consulter
(Un)mute sender=(Dé)mettre en sourdine
Add=Ajouter
Remove=Enlever
CC=Cc
Notifications=Notifications
Reset=Réinitialiser
Restore=Restaurer
Empty=Vider
Trash is empty=La corbeille est vide
From/To=De/À
No contacts=Aucun contact
The method of delivery to @1 is invalid.=La méthode d'expédition à @1 est invalide.
The recipient @1 could not be identified.=Le destinataire @1 n'a pas pu être identifié.
@1 rejected your mail.=@1 a rejeté votre mail.
Chat notifications=Notifications dans le tchat
Receive a message in the chat when there is a new message=Recevoir un message dans le tchat lorsqu'un nouveau message est reçu
On join notifications=Notifications à la connexion
Receive a message at login when inbox isn't empty=Recevoir un message à la connexion lorsque la boîte de réception n'est pas vide
HUD notifications=Notifications ATH
Show an HUD notification when inbox isn't empty=Indiquer dans l'ATH que la boîte de réception n'est pas vide
Sound notifications=Notifications sonores
Message list=Liste de messages
Play a sound when there is a new message=Jouer un son lorsqu'un nouveau message est reçu
Show unread in different color=Coloriser les non lus
Show CC/BCC in different color=Coloriser les Cc/Cci
Default sorting fields=Champs de tri par défaut
Other=Autre
Default sorting field=Champ de tri par défaut
Default sorting direction=Direction de tri par défaut
Move deleted messages to trash=Supprimer les messages dans la corbeille
Automatic marking read=Lu automatique
Date format:=Format de la date :
Receive a message in the chat when there is a new message=Recevoir un message dans le tchat lorsqu'un nouveau message est reçu
Receive a message at login when inbox isn't empty=Recevoir un message à la connexion lorsque la boîte de réception n'est pas vide
Show an HUD notification when inbox isn't empty=Indiquer dans l'ATH que la boîte de réception n'est pas vide
Play a sound when there is a new message=Jouer un son lorsqu'un nouveau message est reçu
Mark a message as read when opened=Marquer un message comme lu lorsqu'il est ouvert
Reset=Réinitialiser
Settings=Paramètres
About=À propos
Save=Sauvegarder
Ascending=Croissant
Descending=Décroissant
From/To=De/À
Date format=Format de la date
Timezone offset=Compensation horaire
Offset to add to server time.=Écart de temps à ajouter à l'heure du serveur.
Mute list=Liste de sourdine
Notifications=Notifications
Message list=Liste de messages
Fields=Champs
Spam=Spam
Other=Autre
Date and Time=Date et Heure
years=années
months=mois
weeks=semaines
@ -96,6 +121,3 @@ hours=heures
minuts=minutes
seconds=secondes
@1 ago=Il y a @1
Restore=Restaurer
Empty=Vider
Trash is empty=La corbeille est vide

View file

@ -1,35 +1,40 @@
# textdomain: mail
The mail could not be sent:=
You did not specify any valid recipient.=
You have mail! Type /mail to read=Van egy leveled! Írd /mail az olvasáshoz
You have a new message from @1! Subject: @2=Van egy új üzeneted @1-től Cím: @2
To view it, type /mail=Ahhoz hogy megnézd, írd /mail
You could also use the button in your inventory.=A gombot is tudod használni az inventoridban.
Original author=
Code=
Internationalization=
Textures=
Audio=
Provided by mt-mods=Feltéve, hogy az én mt-mod-om
Version=Verzió
Version: @1=
Licenses=License
Expat (code), WTFPL (textures)=Expat (kód), WTFPL (textúrák)
Communication using this system is NOT guaranteed to be private!=A systemben lévő komunikáció nem garantáltan privát!
Admins are able to view the messages of any player.=Az adminok megtudják nézni minden játékos üzenetjét.
Contributors=Közreműködöttek
You have a new message from @1! Subject: @2=Van egy új üzeneted @1-től Cím: @2
To view it, type /mail=Ahhoz hogy megnézd, írd /mail
You could also use the button in your inventory.=A gombot is tudod használni az inventoridban.
Group by name=
Group by contribution=
Note=Jegyzet
Settings=Beállítások
About=Róla
BCC=BCC
Cancel=Mégse
Save draft=mentés piszkozatként
Send=Küldés
No contacts=
Subject=Cím
To=Neki
CC=CC
Delete=Törlés
New=Új
Edit=Szerkesztés
Back=Visza
Name=Név
No drafts=Nincsenek piszkozatok
Edit=Szerkesztés
To=Neki
Player name=Játékos neve
That name is already in your contacts=A név már a kontaktok között van
The contact name cannot be empty.=A contakt neve nem lehet üres.
Note=Jegyzet
Maillist name=Levelező lista neve
Desc=Desc
Players=Játékosok
That name is already in your mailing lists.=A név már benne van a levelező listában
The mailing list name cannot be empty.=A levelező lista neve nem lehet üres
Back=Visza
Mark Read=Jelöld olvasottként
Mark Unread=Jelöld olvasatlanul
Trash=
Inbox=PostaLáda
Outbox=Elküldött
@ -39,55 +44,75 @@ Mail lists=Levelező lista
Options=Lehetőségek
Close=Bezár
(No subject)=(nincs cím)
Subject=Cím
Delete=Törlés
New=Új
Date=Dátum
Filter=Filterek
Allow multiple selection=
@1 of @2 selected=
(Un)select all=(ne válaszd ki) mindegyik választása
No mail=Nincs levél
Player name=Játékos neve
That name is already in your contacts=A név már a kontaktok között van
The contact name cannot be empty.=A contakt neve nem lehet üres.
Save=Mentés
Maillist name=Levelező lista neve
Desc=Desc
Players=Játékosok
That name is already in your mailing lists.=A név már benne van a levelező listában
The mailing list name cannot be empty.=A levelező lista neve nem lehet üres
Mark Read=Jelöld olvasottként
Mark Unread=Jelöld olvasatlanul
Mark Spam=
Unmark Spam=
Reply=Válasz
Reply all=Válaszmindenkinek
Forward=Továbbítás
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Read=Olvasott
Date=Dátum
From=Tőle
Filter=Filterek
Allow multiple selection=
@1 of @2 selected=
(Un)select all=(ne válaszd ki) mindegyik választása
No mail=Nincs levél
Read=Olvasott
Ascending=Emelkedő
Descending=Sűlyedő
(No description)=(Nincs leírás)
No maillist=Nincs levelező lista
Receivers=
You have mail! Type /mail to read=Van egy leveled! Írd /mail az olvasáshoz
(Un)mute sender=
Add=Hozzáadás
Remove=Elvétel
CC=CC
Notifications=Értesítések
Reset=Viszaállítás
Restore=
Empty=
Trash is empty=
From/To=Tól(től)/neki
No contacts=
The method of delivery to @1 is invalid.=
The recipient @1 could not be identified.=
@1 rejected your mail.=
Chat notifications=Chates értesítések
Receive a message in the chat when there is a new message=
On join notifications=Belépési értesírés
Receive a message at login when inbox isn't empty=
HUD notifications= HUD értesítés
Show an HUD notification when inbox isn't empty=
Sound notifications=Hang értesítés
Message list=Üzenetek listája
Play a sound when there is a new message=
Show unread in different color=Mutasd a nem olvasottakat más színnel
Show CC/BCC in different color=Mutasd a CC-t/BCC-t más színnel
Default sorting fields=Alap válogató terület
Other=
Default sorting field=Alap válogató terület
Default sorting direction=
Move deleted messages to trash=
Automatic marking read=
Date format:=
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=Viszaállítás
Settings=Beállítások
About=Róla
Save=Mentés
Ascending=Emelkedő
Descending=Sűlyedő
From/To=Tól(től)/neki
Date format=
Timezone offset=
Offset to add to server time.=
Mute list=
Notifications=Értesítések
Message list=Üzenetek listája
Fields=
Spam=
Other=
Date and Time=
years=
months=
weeks=
@ -96,6 +121,8 @@ hours=
minuts=
seconds=
@1 ago=
Restore=
Empty=
Trash is empty=
##### not used anymore #####
Version=Verzió

128
locale/mail.id.tr Normal file
View file

@ -0,0 +1,128 @@
# textdomain: mail
The mail could not be sent:=
You did not specify any valid recipient.=
You have mail! Type /mail to read=Anda memiliki surel! ketik /mail untuk membaca
You have a new message from @1! Subject: @2=Anda memiliki pesan baru dari @1! Subjek: @2
To view it, type /mail=Untuk melihatnya, ketik /mail
You could also use the button in your inventory.=Anda juga dapat menggunakan tombol dalam inventaris Anda.
Original author=
Code=
Internationalization=
Textures=
Audio=
Provided by mt-mods=Disediakan oleh mt-mods
Version: @1=
Licenses=Lisensi
Expat (code), WTFPL (textures)=Expat (kode), WTFPL (tekstur)
Communication using this system is NOT guaranteed to be private!=Komunikasi dengan sistem ini TIDAK dijamin bersifat pribadi!
Admins are able to view the messages of any player.=Admin dapat melihat pesan dari setiap pemain.
Contributors=Kontributor
Group by name=
Group by contribution=
Note=Catatan
Settings=Pengaturan
About=Tentang
BCC=BCC
Cancel=Batal
Save draft=Simpan Draf
Send=Kirim
Subject=Subjek
To=Kpd
CC=CC
Delete=Hapus
New=Baru
Edit=Sunting
Back=Kembali
Name=Nama
No drafts=Tidak ada draf
Trash=Sampah
Inbox=Kotak Masuk
Outbox=Kotak Keluar
Drafts=Draf
Contacts=Kontak
Mail lists=Milis
Options=Pengaturan
Close=Tutup
(No subject)=(Tanpa subjek)
Player name=Nama pemain
That name is already in your contacts=Nama itu sudah ada dalam kontak Anda
The contact name cannot be empty.=Nama kontak tidak boleh kosong.
Save=Simpan
Maillist name=Nama milis
Desc=Deskripsi
Players=Pemain
That name is already in your mailing lists.=Nama tersebut sudah ada dalam milis Anda.
The mailing list name cannot be empty.=Nama milis tidak boleh kosong.
Mark Read=Tndai Sdh Dibaca
Mark Unread=Tndai Blm Dibaca
Mark Spam=
Unmark Spam=
Reply=Balas
Reply all=Balas Semua
Forward=Teruskan
Reply only to the sender=Balas hanya kepada pengirim
Reply to all involved people=Balas kepada semua orang yang terlibat
Transfer message to other people=Teruskan pesan kepada orang lain
Date=Tanggal
From=Dari
Filter=Saring
Allow multiple selection=Izinkan beberapa pilihan
@1 of @2 selected=@1 dari @2 dipilih
(Un)select all=Batal/Pilih Semua
No mail=Tidak ada surat
Read=Baca
Ascending=Menaik
Descending=Menurun
(No description)=(Tidak ada deskripsi)
No maillist=Tidak ada milis
Receivers=Penerima
(Un)mute sender=
Add=Tambah
Remove=Hapus
Reset=Atur Ulang
Restore=Kembalikan
Empty=Kosong
Trash is empty=Sampah kosong
From/To=Dari/Kpd
No contacts=Tidak ada kontak
The method of delivery to @1 is invalid.=
The recipient @1 could not be identified.=
@1 rejected your mail.=
Chat notifications=Pemberitahuan obrolan
Receive a message in the chat when there is a new message=Terima pesan dalam obrolan ketika ada pesan baru
On join notifications=Pemberitahuan saat bergabung
Receive a message at login when inbox isn't empty=Terima pesan saat masuk log ketika kotak masuk tidak kosong
HUD notifications=Pemberitahuan HUD
Show an HUD notification when inbox isn't empty=Tampilkan pemberitahuan HUD saat kotak masuk tidak kosong
Sound notifications=Pemberitahuan suara
Play a sound when there is a new message=Putar suara saat ada pesan baru
Show unread in different color=Tampilkan belum dibaca dengan warna berbeda
Show CC/BCC in different color=Tampilkan CC/BCC dengan warna berbeda
Default sorting field=Kolom pengurutan bawaan
Default sorting direction=Arah pengurutan bawaan
Move deleted messages to trash=Pindahkan pesan yang dihapus ke sampah
Automatic marking read=Penandaan otomatis sudah dibaca
Mark a message as read when opened=Tandai pesan sebagai sudah dibaca saat dibuka
Date format=Format tanggal
Timezone offset=
Offset to add to server time.=
Mute list=
Notifications=Pemberitahuan
Message list=Daftar Pesan
Fields=
Spam=
Other=Lain-Lain
Date and Time=
years=tahun
months=bulan
weeks=pekan
days=hari
hours=jam
minuts=menit
seconds=detik
@1 ago=@1 yang lalu
##### not used anymore #####
Version=Versi

View file

@ -1,35 +1,40 @@
# textdomain: mail
The mail could not be sent:=
You did not specify any valid recipient.=
You have mail! Type /mail to read=Você recebeu e-mail! Tecle /mail para ler
You have a new message from @1! Subject: @2=Você tem uma mensagem de @1! Assunto: @2
To view it, type /mail=Para visualizar a mensagem, digite /mail
You could also use the button in your inventory.=Você também pode usar o botão do seu inventário.
Original author=
Code=
Internationalization=
Textures=
Audio=
Provided by mt-mods=
Version=
Version: @1=
Licenses=
Expat (code), WTFPL (textures)=
Communication using this system is NOT guaranteed to be private!=A comunicação usando este sistema não possui garantia de privacidade
Admins are able to view the messages of any player.=Administradores poderão ler as mensagens de qualquer jogador
Contributors=
You have a new message from @1! Subject: @2=Você tem uma mensagem de @1! Assunto: @2
To view it, type /mail=Para visualizar a mensagem, digite /mail
You could also use the button in your inventory.=Você também pode usar o botão do seu inventário.
Group by name=
Group by contribution=
Note=Nota
Settings=Ajustes
About=Sobre
BCC=BCC
Cancel=Cancelar
Save draft=Salvar rascunho
Send=Enviar
No contacts=
Subject=Assunto
To=Para
CC=CC
Delete=Apagar
New=Novo
Edit=Editar
Back=Voltar
Name=Nome
No drafts=Sem rascunhos
Edit=Editar
To=Para
Player name=Nome do jogador
That name is already in your contacts=Esse nome já consta em sua lista de contatos
The contact name cannot be empty.=Informe o nome do destinatário
Note=Nota
Maillist name=Nome da lista de discussão
Desc=Descrição
Players=Jogador
That name is already in your mailing lists.=Esse nome ja está sendo usado em sua lista de discussões
The mailing list name cannot be empty.=O nome da lista de discussões deve ser informado
Back=Voltar
Mark Read=Marcar como lido
Mark Unread=Marcar como não lido
Trash=
Inbox=Entrada
Outbox=Enviadas
@ -39,55 +44,75 @@ Mail lists=Lista de correios
Options=Opções
Close=Fechar
(No subject)=(Sem assunto)
Subject=Assunto
Delete=Apagar
New=Novo
Date=Data
Filter=Filtrar
Allow multiple selection=Permitir selecionar vários
@1 of @2 selected=
(Un)select all=Desmarcar todos
No mail=Sem e-mails no momento
Player name=Nome do jogador
That name is already in your contacts=Esse nome já consta em sua lista de contatos
The contact name cannot be empty.=Informe o nome do destinatário
Save=Salvar
Maillist name=Nome da lista de discussão
Desc=Descrição
Players=Jogador
That name is already in your mailing lists.=Esse nome ja está sendo usado em sua lista de discussões
The mailing list name cannot be empty.=O nome da lista de discussões deve ser informado
Mark Read=Marcar como lido
Mark Unread=Marcar como não lido
Mark Spam=
Unmark Spam=
Reply=Responder
Reply all=Responder Todos
Forward=Encaminhar
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Read=Ler
Date=Data
From=De
Filter=Filtrar
Allow multiple selection=Permitir selecionar vários
@1 of @2 selected=
(Un)select all=Desmarcar todos
No mail=Sem e-mails no momento
Read=Ler
Ascending=Ascendente
Descending=Descendente
(No description)=(sem descrição)
No maillist=Sem lista de discussão
Receivers=
You have mail! Type /mail to read=Você recebeu e-mail! Tecle /mail para ler
(Un)mute sender=
Add=Adicionar
Remove=Remover
CC=CC
Notifications=Notificações
Reset=
Restore=
Empty=
Trash is empty=
From/To=De/Para
No contacts=
The method of delivery to @1 is invalid.=
The recipient @1 could not be identified.=
@1 rejected your mail.=
Chat notifications=Notificação de conversa
Receive a message in the chat when there is a new message=
On join notifications=Notificação ao entrar
Receive a message at login when inbox isn't empty=
HUD notifications=Notificação no HUD
Show an HUD notification when inbox isn't empty=
Sound notifications=
Message list=Lista de mensagens
Play a sound when there is a new message=
Show unread in different color=Exibir mensagens não lidas em uma cor diferente
Show CC/BCC in different color=Exibir mensagens com copia em uma cor diferente
Default sorting fields=Ordenamento de campos padrão
Other=
Default sorting field=Ordenamento de campo padrão
Default sorting direction=
Move deleted messages to trash=
Automatic marking read=
Date format:=
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=
Settings=Ajustes
About=Sobre
Save=Salvar
Ascending=Ascendente
Descending=Descendente
From/To=De/Para
Date format=
Timezone offset=
Offset to add to server time.=
Mute list=
Notifications=Notificações
Message list=Lista de mensagens
Fields=
Spam=
Other=
Date and Time=
years=
months=
weeks=
@ -96,6 +121,3 @@ hours=
minuts=
seconds=
@1 ago=
Restore=
Empty=
Trash is empty=

123
locale/mail.ru.tr Normal file
View file

@ -0,0 +1,123 @@
# textdomain: mail
The mail could not be sent:=Невозможно отправить почту:
You did not specify any valid recipient.=Вы не указали получателя.
You have mail! Type /mail to read=У вас есть почта! Наберите /mail, чтобы прочитать
You have a new message from @1! Subject: @2=У вас новое сообщение от @1! Тема: @2
To view it, type /mail=Чтобы посмотреть, наберите /mail
You could also use the button in your inventory.=Вы также можете использовать кнопку в Вашем инвентаре.
Original author=Автор оригинала
Code=Код
Internationalization=Перевод
Textures=Текстуры
Audio=Аудио
Provided by mt-mods=Предоставлено mt-mods
Version: @1=Версия: @1
Licenses=Лицензии
Expat (code), WTFPL (textures)=Expat (код), WTFPL (текстуры)
Communication using this system is NOT guaranteed to be private!=Конфиденциальность общения с использованием этой системы НЕ гарантируется!
Admins are able to view the messages of any player.=Админ может читать сообщения любых игроков.
Contributors=Участники
Group by name=Группировать по имени
Group by contribution=Группировать по участию
Note=Заметка
Settings=Настройки
About=О...
BCC=С.копия
Cancel=Отмена
Save draft=Сохр. черновик
Send=Отправить
Subject=Тема
To=Кому
CC=Копия
Delete=Удалить
New=Новое
Edit=Изменить
Back=Назад
Name=Имя
No drafts=Нет черновиков
Trash=Корзина
Inbox=Входящие
Outbox=Исходящие
Drafts=Черновики
Contacts=Контакты
Mail lists=Списки рассылки
Options=Опции
Close=Закрыть
(No subject)=(без темы)
Player name=Имя игрока
That name is already in your contacts=Это имя уже есть в Ваших контактах
The contact name cannot be empty.=Имя контакта не может быть пустым.
Save=Сохранить
Maillist name=Название списка
Desc=Описание
Players=Игроки
That name is already in your mailing lists.=Это имя уже есть в ваших списках рассылки.
The mailing list name cannot be empty.=Название списка не может быть пустым.
Mark Read=Отм. прочитано
Mark Unread=Отм. непрочитано
Mark Spam=Отм. спам
Unmark Spam=Снять отм. спам
Reply=Ответить
Reply all=Ответить всем
Forward=Переслать
Reply only to the sender=Ответить только отправителю
Reply to all involved people=Ответить всем участникам
Transfer message to other people=Переслать сообщение другим игрокам
Date=Дата
From=От
Filter=Фильтр
Allow multiple selection=Разрешить выбор нескольких
@1 of @2 selected=@1 из @2 выбраны
(Un)select all=Снять выбор со всех
No mail=Нет почты
Read=Прочитано
Ascending=Возрастание
Descending=Убывание
(No description)=(Нет описания)
No maillist=Не список рассылки
Receivers=Получатели
(Un)mute sender=Вкл. звук для отправителя
Add=Добавить
Remove=Удалить
Reset=Сбросить
Restore=Восстановить
Empty=Очистить
Trash is empty=Корзина пуста
From/To=От/Кому
No contacts=Нет контактов
The method of delivery to @1 is invalid.=Метод доставки для @1 не действителен.
The recipient @1 could not be identified.=Невозможно идентифицировать получателя @1.
@1 rejected your mail.=@1 отклонил ваше письмо.
Chat notifications=Уведомления в чате
Receive a message in the chat when there is a new message=Получать сообщение в чате, когда приходит новое сообщение
On join notifications=Уведомления при присоединении
Receive a message at login when inbox isn't empty=Получать сообщение при входе, когда есть письма во Входящих
HUD notifications=HUD уведомления
Show an HUD notification when inbox isn't empty=Показывать уведомление HUD, если папка «Входящие» не пуста
Sound notifications=Звуковые уведомления
Play a sound when there is a new message=Проигрывать звук, когда приходит новое сообщение
Show unread in different color=Показывать не прочтенные други цветом
Show CC/BCC in different color=Показывать Копию/Скрытую копию другим цветом
Default sorting field=Поле для сортировки по умолчанию
Default sorting direction=Направление сортировки по умолчанию
Move deleted messages to trash=Перемещать удаленные сообщения в корзину
Automatic marking read=Автоматически отмечать прочтение
Mark a message as read when opened=Отмечать сообщение как прочитанное при открытии
Date format=Формат даты
Timezone offset=Временная зона
Offset to add to server time.=Добавлять ко времени сервера
Mute list=Заглушить список
Notifications=Уведомления
Message list=Список сообщений
Fields=Поля
Spam=Спам
Other=Другое
Date and Time=Дата и Время
years=лет
months=месяцев
weeks=недель
days=дней
hours=часов
minuts=минут
seconds=секунд
@1 ago=@1 назад

123
locale/mail.uk.tr Normal file
View file

@ -0,0 +1,123 @@
# textdomain: mail
The mail could not be sent:=Неможливо відправити пошту:
You did not specify any valid recipient.=Ви не вказали отримувача.
You have mail! Type /mail to read=У вас є пошта! Введіть /mail для прочитання
You have a new message from @1! Subject: @2=У вас нове повідомлення від @1! Тема: @2
To view it, type /mail=Введіть /mail аби прочитати це
You could also use the button in your inventory.=Також ви можете використовувати кнопку в вашому інвентарі.
Original author=Автор оригіналу
Code=Код
Internationalization=Переклад
Textures=Текстури
Audio=Аудіо
Provided by mt-mods=Надано mt-mods
Version: @1=Версія: @1
Licenses=Ліцензії
Expat (code), WTFPL (textures)=Expat (код), WTFPL (текстури)
Communication using this system is NOT guaranteed to be private!=Конфіденційність використання цієї системи НЕ гарантовано є приватною!
Admins are able to view the messages of any player.=Адмін може читати повідомлення всіх гравців.
Contributors=Учасники
Group by name=Групувати по імені
Group by contribution=Групувати по участю
Note=Замітка
Settings=Налаштування
About=Про...
BCC=ВСС
Cancel=Скасувати
Save draft=Збер.чернетку
Send=Відправити
Subject=Тема
To=Кому
CC=Копія
Delete=Видалити
New=Створити
Edit=Редагувати
Back=Назад
Name=Ім'я
No drafts=Немає чернеток
Trash=Кошик
Inbox=Вхідні
Outbox=Вихідні
Drafts=Чернетки
Contacts=Контакти
Mail lists=Списки розсилки
Options=Опції
Close=Закрити
(No subject)=(без теми)
Player name=Ім'я гравця
That name is already in your contacts=Це ім'я вже збережено у ваших контактах
The contact name cannot be empty.=Ім'я контакта не може бути пустим.
Save=Зберегти
Maillist name=Назва списку
Desc=Опис
Players=Гравці
That name is already in your mailing lists.=Це ім'я вже є у ваших списках розсилки.
The mailing list name cannot be empty.=Назва списка не може бути пустою.
Mark Read=Прочитано
Mark Unread=Непрочитано
Mark Spam=Відм. спам
Unmark Spam=Скас. спам
Reply=Відповісти
Reply all=Відповісти усім
Forward=Переслати
Reply only to the sender=Відповісти лише відправнику
Reply to all involved people=Відповісти усім учасникам
Transfer message to other people=Переслати це повідомлення іншим людям
Date=Дата
From=Від
Filter=Фільтр
Allow multiple selection=Дозволити вибір декількох
@1 of @2 selected=@1 з @2 вибрані
(Un)select all=Зняти вибір з усіх
No mail=Немає пошти
Read=Прочитано
Ascending=Зростання
Descending=Зменшення
(No description)=(Немає опису)
No maillist=Немає списків розсилки
Receivers=Отримувачі
(Un)mute sender=Увімк. звук для відправника
Add=Додати
Remove=Видалити
Reset=Скинути
Restore=Відновити
Empty=Очистити
Trash is empty=Кошик пустий
From/To=Від/До
No contacts=Немає контактів
The method of delivery to @1 is invalid.=Метод доставки для @1 не дійсний.
The recipient @1 could not be identified.=Не знайдено @1.
@1 rejected your mail.=@1 відхилив ваше лист.
Chat notifications=Повідомлення у чаті
Receive a message in the chat when there is a new message=Отримувати повідомленння у чаті при отриманні нового повідомлення
On join notifications=Повідомлення при приєднанні
Receive a message at login when inbox isn't empty=Отримувати повідомлення при приєднанні коли у теці «Вхідні» є нові листи
HUD notifications=HUD повідомлення
Show an HUD notification when inbox isn't empty=Показувати HUD повідомлення коли у теці «Вхідні» є нові листи
Sound notifications=Звукові повідомлення
Play a sound when there is a new message=Програвати звук при новому повідомленні
Show unread in different color=Показувати непрочитані іншим кольором
Show CC/BCC in different color=Показувати копію/приховану копію іншим кольором
Default sorting field=Поле для сортування за замовч.
Default sorting direction=Напрям сортування за замовч.
Move deleted messages to trash=Переміщати видалені повідомлення до кошика
Automatic marking read=Автоматично відмічати прочитані
Mark a message as read when opened=Відмічати повідомлення як прочитане, коли відкрито
Date format=Формат дати
Timezone offset=Часовий пояс
Offset to add to server time.=Додавати до часу сервера.
Mute list=Заглушити список
Notifications=Повідомлення
Message list=Список повідомлень
Fields=Поля
Spam=Спам
Other=Інше
Date and Time=Дата й час
years=років
months=місяців
weeks=тижнів
days=днів
hours=годин
minuts=хвилин
seconds=секунд
@1 ago=@1 тому

View file

@ -1,102 +1,129 @@
# textdomain: mail
Provided by mt-mods=
Version=
Licenses=
Expat (code), WTFPL (textures)=
Communication using this system is NOT guaranteed to be private!=
Admins are able to view the messages of any player.=
Contributors=
You have a new message from @1! Subject: @2=
To view it, type /mail=
You could also use the button in your inventory.=
The mail could not be sent:=无法发送邮件:
You did not specify any valid recipient.=
You have mail! Type /mail to read=您有新邮件,请使用 /mail 查看。
You have a new message from @1! Subject: @2=您有一封来自 @1 的新邮件,主题为“@2”。
To view it, type /mail=请使用 /mail 命令查看。
You could also use the button in your inventory.=您也可以使用物品清单里的按键。
Original author=
Code=
Internationalization=
Textures=
Audio=
Provided by mt-mods=由 mt-mods 提供
Version: @1=
Licenses=许可证
Expat (code), WTFPL (textures)=Expat代码WTFPL材质
Communication using this system is NOT guaranteed to be private!=这个系统不适用于私密沟通!
Admins are able to view the messages of any player.=管理员可以查看所有玩家的邮件。
Contributors=贡献者
Group by name=
Group by contribution=
Note=备注
Settings=设置
About=关于
BCC=密送
Cancel=取消
Save draft=保存草稿
Send=发送
No contacts=
Subject=主题
To=收件人
CC=抄送
Delete=删除
New=新
Edit=编辑
Back=返回
Name=名字
#if new means new mail, it would be New=新邮件
No drafts=没有草稿
Edit=编辑
To=收件人
Player name=玩家名字
That name is already in your contacts=
The contact name cannot be empty.=
Note=备注
Maillist name=建组名
Desc=描述
Players=玩家
That name is already in your mailing lists.=
The mailing list name cannot be empty.=
Back=返回
Mark Read=标记为已读
Mark Unread=标记为未读
Trash=
Trash=垃圾箱
Inbox=收件箱
Outbox=已发送
Drafts=草稿
Contacts=通讯录
Mail lists=建组
Options=
Options=选项
Close=关闭
(No subject)=(无主题)
Subject=主题
Delete=删除
New=新
Date=时间
Filter=筛选
Allow multiple selection=允许多选
@1 of @2 selected=
(Un)select all=(取消)选中所有
No mail=无邮件
(No subject)=(无主题)
Player name=玩家名字
That name is already in your contacts=这个玩家已经在您的通讯录里。
The contact name cannot be empty.=联系人名字不能为空。
Save=保存
Maillist name=邮件列表名称
Desc=描述
Players=玩家
That name is already in your mailing lists.=这个玩家已经在您的邮件列表里。
The mailing list name cannot be empty.=邮件列表名称不能为空。
Mark Read=标记为已读
Mark Unread=标记为未读
Mark Spam=标记为垃圾邮件
Unmark Spam=取消标记为垃圾邮件
Reply=回复
Reply all=回复所有
Forward=转发
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Read=浏览
Reply only to the sender=只回复给发件人
Reply to all involved people=回复给所有人
Transfer message to other people=将邮件转发给其他人
Date=时间
From=发件人
(No description)=(无描述)
No maillist=无建组
Receivers=
You have mail! Type /mail to read=
Add=添加
Remove=移除
CC=抄送
Notifications=
Chat notifications=
On join notifications=
HUD notifications=
Sound notifications=
Message list=
Show unread in different color=
Show CC/BCC in different color=
Default sorting fields=
Other=
Move deleted messages to trash=
Automatic marking read=
Date format:=
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=
Settings=
About=关于
Save=保存
Filter=筛选
Allow multiple selection=允许多选
@1 of @2 selected=已选中 @2 项中的 @1 项
(Un)select all=(取消)选中所有
No mail=无邮件
Read=浏览
Ascending=升序
Descending=降序
From/To=
years=
months=
weeks=
days=
hours=
minuts=
seconds=
@1 ago=
Restore=
Empty=
Trash is empty=
(No description)=(无描述)
No maillist=无邮件列表
Receivers=收件人
(Un)mute sender=屏蔽或取消屏蔽发件人
Add=添加
Remove=移除
Reset=重置
Restore=恢复
Empty=空的
Trash is empty=垃圾箱为空
From/To=发件人或收件人
No contacts=无联系人
The method of delivery to @1 is invalid.=无法将邮件发送给 @1。
The recipient @1 could not be identified.=无法找到收件人“@1”。
@1 rejected your mail.=@1 不接收您的邮件。
Chat notifications=在聊天记录中显示通知
Receive a message in the chat when there is a new message=收到新邮件时在聊天记录中显示通知。
On join notifications=加入服务器时显示通知
Receive a message at login when inbox isn't empty=加入服务器且由新邮件时显示通知。
HUD notifications=HUD 通知
Show an HUD notification when inbox isn't empty=收到新邮件时通过 HUD 显示通知。
Sound notifications=提示音
Play a sound when there is a new message=收到新邮件时播放提示音
Show unread in different color=使用不同的颜色标记未读邮件
Show CC/BCC in different color=使用不同的颜色标记抄送和密送邮件
Default sorting field=排序依据
Default sorting direction=排序方向
Move deleted messages to trash=将已删除的邮件移至垃圾箱
Automatic marking read=自动将邮件标记为已读
Mark a message as read when opened=打开邮件时自动将邮件标记为已读
Date format=日期格式
Timezone offset=
Offset to add to server time.=
Mute list=屏蔽列表
Notifications=通知
Message list=邮件列表
Fields=
Spam=垃圾邮件
Other=其它
Date and Time=
years=年
months=月
weeks=周
days=天
hours=小时
minuts=分钟
seconds=秒
@1 ago=@1前
##### not used anymore #####
Version=版本

View file

@ -1,101 +1,128 @@
# textdomain: mail
Provided by mt-mods=
Version=
Licenses=
Expat (code), WTFPL (textures)=
Communication using this system is NOT guaranteed to be private!=
Admins are able to view the messages of any player.=
Contributors=
You have a new message from @1! Subject: @2=
To view it, type /mail=
You could also use the button in your inventory.=
The mail could not be sent:=無法發送郵件:
You did not specify any valid recipient.=
You have mail! Type /mail to read=您有新郵件,請使用 /mail 查看。
You have a new message from @1! Subject: @2=您有一封來自 @1 的新郵件,主題為“@2”。
To view it, type /mail=請使用 /mail 指令查看。
You could also use the button in your inventory.=您也可以使用物品欄裡的按鍵。
Original author=
Code=
Internationalization=
Textures=
Audio=
Provided by mt-mods=由 mt-mods 提供
Version: @1=
Licenses=許可證
Expat (code), WTFPL (textures)=Expat源碼WTFPL材質
Communication using this system is NOT guaranteed to be private!=此系統不適合私密交流!
Admins are able to view the messages of any player.=管理員可以查看所有玩家的郵件。
Contributors=貢獻者
Group by name=
Group by contribution=
Note=備註
Settings=設置
About=關於
BCC=密件副本
Cancel=取消
Save draft=儲存草稿
Send=發送
No contacts=
Subject=主旨
To=收件人
CC=副本
Delete=刪除
New=新建
Edit=編輯
Back=返回
Name=名稱
No drafts=沒有草稿
Edit=編輯
To=收件人
Player name=玩家名稱
That name is already in your contacts=
The contact name cannot be empty.=
Note=備註
Maillist name=郵件列表名稱
Desc=描述
Players=玩家
That name is already in your mailing lists.=
The mailing list name cannot be empty.=
Back=返回
Mark Read=標記已讀
Mark Unread=標記未讀
Trash=
Trash=垃圾箱
Inbox=收件箱
Outbox=寄件備份
Drafts=草稿
Contacts=聯繫人
Mail lists=郵件列表
Options=
Options=選項
Close=關閉
(No subject)=(沒有主旨)
Subject=主旨
Delete=刪除
New=新建
Date=日期
Filter=
Allow multiple selection=
@1 of @2 selected=
(Un)select all=
No mail=沒有郵件
Player name=玩家名稱
That name is already in your contacts=玩家已經在您的通訊錄中。
The contact name cannot be empty.=聯繫人名字不能為空。
Save=儲存
Maillist name=郵件列表名稱
Desc=描述
Players=玩家
That name is already in your mailing lists.=玩家已經在您的郵件列表中。
The mailing list name cannot be empty.=郵件列表名稱不能為空。
Mark Read=標記已讀
Mark Unread=標記未讀
Mark Spam=標記垃圾郵件
Unmark Spam=取消標記垃圾郵件
Reply=回覆
Reply all=回覆所有人
Forward=轉寄
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Reply only to the sender=僅回覆給寄件人
Reply to all involved people=回覆給所有人
Transfer message to other people=將郵件轉發給所有人
Date=日期
From=寄件人
Filter=篩選
Allow multiple selection=允許多選
@1 of @2 selected=已選擇 @2 項中的 @1 項。
(Un)select all=(取消)選中所有
No mail=沒有郵件
Read=閱讀
From=寄件者
(No description)=(沒有描述)
No maillist=沒有郵件列表
Receivers=
You have mail! Type /mail to read=
Add=加入
Remove=移除
CC=副本
Notifications=
Chat notifications=
On join notifications=
HUD notifications=
Sound notifications=
Message list=
Show unread in different color=
Show CC/BCC in different color=
Default sorting fields=
Other=
Move deleted messages to trash=
Automatic marking read=
Date format:=
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=
Settings=
About=關於
Save=儲存
Ascending=
Descending=
From/To=
years=
months=
weeks=
days=
hours=
minuts=
seconds=
@1 ago=
Restore=
Empty=
Trash is empty=
(No description)=(沒有描述)
No maillist=沒有郵件列表
Receivers=收件人
(Un)mute sender=(取消)屏蔽寄件人
Add=加入
Remove=移除
Reset=重置
Restore=恢復
Empty=空
Trash is empty=垃圾箱為空
From/To=寄件人或收件人
No contacts=沒有聯繫人
The method of delivery to @1 is invalid.=無法將郵件發送給 @1。
The recipient @1 could not be identified.=無法找到收件人“@1”。
@1 rejected your mail.=@1 不接收您的郵件
Chat notifications=在聊天室中顯示通知
Receive a message in the chat when there is a new message=收到新郵件時在聊天室中顯示通知。
On join notifications=加入伺服器時顯示通知
Receive a message at login when inbox isn't empty=加入伺服器且有新郵件時顯示通知。
HUD notifications=HUD 通知
Show an HUD notification when inbox isn't empty=收到新郵件時在 HUD 中顯示通知。
Sound notifications=提示聲
Play a sound when there is a new message=收到新郵件時播放提示聲
Show unread in different color=使用不同的顏色標記未讀郵件
Show CC/BCC in different color=使用不同的顏色標記副本和密件副本
Default sorting field=排序方式
Default sorting direction=排序順序
Move deleted messages to trash=將已刪除的郵件移至垃圾箱
Automatic marking read=自動將郵件標記已讀
Mark a message as read when opened=打開郵件時自動將郵件標記已讀
Date format=日期格式
Timezone offset=
Offset to add to server time.=
Mute list=屏蔽列表
Notifications=通知
Message list=郵件列表
Fields=
Spam=垃圾郵件
Other=其他
Date and Time=
years=年
months=月
weeks=周
days=日
hours=小時
minuts=分鐘
seconds=秒
@1 ago=@1前
##### not used anymore #####
Version=版本

View file

@ -1,35 +1,40 @@
# textdomain: mail
The mail could not be sent:=
You did not specify any valid recipient.=
You have mail! Type /mail to read=
You have a new message from @1! Subject: @2=
To view it, type /mail=
You could also use the button in your inventory.=
Original author=
Code=
Internationalization=
Textures=
Audio=
Provided by mt-mods=
Version=
Version: @1=
Licenses=
Expat (code), WTFPL (textures)=
Communication using this system is NOT guaranteed to be private!=
Admins are able to view the messages of any player.=
Contributors=
You have a new message from @1! Subject: @2=
To view it, type /mail=
You could also use the button in your inventory.=
Group by name=
Group by contribution=
Note=
Settings=
About=
BCC=
Cancel=
Save draft=
Send=
No contacts=
Subject=
To=
CC=
Delete=
New=
Edit=
Back=
Name=
No drafts=
Edit=
To=
Player name=
That name is already in your contacts=
The contact name cannot be empty.=
Note=
Maillist name=
Desc=
Players=
That name is already in your mailing lists.=
The mailing list name cannot be empty.=
Back=
Mark Read=
Mark Unread=
Trash=
Inbox=
Outbox=
@ -39,55 +44,75 @@ Mail lists=
Options=
Close=
(No subject)=
Subject=
Delete=
New=
Date=
Filter=
Allow multiple selection=
@1 of @2 selected=
(Un)select all=
No mail=
Player name=
That name is already in your contacts=
The contact name cannot be empty.=
Save=
Maillist name=
Desc=
Players=
That name is already in your mailing lists.=
The mailing list name cannot be empty.=
Mark Read=
Mark Unread=
Mark Spam=
Unmark Spam=
Reply=
Reply all=
Forward=
Reply only to the sender=
Reply to all involved people=
Transfer message to other people=
Read=
Date=
From=
Filter=
Allow multiple selection=
@1 of @2 selected=
(Un)select all=
No mail=
Read=
Ascending=
Descending=
(No description)=
No maillist=
Receivers=
You have mail! Type /mail to read=
(Un)mute sender=
Add=
Remove=
CC=
Notifications=
Reset=
Restore=
Empty=
Trash is empty=
From/To=
No contacts=
The method of delivery to @1 is invalid.=
The recipient @1 could not be identified.=
@1 rejected your mail.=
Chat notifications=
Receive a message in the chat when there is a new message=
On join notifications=
Receive a message at login when inbox isn't empty=
HUD notifications=
Show an HUD notification when inbox isn't empty=
Sound notifications=
Message list=
Play a sound when there is a new message=
Show unread in different color=
Show CC/BCC in different color=
Default sorting fields=
Other=
Default sorting field=
Default sorting direction=
Move deleted messages to trash=
Automatic marking read=
Date format:=
Receive a message in the chat when there is a new message=
Receive a message at login when inbox isn't empty=
Show an HUD notification when inbox isn't empty=
Play a sound when there is a new message=
Mark a message as read when opened=
Reset=
Settings=
About=
Save=
Ascending=
Descending=
From/To=
Date format=
Timezone offset=
Offset to add to server time.=
Mute list=
Notifications=
Message list=
Fields=
Spam=
Other=
Date and Time=
years=
months=
weeks=
@ -96,6 +121,3 @@ hours=
minuts=
seconds=
@1 ago=
Restore=
Empty=
Trash is empty=

View file

@ -1,76 +0,0 @@
# textdomain: mail
# author: nyomi
Provided by mt-mods=Feltéve, hogy az én mt-mod-om
Version=Verzió
Licenses=License
Expat (code), WTFPL (textures)=Expat (kód), WTFPL (textúrák)
Communication using this system is NOT guaranteed to be private!=A systemben lévő komunikáció nem garantáltan privát!
Admins are able to view the messages of any player.=Az adminok megtudják nézni minden játékos üzenetjét.
Contributors=Közreműködöttek
Note=Jegyzet
Settings=Beállítások
About=Róla
You have a new message from @1! Subject: @2=Van egy új üzeneted @1-től Cím: @2
To view it, type /mail=Ahhoz hogy megnézd, írd /mail
You could also use the button in your inventory.=A gombot is tudod használni az inventoridban.
BCC=BCC
Cancel=Mégse
Save draft=mentés piszkozatként
Send=Küldés
Subject=Cím
To=Neki
CC=CC
Name=Név
No drafts=Nincsenek piszkozatok
Edit=Szerkesztés
New=Új
Delete=Törlés
Inbox=PostaLáda
Outbox=Elküldött üzenetek
Drafts=Piszkozatok
Contacts=Contaktok
Mail lists=Levelező lista
Options=Lehetőségek
Close=Bezár
(No subject)=(nincs cím)
Player name=Játékos neve
That name is already in your contacts=A név már a kontaktok között van
The contact name cannot be empty.=A contakt neve nem lehet üres.
Save=Mentés
Maillist name=Levelező lista neve
Desc=Desc
Players=Játékosok
That name is already in your mailing lists.=A név már benne van a levelező listában
The mailing list name cannot be empty.=A levelező lista neve nem lehet üres
Mark Read=Jelöld olvasottként
Mark Unread=Jelöld olvasatlanul
From=Tőle
Read=Olvasott
Filter=Filterek
Allow multiple selection=
@1 selected=@1 kiválasztva
(Un)select all=(ne válaszd ki) mindegyik választása
No mail=Nincs levél
Reply=Válasz
Reply all=Válaszmindenkinek
Forward=Továbbítás
Date=Dátum
Ascending=Emelkedő
Descending=Sűlyedő
(No description)=(Nincs leírás)
No maillist=Nincs levelező lista
You have mail! Type /mail to read=Van egy leveled! Írd /mail az olvasáshoz
Add=Hozzáadás
Remove=Elvétel
Back=Visza
Notifications=Értesítések
Chat notifications=Chates értesítések
On join notifications=Belépési értesírés
HUD notifications= HUD értesítés
Sound notifications=Hang értesítés
Message list=Üzenetek listája
Show unread in different color=Mutasd a nem olvasottakat más színnel
Show CC/BCC in different color=Mutasd a CC-t/BCC-t más színnel
Default sorting fields=Alap válogató terület
From/To=Tól(től)/neki
Reset=Viszaállítás

View file

@ -1,13 +1,13 @@
local STORAGE_VERSION_KEY = "@@version"
local CURRENT_VERSION = 3.1
local function migrate_v1_to_v3()
local file = io.open(minetest.get_worldpath().."/mail.db", "r")
local file = io.open(core.get_worldpath().."/mail.db", "r")
assert(file)
print("[mail] Migration from v1 to v3 database")
local data = file:read("*a")
local oldmails = minetest.deserialize(data)
local oldmails = core.deserialize(data)
file:close()
for name, oldmessages in pairs(oldmails) do
@ -28,7 +28,7 @@ local function migrate_v1_to_v3()
-- rename file
print("[mail,v1] migration done, renaming old mail.db")
os.rename(minetest.get_worldpath().."/mail.db", minetest.get_worldpath().."/mail.db.old")
os.rename(core.get_worldpath().."/mail.db", core.get_worldpath().."/mail.db.old")
end
local function read_json_file(path)
@ -36,7 +36,7 @@ local function read_json_file(path)
local content = {}
if file then
local json = file:read("*a")
content = minetest.parse_json(json or "[]") or {}
content = core.parse_json(json or "[]") or {}
file:close()
end
return content
@ -44,13 +44,13 @@ end
-- migrate from v2 to v3 database
local function migrate_v2_to_v3()
local maildir = minetest.get_worldpath().."/mails"
minetest.mkdir(maildir) -- if necessary (eg. first login)
local maildir = core.get_worldpath().."/mails"
core.mkdir(maildir) -- if necessary (eg. first login)
print("[mail] Migration from v2 to v3 database")
-- defer execution until auth-handler ready (first server-step)
minetest.after(0, function()
for playername, _ in minetest.get_auth_handler().iterate() do
core.after(0, function()
for playername, _ in core.get_auth_handler().iterate() do
local entry = mail.get_storage_entry(playername)
local player_contacts = read_json_file(maildir .. "/contacts/" .. playername .. ".json")
@ -80,20 +80,149 @@ local function migrate_v2_to_v3()
end)
end
local function search_box(playername, box, uuid)
local e = mail.get_storage_entry(playername)
for _, m in ipairs(e[box]) do
if m.id == uuid then
return { time = m.time, from = m.from, to = m.to, cc = m.cc, bcc = m.bcc, subject = m.subject, body = m.body } end
end
return false
end
local function search_boxes(playername, boxes, uuid)
local result
for _, b in ipairs(boxes) do
result = search_box(playername, b, uuid)
if result then return result end
end
end
local function is_uuid_existing(uuid)
local boxes = {"inbox", "outbox", "drafts", "trash"}
if mail.storage.get_keys then
for _, k in ipairs(mail.storage:get_keys()) do
if string.sub(k,1,5) == "mail/" then
local p = string.sub(k, 6)
local result = search_boxes(p, boxes, uuid)
if result then return result end
end
end
else
for p, _ in core.get_auth_handler().iterate() do
local result = search_boxes(p, boxes, uuid)
if result then return result end
end
end
return false
end
local function are_message_sames(a, b)
return a.time == b.time
and a.from == b.from
and a.to == b.to
and a.cc == b.cc
and a.bcc == b.bcc
and a.subject == b.subject
and a.body == b.body
end
local function replace_other_player_message_uuid(p, m, uuid, new_uuid)
local er = mail.get_storage_entry(p)
for _, r in ipairs(er.inbox) do
if r.id == uuid and not are_message_sames(m, r) then
r.id = new_uuid
end
end
for _, r in ipairs(er.outbox) do
if r.id == uuid and not are_message_sames(m, r) then
r.id = new_uuid
end
end
for _, r in ipairs(er.drafts) do
if r.id == uuid and not are_message_sames(m, r) then
r.id = new_uuid
end
end
for _, r in ipairs(er.trash) do
if r.id == uuid and not are_message_sames(m, r) then
r.id = new_uuid
end
end
mail.set_storage_entry(p, er)
end
local function fix_box_duplicate_uuids(playername, box)
local e = mail.get_storage_entry(playername)
for _, m in ipairs(e[box]) do
local uuid = m.id
local exists = is_uuid_existing(uuid)
if exists and not are_message_sames(exists, m) then
local new_uuid = mail.new_uuid() -- generates a new uuid to replace doublons
if mail.storage.get_keys then
for _, k in ipairs(mail.storage:get_keys()) do
if string.sub(k,1,5) == "mail/" then
local p = string.sub(k, 6)
replace_other_player_message_uuid(p, m, uuid, new_uuid)
end
end
else
for p, _ in core.get_auth_handler().iterate() do
replace_other_player_message_uuid(p, m, uuid, new_uuid)
end
end
end
end
end
local function fix_player_duplicate_uuids(playername)
fix_box_duplicate_uuids(playername, "inbox")
fix_box_duplicate_uuids(playername, "outbox")
fix_box_duplicate_uuids(playername, "drafts")
fix_box_duplicate_uuids(playername, "trash")
end
-- repair database for uuid doublons
local function repair_storage()
-- iterate through players
-- get_keys() was introduced in 5.7
if mail.storage.get_keys then
for _, k in ipairs(mail.storage:get_keys()) do
if string.sub(k,1,5) == "mail/" then
local p = string.sub(k, 6)
fix_player_duplicate_uuids(p)
end
end
else
core.after(0, function()
for p, _ in core.get_auth_handler().iterate() do
fix_player_duplicate_uuids(p)
end
end)
end
end
function mail.migrate()
-- check for v2 storage first, v1-migration might have set the v3-flag already
local version = mail.storage:get_int(STORAGE_VERSION_KEY)
if version < 3 then
local version = mail.storage:get_float(STORAGE_VERSION_KEY)
if version < math.floor(CURRENT_VERSION) then
-- v2 to v3
migrate_v2_to_v3()
mail.storage:set_int(STORAGE_VERSION_KEY, 3)
mail.storage:set_float(STORAGE_VERSION_KEY, CURRENT_VERSION)
end
-- check for v1 storage
local v1_file = io.open(minetest.get_worldpath().."/mail.db", "r")
local v1_file = io.open(core.get_worldpath().."/mail.db", "r")
if v1_file then
-- v1 to v3
migrate_v1_to_v3()
mail.storage:set_int(STORAGE_VERSION_KEY, 3)
mail.storage:set_float(STORAGE_VERSION_KEY, CURRENT_VERSION)
end
-- repair storage for uuid doublons
if version < CURRENT_VERSION then
repair_storage()
mail.storage:set_float(STORAGE_VERSION_KEY, CURRENT_VERSION)
end
end

View file

@ -1,3 +1,4 @@
name = mail
description = ingame mail-system
optional_depends = canonical_name,default,mtt,unified_inventory
optional_depends = canonical_name,default,mtt,unified_inventory,sfinv_buttons,beerchat

View file

@ -1,7 +1,7 @@
mtt.register("setup", function(callback)
-- create test players
local auth_handler = minetest.get_auth_handler()
local auth_handler = core.get_auth_handler()
auth_handler.set_password("player1", "")
auth_handler.set_password("player2", "")
auth_handler.set_password("player3", "")

View file

@ -1,8 +1,8 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
minetest.register_on_joinplayer(function(player)
minetest.after(2, function(name)
core.register_on_joinplayer(function(player)
core.after(2, function(name)
local entry = mail.get_storage_entry(name)
local messages = entry.inbox
mail.hud_update(name, messages)
@ -16,8 +16,8 @@ minetest.register_on_joinplayer(function(player)
end
if unreadcount > 0 and mail.get_setting(name, "onjoin_notifications") then
minetest.chat_send_player(name,
minetest.colorize(mail.colors.new, "(" .. unreadcount .. ") " .. S("You have mail! Type /mail to read")))
core.chat_send_player(name,
core.colorize(mail.get_color("new"), "(" .. unreadcount .. ") " .. S("You have mail! Type /mail to read")))
end
end, player:get_player_name())
end)

53
player_recipients.lua Normal file
View file

@ -0,0 +1,53 @@
-- translation
local S = mail.S
local has_canonical_name = core.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 = core.get_player_by_name(name)
if player then
if mail.get_setting(name, "chat_notifications") == true then
core.chat_send_player(name, mail_alert)
if core.get_modpath("unified_inventory") or core.get_modpath("sfinv_buttons") then
core.chat_send_player(name, inventory_alert)
end
end
if mail.get_setting(name, "sound_notifications") == true then
core.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 core.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

BIN
screenshot_1.4.0_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 KiB

BIN
screenshot_1.4.0_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 KiB

View file

@ -31,7 +31,7 @@ function mail.get_storage_entry(playername)
entry = populate_entry()
else
-- deserialize existing entry
local e = minetest.parse_json(str)
local e = core.parse_json(str)
entry = populate_entry(e)
end
@ -55,20 +55,23 @@ end
local function save_worker()
for key, entry in pairs(save_queued_entries) do
-- write to backend
mail.storage:set_string(key, minetest.write_json(entry))
mail.storage:set_string(key, core.write_json(entry))
end
-- clear queue
save_queued_entries = {}
-- clear cached entries
cache = {}
-- save every second
minetest.after(1, save_worker)
core.after(1, save_worker)
end
-- start save-worker loop
save_worker()
-- save on shutdown
minetest.register_on_shutdown(save_worker)
core.register_on_shutdown(save_worker)
-- get a mail by id from the players in- or outbox
function mail.get_message(playername, msg_id)
@ -151,38 +154,46 @@ function mail.sort_messages(messages, sortfield, descending, filter)
return results
end
-- marks a mail read by its id
function mail.mark_read(playername, msg_ids)
local function mark_property(playername, property, msg_ids, value, hud_update)
local entry = mail.get_storage_entry(playername)
if type(msg_ids) ~= "table" then -- if this is not a table
msg_ids = { msg_ids }
end
for _, read_msg_id in ipairs(msg_ids) do
for _, property_msg_id in ipairs(msg_ids) do
for _, entry_msg in ipairs(entry.inbox) do
if entry_msg.id == read_msg_id then
entry_msg.read = true
if entry_msg.id == property_msg_id then
entry_msg[property] = value
end
end
end
mail.set_storage_entry(playername, entry)
if hud_update then
mail.hud_update(playername, entry.inbox)
end
return
end
-- marks a mail read by its id
function mail.mark_read(playername, msg_ids)
mark_property(playername, "read", msg_ids, true, true)
return
end
-- marks a mail unread by its id
function mail.mark_unread(playername, msg_ids)
local entry = mail.get_storage_entry(playername)
if type(msg_ids) ~= "table" then -- if this is not a table
msg_ids = { msg_ids }
end
for _, unread_msg_id in ipairs(msg_ids) do
for _, entry_msg in ipairs(entry.inbox) do
if entry_msg.id == unread_msg_id then
entry_msg.read = false
end
end
end
mail.set_storage_entry(playername, entry)
mark_property(playername, "read", msg_ids, false, true)
return
end
-- marks a mail as a spam
function mail.mark_spam(playername, msg_ids)
mark_property(playername, "spam", msg_ids, true)
return
end
-- marks a mail as a non-spam
function mail.unmark_spam(playername, msg_ids)
mark_property(playername, "spam", msg_ids, false)
return
end
@ -335,6 +346,9 @@ function mail.get_maillist_by_name(playername, listname)
local entry = mail.get_storage_entry(playername)
for _, list in ipairs(entry.lists) do
if list.name == listname then
if not list.players then
list.players = {}
end
return list
end
end
@ -351,6 +365,9 @@ function mail.update_maillist(playername, list, old_list_name)
end
end
-- insert
if not list.players then
list.players = {}
end
table.insert(entry.lists, list)
mail.set_storage_entry(playername, entry)
end
@ -367,63 +384,72 @@ function mail.delete_maillist(playername, listname)
end
end
function mail.extractMaillists(receivers_string, maillists_owner)
local receivers = mail.parse_player_list(receivers_string) -- extracted receivers
local function extract_maillists_main(receivers, maillists_owner, expanded_receivers, seen)
if type(receivers) == "string" then
receivers = mail.parse_player_list(receivers)
end
-- extract players from mailing lists
while string.find(receivers_string, "@") do
local globalReceivers = mail.parse_player_list(receivers_string) -- receivers including maillists
receivers = {}
for _, receiver in ipairs(globalReceivers) do
local receiverInfo = receiver:split("@") -- @maillist
if receiverInfo[1] and receiver == "@" .. receiverInfo[1] then
local maillist = mail.get_maillist_by_name(maillists_owner, receiverInfo[1])
for _, receiver in pairs(receivers) do
if seen[receiver] then
-- Do not add/expand this receiver as it is already seen
core.log("verbose", ("mail: ignoring duplicate receiver %q during maillist expansion"):format(receiver))
elseif string.find(receiver, "^@") then
seen[receiver] = true
local listname = string.sub(receiver, 2)
local maillist = mail.get_maillist_by_name(maillists_owner, listname)
if maillist then
for _, playername in ipairs(maillist.players) do
table.insert(receivers, playername)
core.log("verbose", ("mail: expanding maillist %q"):format(listname))
for _, entry in ipairs(maillist.players) do
extract_maillists_main(entry, maillists_owner, expanded_receivers, seen)
end
end
else -- in case of player
table.insert(receivers, receiver)
end
end
receivers_string = mail.concat_player_list(receivers)
end
return receivers
end
function mail.get_setting_default_value(setting_name)
local default_values = {
chat_notifications = true,
onjoin_notifications = true,
hud_notifications = true,
sound_notifications = true,
unreadcolorenable = true,
cccolorenable = true,
defaultsortfield = 3,
defaultsortdirection = 1,
trash_move_enable = true,
auto_marking_read = true,
date_format = "%Y-%m-%d %X",
}
return default_values[setting_name]
end
function mail.get_setting(playername, setting_name)
local entry = mail.get_storage_entry(playername)
if entry.settings[setting_name] ~= nil then
return entry.settings[setting_name]
else
return mail.get_setting_default_value(setting_name)
seen[receiver] = true
core.log("verbose", ("mail: adding %q to receiver list during maillist expansion"):format(receiver))
table.insert(expanded_receivers, receiver)
end
end
end
function mail.extract_maillists(receivers, maillists_owner)
local expanded_receivers = {}
extract_maillists_main(receivers, maillists_owner, expanded_receivers, {})
return expanded_receivers
end
function mail.get_setting_default_value(key)
return mail.settings[key].default
end
function mail.get_setting(playername, key)
local entry = mail.get_storage_entry(playername)
local value = (entry.settings[key] == nil
and {mail.get_setting_default_value(key)}
or {entry.settings[key]})[1]
if mail.settings[key].sync then -- in case this setting is shared with another mod
local sync_value = mail.settings[key].sync(playername) -- get new value
if sync_value then
value = sync_value
mail.set_setting(playername, key, value, true) -- update the setting in mail storage and don't transfer it again
end
end
return value
end
-- add or update a setting
function mail.set_setting(playername, key, value)
function mail.set_setting(playername, key, value, not_transfer)
local entry = mail.get_storage_entry(playername)
entry.settings[key] = value
local valid_value = value
if mail.settings[key].check then
valid_value = mail.settings[key].check(playername, value)
end
entry.settings[key] = valid_value
mail.set_storage_entry(playername, entry)
if not not_transfer and mail.settings[key].transfer then -- in case this setting is shared with another mod
mail.settings[key].transfer(playername, valid_value)
end
end
function mail.reset_settings(playername)
@ -432,7 +458,7 @@ function mail.reset_settings(playername)
mail.set_storage_entry(playername, entry)
end
function mail.pairsByKeys(t, f)
function mail.pairs_by_keys(t, f)
-- http://www.lua.org/pil/19.3.html
local a = {}
for n in pairs(t) do table.insert(a, n) end

BIN
textures/search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,54 +1,128 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local FORMNAME = "mail:about"
local groups = {
{ "o", S("Original author")},
{ "c", S("Code")},
{ "i", S("Internationalization")},
{ "t", S("Textures")},
{ "a", S("Audio")},
}
local contributors = {
{ name = "Cheapie", groups = {"o", "c"} },
{ name = "aBlueShadow", groups = {"c"} },
{ name = "APercy", groups = {"i"} },
{ name = "Athozus", groups = {"c", "i"} },
{ name = "BuckarooBanzay", groups = {"c"} },
{ name = "Chache", groups = {"i"} },
{ name = "Dennis Jenkins", groups = {"c"} },
{ name = "Emojigit", groups = {"c", "i"} },
{ name = "Eredin", groups = {"i"} },
{ name = "fluxionary", groups = {"c"} },
{ name = "imre84", groups = {"c"} },
{ name = "Muhammad Rifqi Priyo Susanto", groups = {"i"} },
{ name = "NatureFreshMilk", groups = {"c", "t"} },
{ name = "Niklp", groups = {"c", "i"} },
{ name = "Nuno Filipe Povoa", groups = {"a"} },
{ name = "nyomi", groups = {"i"} },
{ name = "OgelGames", groups = {"c"} },
{ name = "Panquesito7", groups = {"c"} },
{ name = "Peter Nerlich", groups = {"c"} },
{ name = "Rubenwardy", groups = {"c"} },
{ name = "savilli", groups = {"c"} },
{ name = "Singularis", groups = {"c"} },
{ name = "SX", groups = {"c"} },
{ name = "TheTrueBeginner", groups = {"i"} },
{ name = "Thomas Rudin", groups = {"c"} },
{ name = "Toby1710", groups = {"c"} },
{ name = "whosit", groups = {"c"} },
{ name = "Wuzzy", groups = {"i"} },
{ name = "y5nw", groups = {"c", "i"} },
}
function mail.show_about(name)
mail.selected_idxs.contributor_grouping[name] = tonumber(mail.selected_idxs.contributor_grouping[name]) or 1
local formspec = [[
size[10,6;]
tabheader[0.3,1;optionstab;]] .. S("Settings") .. "," .. S("About") .. [[;2;false;false]
tabheader[0,0;optionstab;]] .. S("Settings") .. "," .. S("About") .. [[;2;false;false]
button[9.35,0;0.75,0.5;back;X]
label[0,0.8;Mail]
label[0,1.2;]] .. S("Provided by mt-mods") .. [[]
label[0,1.6;]] .. S("Version") .. [[ : 1.3.0]
label[0,2.2;]] .. S("Licenses") .. [[ :]
label[0.2,2.6;]] .. S("Expat (code), WTFPL (textures)") .. [[]
label[0,3.2;https://github.com/mt-mods/mail]
label[0,3.6;https://content.minetest.net/packages/mt-mods/mail]
textarea[0.5,4.8;4,5.5;;]] .. S("Note") .. [[;]] ..
box[0,0;3,0.45;]] .. mail.get_color("highlighted") .. [[]
label[0.2,0;Mail]
label[0.2,0.5;]] .. S("Provided by mt-mods") .. [[]
label[0.2,0.9;]] .. S("Version: @1", "1.5.0-dev") .. [[]
box[0,1.5;3,0.45;]] .. mail.get_color("highlighted") .. [[]
label[0.2,1.5;]] .. S("Licenses") .. [[]
label[0.2,2.0;]] .. S("Expat (code), WTFPL (textures)") .. [[]
box[0,2.6;3,0.45;]] .. mail.get_color("highlighted") .. [[]
label[0.2,2.6;]] .. S("Note") .. [[]
textarea[0.5,3.15;4,5.5;;;]] ..
S("Communication using this system is NOT guaranteed to be private!") .. " " ..
S("Admins are able to view the messages of any player.") .. [[]
tablecolumns[color;text;text]
table[5,0.75;4.9,5.5;contributors;]] ..
mail.colors.header .. [[,]] .. S("Contributors") .. [[,,]] ..
mail.colors.important .. [[,Cheapie,Initial idea/project,]] ..
[[,Rubenwardy,Lua/UI improvements,]] ..
[[,BuckarooBanzay,Clean-ups\, Refactoring,]] ..
[[,Athozus,Boxes\, Maillists\, UI\, Settings,]] ..
[[,fluxionary,Minor fixups,]] ..
[[,SX,Various fixes\, UI,]] ..
[[,Toby1710,UX fixes,]] ..
[[,Peter Nerlich,CC\, BCC,]] ..
[[,Niklp,German translation,]] ..
[[,Emojigit,Traditional Chinese trans.,]] ..
[[,Dennis Jenkins,UX fixes,]] ..
[[,Thomas Rudin,Maintenance,]] ..
[[,NatureFreshMilk,Maintenance,]] ..
[[,imre84,UI fixes,]] ..
[[,Chache,Spanish translation,]] ..
[[,APercy,Brazilian Portuguese trans.,]] ..
[[,Nuno Filipe Povoa,mail_notif.ogg,]] ..
[[,TheTrueBeginner,Simplified Chinese trans.,]] ..
[[,nyomi,Hungarian translation,]] ..
[[,whosit,UI fixes,]] ..
[[,Wuzzy,German translation]
]] .. mail.theme
button[0,5.7;2,0.5;github;GitHub]
button[2,5.7;2,0.5;contentdb;ContentDB]
minetest.show_formspec(name, FORMNAME, formspec)
box[4,0;3,0.45;]] .. mail.get_color("highlighted") .. [[]
label[4.2,0;]] .. S("Contributors") .. [[]
dropdown[4,0.75;6.4;contributor_grouping;]]
.. S("Group by name") .. ","
.. S("Group by contribution") .. ";" .. mail.selected_idxs.contributor_grouping[name] .. [[;true]
]]
local contributor_list, contributor_columns = {}
if mail.selected_idxs.contributor_grouping[name] == 2 then
contributor_columns = "color;text"
local sorted = {}
for _, g in ipairs(groups) do
sorted[g[1]] = {}
end
for _, c in ipairs(contributors) do
for _, g in ipairs(c.groups) do
table.insert(sorted[g] or {}, c.name)
end
end
for _, g in ipairs(groups) do
table.insert(contributor_list, mail.get_color("header") .. "," .. g[2])
for _, c in ipairs(sorted[g[1]]) do
table.insert(contributor_list, "," .. c)
end
end
else
contributor_columns = "text;text"
for _, c in ipairs(contributors) do
for _, g in ipairs(groups) do
local index = table.indexof(c.groups, g[1])
if index >= 1 then
if index == 1 then
table.insert(contributor_list, c.name)
else
table.insert(contributor_list, "")
end
table.insert(contributor_list, g[2])
end
end
end
end
formspec = formspec .. ("tablecolumns[%s]"):format(contributor_columns) ..
("table[4,1.6;5.9,4.65;contributors;%s]"):format(table.concat(contributor_list, ","))
formspec = formspec .. mail.theme
core.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
@ -65,5 +139,14 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
elseif fields.optionstab == "2" then
mail.selected_idxs.optionstab[playername] = 2
mail.show_about(playername)
elseif fields.github then
core.chat_send_player(playername, "https://github.com/mt-mods/mail")
elseif fields.contentdb then
core.chat_send_player(playername, "https://content.minetest.net/packages/mt-mods/mail")
elseif fields.contributor_grouping then
mail.selected_idxs.contributor_grouping[playername] = fields.contributor_grouping
mail.show_about(playername)
end
end)

View file

@ -1,8 +1,7 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local FORMNAME = "mail:compose"
local msg_id = {}
function mail.show_compose(name, to, subject, body, cc, bcc, id)
local formspec = [[
@ -21,32 +20,25 @@ function mail.show_compose(name, to, subject, body, cc, bcc, id)
]] .. mail.theme
formspec = string.format(formspec,
minetest.formspec_escape(to) or "",
minetest.formspec_escape(cc) or "",
minetest.formspec_escape(bcc) or "",
minetest.formspec_escape(subject) or "",
minetest.formspec_escape(body) or "")
core.formspec_escape(to) or "",
core.formspec_escape(cc) or "",
core.formspec_escape(bcc) or "",
core.formspec_escape(subject) or "",
core.formspec_escape(body) or "")
if id then
msg_id[name] = id
else
msg_id[name] = nil
end
mail.selected_idxs.message[name] = id or mail.new_uuid()
minetest.show_formspec(name, FORMNAME, formspec)
core.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
if fields.send then
local id = mail.new_uuid()
if msg_id[name] then
id = msg_id[name]
end
local id = mail.selected_idxs.message[name] or mail.new_uuid()
if (fields.to == "" and fields.cc == "" and fields.bcc == "") or fields.body == "" then
-- if mail is invalid then store it as a draft
mail.save_draft({
@ -71,7 +63,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
body = fields.body,
})
if not success then
minetest.chat_send_player(name, err)
core.chat_send_player(name, err)
return
end
@ -95,7 +87,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end
minetest.after(0.5, function()
core.after(0.5, function()
mail.selected_idxs.drafts[name] = nil
mail.show_mail_menu(name)
end)
@ -117,8 +109,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
elseif fields.draft then
local id = mail.new_uuid()
if msg_id[name] then
id = msg_id[name]
if mail.selected_idxs.message[name] then
id = mail.selected_idxs.message[name]
end
mail.save_draft({
id = id,

View file

@ -1,5 +1,5 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local FORMNAME = "mail:contacts"
@ -9,15 +9,15 @@ local contacts_formspec = "size[8,9;]" .. mail.theme .. [[
button[6,1.60;2,0.5;delete;]] .. S("Delete") .. [[]
button[6,8.25;2,0.5;back;]] .. S("Back") .. [[]
tablecolumns[color;text;text]
table[0,0;5.75,9;contacts;]] .. mail.colors.header .. "," .. S("Name") .. "," .. S("Note")
table[0,0;5.75,9;contacts;]] .. mail.get_color("header") .. "," .. S("Name") .. "," .. S("Note")
function mail.show_contacts(name)
local formspec = contacts_formspec .. mail.compile_contact_list(name, mail.selected_idxs.contacts[name])
minetest.show_formspec(name, FORMNAME, formspec)
core.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
@ -26,8 +26,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local contacts = mail.get_contacts(name)
if fields.contacts then
local evt = minetest.explode_table_event(fields.contacts)
for k, _, i in mail.pairsByKeys(contacts) do
local evt = core.explode_table_event(fields.contacts)
for k, _, i in mail.pairs_by_keys(contacts) do
if i == evt.row - 1 then
mail.selected_idxs.contacts[name] = tonumber(k)
break
@ -58,7 +58,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
-- except if it was the last. Then determine the new last
local found = false
local last = nil
for k in mail.pairsByKeys(contacts) do
for k in mail.pairs_by_keys(contacts) do
if found then
mail.selected_idxs.contacts[name] = tonumber(k)
break

View file

@ -1,6 +1,5 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
function mail.show_drafts(name)
local trash_tab = ""
@ -8,20 +7,20 @@ function mail.show_drafts(name)
trash_tab = "," .. S("Trash")
end
local drafts_formspec = "size[8.5,10;]" .. mail.theme .. [[
local drafts_formspec = "size[8.5,11;]" .. mail.theme .. [[
tabheader[0.3,1;boxtab;]] ..
S("Inbox") .. "," .. S("Outbox").. "," .. S("Drafts") .. trash_tab .. [[;3;false;false]
button[6,0.10;2.5,0.5;new;]] .. S("New") .. [[]
button[6,0.95;2.5,0.5;edit;]] .. S("Edit") .. [[]
button[6,1.70;2.5,0.5;delete;]] .. S("Delete") .. [[]
button[6,6.8;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,7.6;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,8.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,9.5;2.5,0.5;quit;]] .. S("Close") .. [[]
button[6,8.0;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,8.8;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,9.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,10.5;2.5,0.5;quit;]] .. S("Close") .. [[]
tablecolumns[color;text;text]
table[0,0.7;5.75,9.35;drafts;]] .. mail.colors.header .. "," .. S("To") .. "," .. S("Subject")
table[0,0.7;5.75,10.35;drafts;]] .. mail.get_color("header") .. "," .. S("To") .. "," .. S("Subject")
local formspec = { drafts_formspec }
local entry = mail.get_storage_entry(name)
@ -33,14 +32,14 @@ function mail.show_drafts(name)
for _, message in ipairs(messages) do
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(message.to)
formspec[#formspec + 1] = core.formspec_escape(message.to)
formspec[#formspec + 1] = ","
if message.subject ~= "" then
if string.len(message.subject) > 30 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = core.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(message.subject)
formspec[#formspec + 1] = core.formspec_escape(message.subject)
end
else
formspec[#formspec + 1] = S("(No subject)")
@ -54,5 +53,5 @@ function mail.show_drafts(name)
else
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("No drafts") .. "]"
end
minetest.show_formspec(name, "mail:drafts", table.concat(formspec, ""))
core.show_formspec(name, "mail:drafts", table.concat(formspec, ""))
end

View file

@ -1,5 +1,5 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local FORMNAME = "mail:editcontact"
@ -24,12 +24,12 @@ function mail.show_edit_contact(name, contact_name, note, illegal_name_hint)
end
formspec = formspec .. mail.theme
formspec = string.format(formspec,
minetest.formspec_escape(contact_name or ""),
minetest.formspec_escape(note or ""))
minetest.show_formspec(name, FORMNAME, formspec)
core.formspec_escape(contact_name or ""),
core.formspec_escape(note or ""))
core.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end

View file

@ -1,5 +1,5 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local FORMNAME = "mail:editmaillist"
@ -25,13 +25,13 @@ function mail.show_edit_maillist(playername, maillist_name, desc, players, illeg
end
formspec = formspec .. mail.theme
formspec = string.format(formspec,
minetest.formspec_escape(maillist_name or ""),
minetest.formspec_escape(desc or ""),
minetest.formspec_escape(players or ""))
minetest.show_formspec(playername, FORMNAME, formspec)
core.formspec_escape(maillist_name or ""),
core.formspec_escape(desc or ""),
core.formspec_escape(players or ""))
core.show_formspec(playername, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
@ -59,7 +59,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
desc = fields.desc,
players = mail.parse_player_list(fields.players)
}, old_maillist.name)
maillists[mail.selected_idxs.maillists[name]] = nil
end
else
mail.update_maillist(name, {

View file

@ -1,6 +1,6 @@
-- Getter to filter and sort messages on demand
local function messageGetter(messages, sortfield, ascending, filter)
local function message_getter(messages, sortfield, ascending, filter)
local results
return function()
if not results then
@ -14,12 +14,14 @@ local function nonempty(x)
return ((type(x)=="table")and(#x>0))
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "mail:inbox" and formname ~= "mail:outbox"
and formname ~= "mail:drafts" and formname ~= "mail:trash" then
return
elseif fields.quit then
return
end
if fields.quit then
return true
end
-- Get player name and handle / convert common input fields
@ -52,19 +54,24 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local entry = mail.get_storage_entry(name)
local messagesDrafts = entry.drafts
local messagesTrash = entry.trash
local getInbox = messageGetter(entry.inbox, inboxsortfield, sortdirection == "2", filter)
local getOutbox = messageGetter(entry.outbox, outboxsortfield, sortdirection == "2", filter)
local getInbox = message_getter(entry.inbox, inboxsortfield, sortdirection == "2", filter)
local getOutbox = message_getter(entry.outbox, outboxsortfield, sortdirection == "2", filter)
-- Hanmdle formspec event
if fields.inbox then -- inbox table
local evt = minetest.explode_table_event(fields.inbox)
local evt = core.explode_table_event(fields.inbox)
if evt.row == 1 then -- header
if mail.selected_idxs.sortfield[name] == evt.column-1 then -- if already this field, then change direction
mail.selected_idxs.sortdirection[name] = mail.selected_idxs.sortdirection[name] == "2" and "1" or "2"
end
mail.selected_idxs.sortfield[name] = evt.column-1 -- update column
mail.show_mail_menu(name)
return
return true
end
local inbox = getInbox()[evt.row-1]
if not inbox then
mail.show_mail_menu(name)
return true
end
if mail.selected_idxs.multipleselection[name] then
if not mail.selected_idxs.inbox[name] then
@ -73,7 +80,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local selected_id = 0
if mail.selected_idxs.inbox[name] and #mail.selected_idxs.inbox[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.inbox[name]) do
if getInbox()[evt.row-1].id == selected_msg then
if inbox.id == selected_msg then
selected_id = i
table.remove(mail.selected_idxs.inbox[name], i)
break
@ -81,13 +88,16 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end
if selected_id == 0 then
table.insert(mail.selected_idxs.inbox[name], getInbox()[evt.row-1].id)
table.insert(mail.selected_idxs.inbox[name], inbox.id)
mail.selected_idxs.message[name] = inbox.id
end
else
mail.selected_idxs.inbox[name] = { (getInbox()[evt.row-1] or {}).id }
mail.selected_idxs.inbox[name] = { inbox.id }
mail.selected_idxs.message[name] = inbox.id
end
if evt.type == "DCL" and getInbox()[evt.row-1] then
mail.show_message(name, getInbox()[evt.row-1].id)
if evt.type == "DCL" then
mail.selected_idxs.message[name] = inbox.id
mail.show_message(name, inbox.id)
else
mail.show_mail_menu(name)
end
@ -95,14 +105,19 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
if fields.outbox then -- outbox table
local evt = minetest.explode_table_event(fields.outbox)
local evt = core.explode_table_event(fields.outbox)
if evt.row == 1 then -- header
if mail.selected_idxs.sortfield[name] == evt.column-1 then -- if already this field, then change direction
mail.selected_idxs.sortdirection[name] = mail.selected_idxs.sortdirection[name] == "2" and "1" or "2"
end
mail.selected_idxs.sortfield[name] = evt.column-1 -- update column
mail.show_mail_menu(name)
return
return true
end
local outbox = getOutbox()[evt.row-1]
if not outbox then
mail.show_mail_menu(name)
return true
end
if mail.selected_idxs.multipleselection[name] then
if not mail.selected_idxs.outbox[name] then
@ -111,7 +126,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local selected_id = 0
if mail.selected_idxs.outbox[name] and #mail.selected_idxs.outbox[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.outbox[name]) do
if getOutbox()[evt.row-1].id == selected_msg then
if outbox.id == selected_msg then
selected_id = i
table.remove(mail.selected_idxs.outbox[name], i)
break
@ -119,13 +134,16 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end
if selected_id == 0 then
table.insert(mail.selected_idxs.outbox[name], getOutbox()[evt.row-1].id)
table.insert(mail.selected_idxs.outbox[name], outbox.id)
mail.selected_idxs.message[name] = outbox.id
end
else
mail.selected_idxs.outbox[name] = { (getOutbox()[evt.row-1] or {}).id }
mail.selected_idxs.outbox[name] = { outbox.id }
mail.selected_idxs.message[name] = outbox.id
end
if evt.type == "DCL" and getOutbox()[evt.row-1] then
mail.show_message(name, getOutbox()[evt.row-1].id)
if evt.type == "DCL" then
mail.selected_idxs.message[name] = outbox.id
mail.show_message(name, outbox.id)
else
mail.show_mail_menu(name)
end
@ -133,7 +151,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
if fields.drafts then -- drafts table
local evt = minetest.explode_table_event(fields.drafts)
local evt = core.explode_table_event(fields.drafts)
if evt.row == 1 then -- header
if mail.selected_idxs.sortfield[name] == evt.column-1 then -- if already this field, then change direction
mail.selected_idxs.sortdirection[name] = mail.selected_idxs.sortdirection[name] == "2" and "1" or "2"
@ -144,6 +162,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
mail.selected_idxs.drafts[name] = evt.row - 1
if evt.type == "DCL" and messagesDrafts[mail.selected_idxs.drafts[name]] then
mail.selected_idxs.message[name] = messagesDrafts[mail.selected_idxs.drafts[name]].id
mail.show_compose(name,
messagesDrafts[mail.selected_idxs.drafts[name]].to,
messagesDrafts[mail.selected_idxs.drafts[name]].subject,
@ -157,7 +176,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
if fields.trash then -- trash table
local evt = minetest.explode_table_event(fields.trash)
local evt = core.explode_table_event(fields.trash)
if evt.row == 1 then -- header
if mail.selected_idxs.sortfield[name] == evt.column-1 then -- if already this field, then change direction
mail.selected_idxs.sortdirection[name] = mail.selected_idxs.sortdirection[name] == "2" and "1" or "2"
@ -168,6 +187,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
mail.selected_idxs.trash[name] = evt.row - 1
if evt.type == "DCL" and messagesTrash[mail.selected_idxs.trash[name]] then
mail.selected_idxs.message[name] = messagesTrash[mail.selected_idxs.trash[name]].id
mail.show_message(name, messagesTrash[mail.selected_idxs.trash[name]].id)
end
return true
@ -191,11 +211,14 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
elseif fields.read then
if formname == "mail:inbox" and nonempty(mail.selected_idxs.inbox[name]) then -- inbox table
mail.show_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
mail.selected_idxs.message[name] = mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]]
elseif formname == "mail:outbox" and nonempty(mail.selected_idxs.outbox[name]) then -- outbox table
mail.show_message(name, mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]])
mail.selected_idxs.message[name] = mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]]
elseif formname == "mail:trash" and messagesTrash[mail.selected_idxs.trash[name]] then
mail.show_message(name, messagesTrash[mail.selected_idxs.trash[name]].id)
mail.selected_idxs.message[name] = messagesTrash[mail.selected_idxs.trash[name]].id
end
if mail.selected_idxs.message[name] then
mail.show_message(name, mail.selected_idxs.message[name])
end
elseif fields.edit then
@ -290,6 +313,20 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
mail.show_mail_menu(name, sortfieldindex, sortdirection, filter)
elseif fields.markspam then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] then
mail.mark_spam(name, mail.selected_idxs.inbox[name])
end
mail.show_mail_menu(name, sortfieldindex, sortdirection, filter)
elseif fields.unmarkspam then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] then
mail.unmark_spam(name, mail.selected_idxs.inbox[name])
end
mail.show_mail_menu(name, sortfieldindex, sortdirection, filter)
elseif fields.new then
mail.show_compose(name)

View file

@ -1,5 +1,5 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
function mail.show_inbox(name, sortfieldindex, sortdirection, filter)
sortfieldindex = tonumber(sortfieldindex or mail.selected_idxs.sortfield[name])
@ -33,7 +33,7 @@ function mail.show_inbox(name, sortfieldindex, sortdirection, filter)
trash_tab = "," .. S("Trash")
end
local inbox_formspec = "size[8.5,10;]" .. mail.theme .. [[
local inbox_formspec = "size[8.5,11;]" .. mail.theme .. [[
tabheader[0.3,1;boxtab;]] ..
S("Inbox") .. "," .. S("Outbox").. "," .. S("Drafts") .. trash_tab .. [[;1;false;false]
@ -43,41 +43,46 @@ function mail.show_inbox(name, sortfieldindex, sortdirection, filter)
button[6,2.45;2.5,0.5;replyall;]] .. S("Reply all") .. [[]
button[6,3.20;2.5,0.5;forward;]] .. S("Forward") .. [[]
button[6,3.95;2.5,0.5;delete;]] .. S("Delete") .. [[]
button[6,4.82;2.5,0.5;markread;]] .. S("Mark Read") .. [[]
button[6,4.85;2.5,0.5;markread;]] .. S("Mark Read") .. [[]
button[6,5.55;2.5,0.5;markunread;]] .. S("Mark Unread") .. [[]
button[6,6.8;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,7.6;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,8.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,9.5;2.5,0.5;quit;]] .. S("Close") .. [[]
button[6,6.4;2.5,0.5;markspam;]] .. S("Mark Spam") .. [[]
button[6,7.1;2.5,0.5;unmarkspam;]] .. S("Unmark Spam") .. [[]
button[6,8.0;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,8.8;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,9.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,10.5;2.5,0.5;quit;]] .. S("Close") .. [[]
tooltip[reply;]] .. S("Reply only to the sender") .. [[]
tooltip[replyall;]] .. S("Reply to all involved people") .. [[]
tooltip[forward;]] .. S("Transfer message to other people") .. [[]
dropdown[0,8.5;2,0.5;sortfield;]] ..
dropdown[0,9.5;2,0.5;sortfield;]] ..
S("From") .. "," .. S("Subject") .. "," .. S("Date") .. [[;]] .. sortfieldindex .. [[;true]
dropdown[2.0,8.5;2,0.5;sortdirection;]] ..
dropdown[2.0,9.5;2,0.5;sortdirection;]] ..
S("Ascending") .. "," .. S("Descending") .. [[;]] .. sortdirection .. [[;true]
field[4.25,8.95;1.4,0.5;filter;]] .. S("Filter") .. [[:;]] .. filter .. [[]
button[5.14,8.62;0.85,0.5;search;Q]
field[4.25,9.95;1.4,0.5;filter;]] .. S("Filter") .. [[:;]] .. filter .. [[]
image_button[5.14,9.5;0.85,0.85;search.png;search;]
checkbox[0,9.1;multipleselection;]] .. S("Allow multiple selection") .. [[;]] ..
checkbox[0,10.1;multipleselection;]] .. S("Allow multiple selection") .. [[;]] ..
tostring(mail.selected_idxs.multipleselection[name]) .. [[]
label[0,9.65;]] .. S("@1 of @2 selected", tostring(#mail.selected_idxs.inbox[name]), tostring(#messages)) .. [[]
button[3.5,9.5;2.5,0.5;selectall;]] .. S("(Un)select all") .. [[]
label[0,10.65;]] ..
S("@1 of @2 selected", tostring(#mail.selected_idxs.inbox[name]), tostring(#messages)) .. [[]
button[3.5,10.5;2.5,0.5;selectall;]] .. S("(Un)select all") .. [[]
tablecolumns[color;text;text]
table[0,0.7;5.75,7.45;inbox;]] .. mail.colors.header .. "," .. S("From") .. "," .. S("Subject")
table[0,0.7;5.75,8.45;inbox;]] .. mail.get_color("header") .. "," .. S("From") .. "," .. S("Subject")
local formspec = { inbox_formspec }
mail.message_drafts[name] = nil
local unread_color_enable = mail.get_setting(name, "unreadcolorenable")
local cc_color_enable = mail.get_setting(name, "cccolorenable")
local mute_list = mail.get_setting(name, "mute_list")
if #messages > 0 then
for _, message in ipairs(messages) do
local selected_id = 0
local displayed_color = {}
-- check if message is in selection list and return its id
if mail.selected_idxs.inbox[name] and #mail.selected_idxs.inbox[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.inbox[name]) do
@ -88,43 +93,30 @@ function mail.show_inbox(name, sortfieldindex, sortdirection, filter)
end
end
if selected_id > 0 then
table.insert(displayed_color, "selected")
end
if not message.read and unread_color_enable then
if not mail.player_in_list(name, message.to) and cc_color_enable then
formspec[#formspec + 1] = "," .. mail.colors.imp_add_sel
else
formspec[#formspec + 1] = "," .. mail.colors.imp_sel
table.insert(displayed_color, "important")
end
else
if not mail.player_in_list(name, message.to) and cc_color_enable then
formspec[#formspec + 1] = "," .. mail.colors.add_sel
else
formspec[#formspec + 1] = "," .. mail.colors.selected
table.insert(displayed_color, "additional")
end
if message.spam then
table.insert(displayed_color, "warning")
end
else
if not message.read and unread_color_enable then
if not mail.player_in_list(name, message.to) and cc_color_enable then
formspec[#formspec + 1] = "," .. mail.colors.imp_add
else
formspec[#formspec + 1] = "," .. mail.colors.important
if table.indexof(mute_list, message.from) >= 1 then
table.insert(displayed_color, "muted")
end
else
if not mail.player_in_list(name, message.to) and cc_color_enable then
formspec[#formspec + 1] = "," .. mail.colors.additional
else
formspec[#formspec + 1] = "," .. mail.get_color(displayed_color)
formspec[#formspec + 1] = ","
end
end
end
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(message.from)
formspec[#formspec + 1] = core.formspec_escape(message.from)
formspec[#formspec + 1] = ","
if message.subject ~= "" then
if string.len(message.subject) > 30 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = core.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(message.subject)
formspec[#formspec + 1] = core.formspec_escape(message.subject)
end
else
formspec[#formspec + 1] = S("(No subject)")
@ -135,5 +127,5 @@ function mail.show_inbox(name, sortfieldindex, sortdirection, filter)
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("No mail") .. "]"
end
minetest.show_formspec(name, "mail:inbox", table.concat(formspec, ""))
core.show_formspec(name, "mail:inbox", table.concat(formspec, ""))
end

View file

@ -1,3 +1,23 @@
-- sub files
local MP = core.get_modpath(core.get_current_modname())
dofile(MP .. "/ui/inbox.lua")
dofile(MP .. "/ui/outbox.lua")
dofile(MP .. "/ui/drafts.lua")
dofile(MP .. "/ui/trash.lua")
dofile(MP .. "/ui/message.lua")
dofile(MP .. "/ui/receivers.lua")
dofile(MP .. "/ui/events.lua")
dofile(MP .. "/ui/contacts.lua")
dofile(MP .. "/ui/edit_contact.lua")
dofile(MP .. "/ui/select_contact.lua")
dofile(MP .. "/ui/maillists.lua")
dofile(MP .. "/ui/edit_maillists.lua")
dofile(MP .. "/ui/compose.lua")
dofile(MP .. "/ui/options.lua")
dofile(MP .. "/ui/settings.lua")
dofile(MP .. "/ui/about.lua")
-- helper function for tabbed overview
function mail.show_mail_menu(playername, sortfield, sortdirection, filter)

View file

@ -1,5 +1,5 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local FORMNAME = "mail:maillists"
@ -9,7 +9,7 @@ local maillists_formspec = "size[8,9;]" .. mail.theme .. [[
button[6,1.60;2,0.5;delete;]] .. S("Delete") .. [[]
button[6,8.25;2,0.5;back;]] .. S("Back") .. [[]
tablecolumns[color;text;text]
table[0,0;5.75,9;maillists;]] .. mail.colors.header .. "," .. S("Name") .. "," .. S("Note")
table[0,0;5.75,9;maillists;]] .. mail.get_color("header") .. "," .. S("Name") .. "," .. S("Note")
function mail.show_maillists(name)
local formspec = { maillists_formspec }
@ -19,14 +19,14 @@ function mail.show_maillists(name)
for _, maillist in ipairs(maillists) do
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = "@" .. minetest.formspec_escape(maillist.name)
formspec[#formspec + 1] = "@" .. core.formspec_escape(maillist.name)
formspec[#formspec + 1] = ","
if maillist.desc ~= "" then
if string.len(maillist.desc) > 30 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(maillist.desc, 1, 27))
if string.len(maillist.desc or "") > 30 then
formspec[#formspec + 1] = core.formspec_escape(string.sub(maillist.desc, 1, 27))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(maillist.desc)
formspec[#formspec + 1] = core.formspec_escape(maillist.desc)
end
else
formspec[#formspec + 1] = S("(No description)")
@ -40,10 +40,10 @@ function mail.show_maillists(name)
else
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("No maillist") .. "]"
end
minetest.show_formspec(name, FORMNAME, table.concat(formspec, ""))
core.show_formspec(name, FORMNAME, table.concat(formspec, ""))
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
@ -52,7 +52,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local maillists = mail.get_maillists(name)
if fields.maillists then
local evt = minetest.explode_table_event(fields.maillists)
local evt = core.explode_table_event(fields.maillists)
mail.selected_idxs.maillists[name] = evt.row - 1
if evt.type == "DCL" and maillists[mail.selected_idxs.maillists[name]] then
local maillist = mail.get_maillist_by_name(name, maillists[mail.selected_idxs.maillists[name]].name)
@ -85,7 +85,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
-- except if it was the last. Then determine the new last
local found = false
local last = nil
for k in mail.pairsByKeys(maillists) do
for k in mail.pairs_by_keys(maillists) do
if found then
mail.selected_idxs.maillists[name] = k
break

View file

@ -1,47 +1,67 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local FORMNAME = "mail:message"
local function interleave_msg(body)
return "> " .. (body or ""):gsub("\n", "\n> ")
end
function mail.show_message(name, id)
local message = mail.get_message(name, id)
if not message then
-- message not found or vanished
return
end
mail.selected_idxs.message[name] = id
local formspec = [[
size[8,9]
size[10,10]
box[0,0;7,1.9;]] .. mail.colors.highlighted .. [[]
box[0,0;7,1.9;]] .. mail.get_color("highlighted") .. [[]
button[7.25,0.15;0.75,0.5;back;X]
button[9.25,0.15;0.75,0.5;back;X]
button[7.25,-0.07;2,1;receivers;]] .. S("Receivers") .. [[]
label[0.2,0.1;]] .. S("From") .. [[: %s]
label[0.2,0.5;]] .. S("To") .. [[: %s]
label[0.2,0.9;]] .. S("CC") .. [[: %s]
label[0.2,1.3;]] .. S("Date") .. [[: %s]
tooltip[0.2,1.3;4.8,0.4;]] .. mail.time_ago(message.time) .. [[]
button[5.1,1;2,1;receivers;]] .. S("Receivers") .. [[]
label[0,2.1;]] .. S("Subject") .. [[: %s]
textarea[0.25,2.6;8,7.0;;;%s]
textarea[0.25,2.6;7.25,8.75;;;%s]
button[0,8.5;2,1;reply;]] .. S("Reply") .. [[]
button[2,8.5;2,1;replyall;]] .. S("Reply all") .. [[]
button[4,8.5;2,1;forward;]] .. S("Forward") .. [[]
button[6,8.5;2,1;delete;]] .. S("Delete") .. [[]
button[7.25,1.0;2.75,1;reply;]] .. S("Reply") .. [[]
button[7.25,1.8;2.75,1;replyall;]] .. S("Reply all") .. [[]
button[7.25,2.6;2.75,1;forward;]] .. S("Forward") .. [[]
button[7.25,3.6;2.75,1;markspam;]] .. S("Mark Spam") .. [[]
button[7.25,4.4;2.75,1;unmarkspam;]] .. S("Unmark Spam") .. [[]
button[7.25,5.4;2.75,1;togglemute;]] .. S("(Un)mute sender") .. [[]
box[7.25,6.4;2.5,2.75;]] .. mail.get_color("disabled") .. [[]
button[7.25,9.25;2.75,1;delete;]] .. S("Delete") .. [[]
tooltip[reply;]] .. S("Reply only to the sender") .. [[]
tooltip[replyall;]] .. S("Reply to all involved people") .. [[]
tooltip[forward;]] .. S("Transfer message to other people") .. [[]
]] .. mail.theme
local from = minetest.formspec_escape(message.from) or ""
local to = minetest.formspec_escape(message.to) or ""
local from = core.formspec_escape(message.from) or ""
local to = core.formspec_escape(message.to) or ""
if string.len(to) > 70 then to = string.sub(to, 1, 67) .. "..." end
local cc = minetest.formspec_escape(message.cc) or ""
local cc = core.formspec_escape(message.cc) or ""
if string.len(cc) > 50 then cc = string.sub(cc, 1, 47) .. "..." end
local date = type(message.time) == "number"
and minetest.formspec_escape(os.date(mail.get_setting(name, "date_format"), message.time)) or ""
local subject = minetest.formspec_escape(message.subject) or ""
local body = minetest.formspec_escape(message.body) or ""
and core.formspec_escape(os.date(mail.get_setting(name, "date_format"),
message.time+3600*mail.get_setting(name, "timezone_offset"))) or ""
local subject = core.formspec_escape(message.subject) or ""
local body = core.formspec_escape(message.body) or ""
formspec = string.format(formspec, from, to, cc, date, subject, body)
if not message.read and mail.get_setting(name, "auto_marking_read") then
@ -49,30 +69,27 @@ function mail.show_message(name, id)
mail.mark_read(name, id)
end
minetest.show_formspec(name, FORMNAME, formspec)
core.show_formspec(name, FORMNAME, formspec)
end
function mail.reply(name, message)
if not message then
-- TODO: workaround for https://github.com/mt-mods/mail/issues/84
minetest.log("error", "[mail] reply called with nil message for player: " .. name)
minetest.log("error", "[mail] current mail-context: " .. dump(mail.selected_idxs))
core.log("error", "[mail] reply called with nil message for player: " .. name)
core.log("error", "[mail] current mail-context: " .. dump(mail.selected_idxs))
return
end
local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body
mail.show_compose(name, message.from, "Re: "..message.subject, replyfooter)
mail.show_compose(name, message.from, "Re: "..message.subject, interleave_msg(message.body))
end
function mail.replyall(name, message)
if not message then
-- TODO: workaround for https://github.com/mt-mods/mail/issues/84
minetest.log("error", "[mail] replyall called with nil message for player: " .. name)
minetest.log("error", "[mail] current mail-context: " .. dump(mail.selected_idxs))
core.log("error", "[mail] replyall called with nil message for player: " .. name)
core.log("error", "[mail] current mail-context: " .. dump(mail.selected_idxs))
return
end
local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body
-- new recipients are the sender plus the original recipients, minus ourselves
local recipients = message.to or ""
if message.from ~= nil then
@ -97,30 +114,21 @@ function mail.replyall(name, message)
end
cc = mail.concat_player_list(cc)
mail.show_compose(name, recipients, "Re: "..message.subject, replyfooter, cc)
mail.show_compose(name, recipients, "Re: "..message.subject, interleave_msg(message.body), cc)
end
function mail.forward(name, message)
local fwfooter = "Type your message here.\n\n--Original message follows--\n" .. (message.body or "")
mail.show_compose(name, "", "Fw: " .. (message.subject or ""), fwfooter)
mail.show_compose(name, "", "Fw: " .. (message.subject or ""), interleave_msg(message.body))
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
local entry = mail.get_storage_entry(name)
local message = ""
if mail.selected_idxs.inbox[name] and mail.selected_idxs.boxtab[name] == 1 then
message = mail.get_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
elseif mail.selected_idxs.outbox[name] and mail.selected_idxs.boxtab[name] == 2 then
message = mail.get_message(name, mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]])
elseif mail.selected_idxs.trash[name] and mail.selected_idxs.boxtab[name] == 4 then
message = mail.get_message(name, entry.trash[mail.selected_idxs.trash[name]].id)
end
local message = mail.get_message(name, mail.selected_idxs.message[name])
if fields.back then
mail.show_mail_menu(name)
@ -135,6 +143,22 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
elseif fields.forward then
mail.forward(name, message)
elseif fields.markspam then
mail.mark_spam(name, message.id)
elseif fields.unmarkspam then
mail.unmark_spam(name, message.id)
elseif fields.togglemute then
local mutes = table.copy(mail.get_setting(name, "mute_list"))
local mute_indexof = table.indexof(mutes, message.from)
if mute_indexof == -1 then -- mute
table.insert(mutes, message.from)
else -- unmute
table.remove(mutes, mute_indexof)
end
mail.set_setting(name, "mute_list", mutes)
elseif fields.delete then
if mail.get_setting(name, "trash_move_enable") and mail.selected_idxs.boxtab[name] ~= 4 then
mail.trash_mail(name, message.id)

View file

@ -1,5 +1,5 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
function mail.show_outbox(name, sortfieldindex, sortdirection, filter)
sortfieldindex = tonumber(sortfieldindex or mail.selected_idxs.sortfield[name])
@ -33,7 +33,7 @@ function mail.show_outbox(name, sortfieldindex, sortdirection, filter)
trash_tab = "," .. S("Trash")
end
local outbox_formspec = "size[8.5,10;]" .. mail.theme .. [[
local outbox_formspec = "size[8.5,11;]" .. mail.theme .. [[
tabheader[0.3,1;boxtab;]] ..
S("Inbox") .. "," .. S("Outbox").. "," .. S("Drafts") .. trash_tab .. [[;2;false;false]
@ -43,29 +43,30 @@ function mail.show_outbox(name, sortfieldindex, sortdirection, filter)
button[6,2.45;2.5,0.5;replyall;]] .. S("Reply all") .. [[]
button[6,3.20;2.5,0.5;forward;]] .. S("Forward") .. [[]
button[6,3.95;2.5,0.5;delete;]] .. S("Delete") .. [[]
button[6,6.8;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,7.6;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,8.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,9.5;2.5,0.5;quit;]] .. S("Close") .. [[]
button[6,8.0;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,8.8;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,9.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,10.5;2.5,0.5;quit;]] .. S("Close") .. [[]
tooltip[reply;]] .. S("Reply only to the sender") .. [[]
tooltip[replyall;]] .. S("Reply to all involved people") .. [[]
tooltip[forward;]] .. S("Transfer message to other people") .. [[]
dropdown[0,8.5;2,0.5;sortfield;]] ..
dropdown[0,9.5;2,0.5;sortfield;]] ..
S("To") .. "," .. S("Subject") .. "," .. S("Date") .. [[;]] .. sortfieldindex .. [[;true]
dropdown[2.0,8.5;2,0.5;sortdirection;]] ..
dropdown[2.0,9.5;2,0.5;sortdirection;]] ..
S("Ascending") .. "," .. S("Descending") .. [[;]] .. sortdirection .. [[;true]
field[4.25,8.95;1.4,0.5;filter;]] .. S("Filter") .. [[:;]] .. filter .. [[]
button[5.14,8.62;0.85,0.5;search;Q]
field[4.25,9.95;1.4,0.5;filter;]] .. S("Filter") .. [[:;]] .. filter .. [[]
image_button[5.14,9.5;0.85,0.85;search.png;search;]
checkbox[0,9.1;multipleselection;]] .. S("Allow multiple selection") .. [[;]] ..
checkbox[0,10.1;multipleselection;]] .. S("Allow multiple selection") .. [[;]] ..
tostring(mail.selected_idxs.multipleselection[name]) .. [[]
label[0,9.65;]] .. S("@1 of @2 selected", tostring(#mail.selected_idxs.outbox[name]), tostring(#messages)) ..[[]
button[3.5,9.5;2.5,0.5;selectall;]] .. S("(Un)select all") .. [[]
label[0,10.65;]] ..
S("@1 of @2 selected", tostring(#mail.selected_idxs.outbox[name]), tostring(#messages)) .. [[]
button[3.5,10.5;2.5,0.5;selectall;]] .. S("(Un)select all") .. [[]
tablecolumns[color;text;text]
table[0,0.7;5.75,7.45;outbox;]] .. mail.colors.header .. "," .. S("To") .. "," .. S("Subject")
table[0,0.7;5.75,8.45;outbox;]] .. mail.get_color("header") .. "," .. S("To") .. "," .. S("Subject")
local formspec = { outbox_formspec }
mail.message_drafts[name] = nil
@ -73,6 +74,7 @@ function mail.show_outbox(name, sortfieldindex, sortdirection, filter)
if #messages > 0 then
for _, message in ipairs(messages) do
local selected_id = 0
local displayed_color = {}
-- check if message is in selection list and return its id
if mail.selected_idxs.outbox[name] and #mail.selected_idxs.outbox[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.outbox[name]) do
@ -83,24 +85,23 @@ function mail.show_outbox(name, sortfieldindex, sortdirection, filter)
end
end
if selected_id > 0 then
formspec[#formspec + 1] = "," .. mail.colors.selected
else
formspec[#formspec + 1] = ","
table.insert(displayed_color, "selected")
end
formspec[#formspec + 1] = "," .. mail.get_color(displayed_color)
formspec[#formspec + 1] = ","
if string.len(message.to) > 20 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(message.to, 1, 17))
formspec[#formspec + 1] = core.formspec_escape(string.sub(message.to, 1, 17))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(message.to)
formspec[#formspec + 1] = core.formspec_escape(message.to)
end
formspec[#formspec + 1] = ","
if message.subject ~= "" then
if string.len(message.subject) > 30 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = core.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(message.subject)
formspec[#formspec + 1] = core.formspec_escape(message.subject)
end
else
formspec[#formspec + 1] = S("(No subject)")
@ -111,5 +112,5 @@ function mail.show_outbox(name, sortfieldindex, sortdirection, filter)
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("No mail") .. "]"
end
minetest.show_formspec(name, "mail:outbox", table.concat(formspec, ""))
core.show_formspec(name, "mail:outbox", table.concat(formspec, ""))
end

View file

@ -1,5 +1,5 @@
-- translation
local S = minetest.get_translator("mail")
local S = core.get_translator("mail")
local FORMNAME = "mail:receivers"
@ -9,7 +9,7 @@ function mail.show_receivers(name, id)
local formspec = [[
size[8,6]
box[0,0;7,1.1;]] .. mail.colors.highlighted .. [[]
box[0,0;7,1.1;]] .. mail.get_color("highlighted") .. [[]
button[7.25,0.15;0.75,0.5;back;X]
@ -23,36 +23,28 @@ function mail.show_receivers(name, id)
table[4,1.5;3.8,4.5;cc;%s]
]] .. mail.theme
local from = minetest.formspec_escape(message.from) or ""
local from = core.formspec_escape(message.from) or ""
local to = mail.parse_player_list(message.to or "")
local to_str = mail.colors.header .. "," .. S("To") .. ",,"
local to_str = mail.get_color("header") .. "," .. S("To") .. ",,"
to_str = to_str .. table.concat(to, ",,")
local cc = mail.parse_player_list(message.cc or "")
local cc_str = mail.colors.header .. "," .. S("CC") .. ",,"
local cc_str = mail.get_color("header") .. "," .. S("CC") .. ",,"
cc_str = cc_str .. table.concat(cc, ",,")
local date = type(message.time) == "number"
and minetest.formspec_escape(os.date(mail.get_setting(name, "date_format"), message.time)) or ""
and core.formspec_escape(os.date(mail.get_setting(name, "date_format"), message.time)) or ""
formspec = string.format(formspec, from, date, to_str, cc_str)
minetest.show_formspec(name, FORMNAME, formspec)
core.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
local entry = mail.get_storage_entry(name)
local message_id = ""
if mail.selected_idxs.inbox[name] and mail.selected_idxs.boxtab[name] == 1 then
message_id = mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]]
elseif mail.selected_idxs.outbox[name] and mail.selected_idxs.boxtab[name] == 2 then
message_id = mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]]
elseif mail.selected_idxs.trash[name] and mail.selected_idxs.boxtab[name] == 4 then
message_id = entry.trash[mail.selected_idxs.trash[name]].id
end
local message_id = mail.selected_idxs.message[name]
if fields.back then
mail.show_message(name, message_id)

View file

@ -1,19 +1,19 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local FORMNAME = "mail:selectcontact"
local select_contact_formspec = "size[8,9;]" .. mail.theme .. [[
tablecolumns[color;text;text]
table[0,0;3.5,9;contacts;]] .. mail.colors.header .. "," .. S("Name") .. "," .. S("Note") .. [[%s]
table[0,0;3.5,9;contacts;]] .. mail.get_color("header") .. "," .. S("Name") .. "," .. S("Note") .. [[%s]
button[3.55,2.00;1.75,0.5;toadd; ]] .. S("Add") .. [[]
button[3.55,2.75;1.75,0.5;toremove; ]] .. S("Remove") .. [[]
button[3.55,6.00;1.75,0.5;ccadd; ]] .. S("Add") .. [[]
button[3.55,6.75;1.75,0.5;ccremove; ]] .. S("Remove") .. [[]
tablecolumns[color;text;text]
table[5.15,0.0;2.75,4.5;to;]] .. mail.colors.header .. "," .. S("To") .. ":," .. S("Note") .. [[%s]
table[5.15,0.0;2.75,4.5;to;]] .. mail.get_color("header") .. "," .. S("To") .. ":," .. S("Note") .. [[%s]
tablecolumns[color;text;text]
table[5.15,4.6;2.75,4.5;cc;]] .. mail.colors.header .. "," .. S("CC") .. ":," .. S("Note") .. [[%s]
table[5.15,4.6;2.75,4.5;cc;]] .. mail.get_color("header") .. "," .. S("CC") .. ":," .. S("Note") .. [[%s]
button[3.55,8.25;1.75,0.5;back;]] .. S("Back") .. [[]
]]
@ -39,10 +39,10 @@ function mail.show_select_contact(name, to, cc)
bcc = ""
end]]--
formspec = string.format(formspec, contacts, to, cc)--, bcc()
minetest.show_formspec(name, FORMNAME, formspec)
core.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
@ -60,7 +60,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
bcc = "bccremove"
}) do
if fields[k] then
local evt = minetest.explode_table_event(fields[k])
local evt = core.explode_table_event(fields[k])
mail.selected_idxs[k][name] = evt.row - 1
if evt.type == "DCL" and mail.selected_idxs[k][name] then
fields[action] = true
@ -75,7 +75,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if fields[v.."add"] then
update = true
if mail.selected_idxs.contacts[name] then
for k, contact, i in mail.pairsByKeys(contacts) do
for k, contact, i in mail.pairs_by_keys(contacts) do
if k == mail.selected_idxs.contacts[name] or i == mail.selected_idxs.contacts[name] then
local list = mail.parse_player_list(draft[v])
list[#list+1] = contact.name

View file

@ -1,86 +1,241 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local FORMNAME = "mail:settings"
local date_formats = {"%Y-%m-%d %X", "%d/%m/%y %X", "%A %d %B %Y %X"}
local function get_settings_groups(parent)
-- generate ordered list of settings
local groups = {}
for _, g in ipairs(mail.settings_groups) do
if (g.parent or 0) == parent then
table.insert(groups, g)
-- insert sub groups just after the parent group
table.insert_all(groups, get_settings_groups(g.name))
end
end
return groups
end
local groups_labels = {}
local ordered_groups = get_settings_groups(0)
local tree_indent = 0
for i, g in ipairs(ordered_groups) do
if not g.parent then tree_indent = 0
elseif i > 1 and g.parent == ordered_groups[i-1].name then tree_indent = tree_indent + 1
elseif i > 1 and g.parent ~= ordered_groups[i-1].parent then tree_indent = tree_indent - 1
end
table.insert(groups_labels, tostring(tree_indent))
table.insert(groups_labels, g.label)
end
local groups_str = table.concat(groups_labels, ",")
function mail.show_settings(name)
-- date formats prepare
local dates_now = {}
local previous_date_format = mail.get_setting(name, "date_format")
local date_dropdown_index = 1
for i, f in pairs(date_formats) do
table.insert(dates_now, os.date(f, os.time()))
if f == previous_date_format then date_dropdown_index = i end
local group_index = 1
mail.selected_idxs.settings_group[name] = mail.selected_idxs.settings_group[name] or mail.settings_groups[1].name
for i, g in ipairs(ordered_groups) do
if g.name == mail.selected_idxs.settings_group[name] then
group_index = i
break
end
end
local date_dropdown_str = table.concat(dates_now, ",")
local formspec = [[
size[10,6;]
tabheader[0.3,1;optionstab;]] .. S("Settings") .. "," .. S("About") .. [[;1;false;false]
tabheader[0,0;optionstab;]] .. S("Settings") .. "," .. S("About") .. [[;1;false;false]
button[9.35,0;0.75,0.5;back;X]
box[0,0.8;3,0.45;]] .. mail.colors.highlighted .. [[]
label[0.2,0.8;]] .. S("Notifications") .. [[]
checkbox[0,1.2;chat_notifications;]] .. S("Chat notifications") .. [[;]] ..
tostring(mail.get_setting(name, "chat_notifications")) .. [[]
checkbox[0,1.6;onjoin_notifications;]] .. S("On join notifications") .. [[;]] ..
tostring(mail.get_setting(name, "onjoin_notifications")) .. [[]
checkbox[0,2.0;hud_notifications;]] .. S("HUD notifications") .. [[;]] ..
tostring(mail.get_setting(name, "hud_notifications")) .. [[]
checkbox[0,2.4;sound_notifications;]] .. S("Sound notifications") .. [[;]] ..
tostring(mail.get_setting(name, "sound_notifications")) .. [[]
tablecolumns[tree;text]
table[0,0.775;3,4.5;groups;]] .. groups_str .. [[;]] .. group_index .. [[]
box[5,0.8;3,0.45;]] .. mail.colors.highlighted .. [[]
label[5.2,0.8;]] .. S("Message list") .. [[]
checkbox[5,1.2;unreadcolorenable;]] .. S("Show unread in different color") .. [[;]] ..
tostring(mail.get_setting(name, "unreadcolorenable")) .. [[]
checkbox[5,1.6;cccolorenable;]] .. S("Show CC/BCC in different color") .. [[;]] ..
tostring(mail.get_setting(name, "cccolorenable")) .. [[]
box[0,0;3,0.45;]] .. mail.get_color("highlighted") .. [[]
label[0.2,0;]] .. mail.settings_groups[group_index].label .. [[]
label[5,2.6;]] .. S("Default sorting fields") .. [[]
dropdown[5.5,3.0;2,0.5;defaultsortfield;]] ..
S("From/To") .. "," .. S("Subject") .. "," .. S("Date") .. [[;]] ..
tostring(mail.get_setting(name, "defaultsortfield")) .. [[;true]
dropdown[7.5,3.0;2,0.5;defaultsortdirection;]] ..
S("Ascending") .. "," .. S("Descending") .. [[;]] ..
tostring(mail.get_setting(name, "defaultsortdirection")) .. [[;true]
button[0,5.65;2.5,0.5;reset;]] .. S("Reset") .. [[]
button[7.5,5.65;2.5,0.5;save;]] .. S("Save") .. [[]
]]
box[0,3.2;3,0.45;]] .. mail.colors.highlighted .. [[]
label[0.2,3.2;]] .. S("Other") .. [[]
checkbox[0,3.6;trash_move_enable;]] .. S("Move deleted messages to trash") .. [[;]] ..
tostring(mail.get_setting(name, "trash_move_enable")) .. [[]
checkbox[0,4.0;auto_marking_read;]] .. S("Automatic marking read") .. [[;]] ..
tostring(mail.get_setting(name, "auto_marking_read")) .. [[]
label[0.31,4.7;]] .. S("Date format:") .. [[]
dropdown[2.7,4.6;4,0.5;date_format;]] .. date_dropdown_str .. [[;]] ..
tostring(date_dropdown_index) .. [[;true]
local x = 3.5
local y = -0.7
-- put settings in order
local ordered_settings = {}
for setting, data in pairs(mail.settings) do
if data.group == mail.selected_idxs.settings_group[name] then
table.insert(ordered_settings, setting)
end
end
table.sort(ordered_settings, function(a, b) return mail.settings[a].index < mail.settings[b].index end)
for _, setting in pairs(ordered_settings) do
local data = mail.settings[setting]
y = y + 0.4
local field_default = mail.selected_idxs[setting][name]
if field_default == nil then field_default = mail.get_setting(name, setting) end
if data.type == "bool" then
formspec = formspec .. [[
checkbox[]] .. x .. "," .. y .. ";" .. setting .. ";" ..
data.label .. ";" .. tostring(field_default) .. [[]
]]
if data.tooltip then
formspec = formspec .. [[
tooltip[]] .. setting .. ";" .. data.tooltip .. [[]
]]
end
elseif data.type == "string" then
y = y + 1
formspec = formspec .. [[
field[]] .. x+0.275 .. "," .. y .. ";3,0.5;" .. setting .. ";" .. data.label .. [[;]] ..
field_default .. [[]
]]
if data.tooltip then
formspec = formspec .. "tooltip[" .. setting .. ";" .. data.tooltip .. "]"
end
if data.dataset then
local formatted_dataset = table.copy(data.dataset)
if data.format then
for i, d in ipairs(formatted_dataset) do
formatted_dataset[i] = data.format(d)
end
end
local dataset_str = table.concat(formatted_dataset, ",")
local dataset_selected_id = 1
for i, d in ipairs(data.dataset) do
if d == field_default then
dataset_selected_id = i
break
end
end
formspec = formspec .. [[
dropdown[]] .. x+3 .. "," .. y-0.45 .. ";3,0.5;" .. "dataset_" .. setting .. ";" ..
dataset_str .. [[;]] .. dataset_selected_id .. [[;true]
]]
end
elseif data.type == "number" then
y = y + 1
formspec = formspec .. [[
field[]] .. x+0.275 .. "," .. y .. ";3,0.5;" .. setting .. ";" .. data.label .. [[;]] ..
tostring(field_default) .. [[]
]]
if data.tooltip then
formspec = formspec .. "tooltip[" .. setting .. ";" .. data.tooltip .. "]"
end
if data.dataset then
local formatted_dataset = table.copy(data.dataset)
if data.format then
for i, d in ipairs(formatted_dataset) do
formatted_dataset[i] = data.format(d)
end
end
local dataset_str = table.concat(formatted_dataset, ",")
local dataset_selected_id = 1
for i, d in ipairs(data.dataset) do
if d == field_default then
dataset_selected_id = i
break
end
end
formspec = formspec .. [[
dropdown[]] .. x+3 .. "," .. y-0.45 .. ";3,0.5;" .. "dataset_" .. setting .. ";" ..
dataset_str .. [[;]] .. dataset_selected_id .. [[;true]
]]
end
elseif data.type == "index" then
y = y + 0.2
local formatted_dataset = table.copy(data.dataset)
if data.format then
for i, d in ipairs(formatted_dataset) do
formatted_dataset[i] = data.format(d)
end
end
local dataset_str = table.concat(formatted_dataset, ",")
local dataset_selected_id = field_default
formspec = formspec .. [[
label[]] .. x .. "," .. y .. ";" .. data.label .. "]"
y = y + 0.4
formspec = formspec .. [[
dropdown[]] .. x .. "," .. y .. ";3,0.5;" .. setting .. ";" ..
dataset_str .. [[;]] .. dataset_selected_id .. [[;true]
]]
if data.tooltip then
formspec = formspec .. [[
tooltip[]] .. setting .. ";" .. data.tooltip .. [[]
]]
end
y = y + 0.2
elseif data.type == "list" then
y = y + 0.3
formspec = formspec .. [[
tablecolumns[color;text]
table[]] .. x-0.0125 .. "," .. y .. ";3.8125,2.5;" .. setting .. ";" ..
mail.get_color("header") .. "," .. data.label .. ",," ..
table.concat(field_default, ",,") .. "]"
tooltip[chat_notifications;]] .. S("Receive a message in the chat when there is a new message") .. [[]
tooltip[onjoin_notifications;]] .. S("Receive a message at login when inbox isn't empty") .. [[]
tooltip[hud_notifications;]] .. S("Show an HUD notification when inbox isn't empty") .. [[]
tooltip[sound_notifications;]] .. S("Play a sound when there is a new message") .. [[]
tooltip[auto_marking_read;]] .. S("Mark a message as read when opened") .. [[]
y = y + 3.1
formspec = formspec .. [[
field[]] .. x+0.275 .. "," .. y .. ";2.975,0.5;field_" .. setting .. [[;;]
button[]] .. x+2.75 .. "," .. y-0.325 .. ";0.75,0.5;add_" .. setting .. [[;+]
button[]] .. x+3.25 .. "," .. y-0.325 .. ";0.75,0.5;remove_" .. setting .. [[;-]
]]
button[0,5.5;2.5,0.5;save;]] .. S("Save") .. [[]
button[2.7,5.5;2.5,0.5;reset;]] .. S("Reset") .. [[]
]] .. mail.theme
if data.tooltip then
formspec = formspec .. "tooltip[field_" .. setting .. ";" .. data.tooltip .. "]"
end
minetest.show_formspec(name, FORMNAME, formspec)
y = y - 0.4
end
end
formspec = formspec .. mail.theme
core.show_formspec(name, FORMNAME, formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local playername = player:get_player_name()
for setting, data in pairs(mail.settings) do
if fields[setting] or fields["add_" .. setting] or fields["remove_" .. setting] then
if data.type == "bool" then
mail.selected_idxs[setting][playername] = fields[setting] == "true"
break
elseif data.type == "string" then
if data.dataset and fields["dataset_" .. setting] then
mail.selected_idxs[setting][playername] = data.dataset[tonumber(fields["dataset_" .. setting])]
mail.show_settings(playername)
end
elseif data.type == "number" then
if data.dataset and fields["dataset_" .. setting] then
mail.selected_idxs[setting][playername] = data.dataset[tonumber(fields["dataset_" .. setting])]
mail.show_settings(playername)
end
elseif data.type == "index" then
mail.selected_idxs[setting][playername] = tonumber(fields[setting])
elseif data.type == "list" then
mail.selected_idxs[setting][playername] = mail.selected_idxs[setting][playername] or
mail.get_setting(playername, setting)
if fields[setting] then
local evt = core.explode_table_event(fields[setting])
mail.selected_idxs["index_" .. setting][playername] = evt.row-1
elseif fields["add_" .. setting] then
table.insert(mail.selected_idxs[setting][playername], fields["field_" .. setting])
elseif fields["remove_" .. setting] and mail.selected_idxs["index_" .. setting][playername] then
table.remove(mail.selected_idxs[setting][playername],
mail.selected_idxs["index_" .. setting][playername])
end
mail.show_settings(playername)
end
end
end
if fields.back then
mail.show_mail_menu(playername)
return
elseif fields.groups then
local evt = core.explode_table_event(fields.groups)
mail.selected_idxs.settings_group[playername] = mail.settings_groups[tonumber(evt.row)].name
mail.show_settings(playername)
elseif fields.optionstab == "1" then
mail.selected_idxs.optionstab[playername] = 1
@ -89,47 +244,14 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
mail.show_about(playername)
return
elseif fields.chat_notifications then
mail.selected_idxs.chat_notifications[playername] = fields.chat_notifications == "true"
elseif fields.onjoin_notifications then
mail.selected_idxs.onjoin_notifications[playername] = fields.onjoin_notifications == "true"
elseif fields.hud_notifications then
mail.selected_idxs.hud_notifications[playername] = fields.hud_notifications == "true"
elseif fields.sound_notifications then
mail.selected_idxs.sound_notifications[playername] = fields.sound_notifications == "true"
elseif fields.unreadcolorenable then
mail.selected_idxs.unreadcolorenable[playername] = fields.unreadcolorenable == "true"
elseif fields.cccolorenable then
mail.selected_idxs.cccolorenable[playername] = fields.cccolorenable == "true"
elseif fields.trash_move_enable then
mail.selected_idxs.trash_move_enable[playername] = fields.trash_move_enable == "true"
elseif fields.auto_marking_read then
mail.selected_idxs.auto_marking_read[playername] = fields.auto_marking_read == "true"
elseif fields.save then
-- checkboxes
mail.set_setting(playername, "chat_notifications", mail.selected_idxs.chat_notifications[playername])
mail.set_setting(playername, "onjoin_notifications", mail.selected_idxs.onjoin_notifications[playername])
mail.set_setting(playername, "hud_notifications", mail.selected_idxs.hud_notifications[playername])
mail.set_setting(playername, "sound_notifications", mail.selected_idxs.sound_notifications[playername])
mail.set_setting(playername, "unreadcolorenable", mail.selected_idxs.unreadcolorenable[playername])
mail.set_setting(playername, "cccolorenable", mail.selected_idxs.cccolorenable[playername])
mail.set_setting(playername, "trash_move_enable", mail.selected_idxs.trash_move_enable[playername])
mail.set_setting(playername, "auto_marking_read", mail.selected_idxs.auto_marking_read[playername])
-- dropdowns
local defaultsortfield = fields.defaultsortfield or mail.get_setting(playername, "defaultsortfield")
local defaultsortdirection = fields.defaultsortdirection or mail.get_setting(playername, "defaultsortdirection")
local date_format = date_formats[tonumber(fields.date_format)] or mail.get_setting(playername, "date_format")
mail.set_setting(playername, "defaultsortfield", tonumber(defaultsortfield))
mail.set_setting(playername, "defaultsortdirection", tonumber(defaultsortdirection))
mail.set_setting(playername, "date_format", date_format)
-- save settings
for setting, _ in pairs(mail.settings) do
local new_value = mail.selected_idxs[setting][playername]
mail.selected_idxs[setting][playername] = nil
if new_value == nil then new_value = mail.get_setting(playername, setting) end
mail.set_setting(playername, setting, new_value)
end
-- update visuals
mail.hud_update(playername, mail.get_storage_entry(playername).inbox)
mail.show_settings(playername)

View file

@ -1,7 +1,7 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local trash_formspec = "size[8.5,10;]" .. mail.theme .. [[
local trash_formspec = "size[8.5,11;]" .. mail.theme .. [[
tabheader[0.3,1;boxtab;]] ..
S("Inbox") .. "," .. S("Outbox").. "," .. S("Drafts") .. "," .. S("Trash") .. [[;4;false;false]
@ -10,13 +10,13 @@ local trash_formspec = "size[8.5,10;]" .. mail.theme .. [[
button[6,1.70;2.5,0.5;restore;]] .. S("Restore") .. [[]
button[6,2.45;2.5,0.5;delete;]] .. S("Delete") .. [[]
button[6,3.20;2.5,0.5;empty;]] .. S("Empty") .. [[]
button[6,6.8;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,7.6;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,8.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,9.5;2.5,0.5;quit;]] .. S("Close") .. [[]
button[6,8.0;2.5,0.5;contacts;]] .. S("Contacts") .. [[]
button[6,8.8;2.5,0.5;maillists;]] .. S("Mail lists") .. [[]
button[6,9.7;2.5,0.5;options;]] .. S("Options") .. [[]
button_exit[6,10.5;2.5,0.5;quit;]] .. S("Close") .. [[]
tablecolumns[color;text;text]
table[0,0.7;5.75,9.35;trash;]] .. mail.colors.header .. "," .. S("From/To") .. "," .. S("Subject")
table[0,0.7;5.75,10.35;trash;]] .. mail.get_color("header") .. "," .. S("From/To") .. "," .. S("Subject")
function mail.show_trash(name)
@ -28,14 +28,14 @@ function mail.show_trash(name)
for _, message in ipairs(messages) do
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(message.to)
formspec[#formspec + 1] = core.formspec_escape(message.to)
formspec[#formspec + 1] = ","
if message.subject ~= "" then
if string.len(message.subject) > 30 then
formspec[#formspec + 1] = minetest.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = core.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = minetest.formspec_escape(message.subject)
formspec[#formspec + 1] = core.formspec_escape(message.subject)
end
else
formspec[#formspec + 1] = S("(No subject)")
@ -49,5 +49,5 @@ function mail.show_trash(name)
else
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("Trash is empty") .. "]"
end
minetest.show_formspec(name, "mail:trash", table.concat(formspec, ""))
core.show_formspec(name, "mail:trash", table.concat(formspec, ""))
end

58
util/colors.lua Normal file
View file

@ -0,0 +1,58 @@
local generic_colors = {
header = "#999999",
selected = "#72FF63",
important = "#FFD700",
additional = "#CCCCDD",
highlighted = "#608631",
new = "#00F529",
warning = "#FF8800",
disabled = "#332222",
muted = "#CCCCCC",
}
local function get_base_color(c)
return generic_colors[c] or ""
end
local function hex2rgb(hex)
hex = hex:gsub("#","")
return {
r = tonumber("0x" .. hex:sub(1,2)),
g = tonumber("0x" .. hex:sub(3,4)),
b = tonumber("0x" .. hex:sub(5,6))
}
end
local function rgb2hex(rgb)
return "#" .. string.format("%x", rgb.r) .. string.format("%x", rgb.g) .. string.format("%x", rgb.b)
end
local function rgb_colors_mix(colors)
local R = 0
local G = 0
local B = 0
for _, c in ipairs(colors) do
R = R + c.r
G = G + c.g
B = B + c.b
end
R = math.floor(R / #colors + 0.5)
G = math.floor(G / #colors + 0.5)
B = math.floor(B / #colors + 0.5)
return {r=R,g=G,b=B}
end
function mail.get_color(mix)
if type(mix) == "string" then
return get_base_color(mix)
elseif #mix == 1 then
return get_base_color(mix[1])
else
local colors2mix = {}
for _, c in ipairs(mix) do
colors2mix[#colors2mix+1] = hex2rgb(get_base_color(c))
end
local mixed_color = rgb_colors_mix(colors2mix)
return rgb2hex(mixed_color)
end
end

View file

@ -1,5 +1,5 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
function mail.compile_contact_list(name, selected, playernames)
-- TODO: refactor this - not just compiles *a* list, but *the* list for the contacts screen (too inflexible)
@ -8,11 +8,11 @@ function mail.compile_contact_list(name, selected, playernames)
if playernames == nil then
local length = 0
for k, contact, i, l in mail.pairsByKeys(contacts) do
for k, contact, i, l in mail.pairs_by_keys(contacts) do
if i == 1 then length = l end
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(contact.name)
formspec[#formspec + 1] = core.formspec_escape(contact.name)
formspec[#formspec + 1] = ","
local note = contact.note
-- display an ellipsis if the note spans multiple lines
@ -20,7 +20,7 @@ function mail.compile_contact_list(name, selected, playernames)
if idx ~= nil then
note = string.sub(note, 1, idx-1) .. ' ...'
end
formspec[#formspec + 1] = minetest.formspec_escape(note)
formspec[#formspec + 1] = core.formspec_escape(note)
if type(selected) == "string" then
if string.lower(selected) == k then
selected = i
@ -43,7 +43,7 @@ function mail.compile_contact_list(name, selected, playernames)
for i,c in ipairs(playernames) do
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(c)
formspec[#formspec + 1] = core.formspec_escape(c)
formspec[#formspec + 1] = ","
if contacts[string.lower(c)] == nil then
formspec[#formspec + 1] = ""
@ -54,7 +54,7 @@ function mail.compile_contact_list(name, selected, playernames)
if idx ~= nil then
note = string.sub(note, 1, idx-1) .. ' ...'
end
formspec[#formspec + 1] = minetest.formspec_escape(note)
formspec[#formspec + 1] = core.formspec_escape(note)
end
if not selected then
if type(selected) == "string" then

9
util/init.lua Normal file
View file

@ -0,0 +1,9 @@
-- sub files
local MP = core.get_modpath(core.get_current_modname())
dofile(MP .. "/util/normalize.lua")
dofile(MP .. "/util/colors.lua")
dofile(MP .. "/util/contact.lua")
dofile(MP .. "/util/settings.lua")
dofile(MP .. "/util/spam.lua")
dofile(MP .. "/util/time_ago.lua")
dofile(MP .. "/util/uuid.lua")

View file

@ -1,18 +1,44 @@
local has_canonical_name = minetest.get_modpath("canonical_name")
-- translation
local S = mail.S
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,29 +47,23 @@ 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
for name in field:gmatch(pattern) do
table.insert(order, name)
end
player_set[lower] = player_name
order[#order+1] = player_name
end
end)
return order
end
function mail.concat_player_list(order)
-- turn list of players back into normalized string
if order == nil or #order == 0 then
return ""
end
return table.concat(order, ", ")
end

View file

@ -2,7 +2,7 @@
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))

113
util/settings.lua Normal file
View file

@ -0,0 +1,113 @@
-- translation
local S = mail.S
mail.settings = {
chat_notifications = {
type = "bool", default = true, group = "notifications", index = 1,
label = S("Chat notifications"), tooltip = S("Receive a message in the chat when there is a new message")
},
onjoin_notifications = {
type = "bool", default = true, group = "notifications", index = 2,
label = S("On join notifications"), tooltip = S("Receive a message at login when inbox isn't empty") },
hud_notifications = {
type = "bool", default = true, group = "notifications", index = 3,
label = S("HUD notifications"), tooltip = S("Show an HUD notification when inbox isn't empty")
},
sound_notifications = {
type = "bool", default = true, group = "notifications", index = 4,
label = S("Sound notifications"), tooltip = S("Play a sound when there is a new message")
},
unreadcolorenable = {
type = "bool", default = true, group = "message_list", index = 1,
label = S("Show unread in different color")
},
cccolorenable = {
type = "bool", default = true, group = "message_list", index = 2,
label = S("Show CC/BCC in different color")
},
defaultsortfield = {
type = "index", default = 3, group = "box_fields", index = 1,
label = S("Default sorting field"), dataset = { S("From/To"), S("Subject"), S("Date") }
},
defaultsortdirection = {
type = "index", default = 1, group = "box_fields", index = 2,
label = S("Default sorting direction"), dataset = { S("Ascending"), S("Descending") }
},
trash_move_enable = {
type = "bool", default = true, group = "other", index = 1,
label = S("Move deleted messages to trash")
},
auto_marking_read = {
type = "bool", default = true, group = "other", index = 2,
label = S("Automatic marking read"), tooltip = S("Mark a message as read when opened")
},
date_format = {
type = "string", default = "%Y-%m-%d %X", group = "date_and_time", index = 3, label = S("Date format"),
dataset = {"%Y-%m-%d %X", "%d/%m/%y %X", "%A %d %B %Y %X"}, format = os.date
},
timezone_offset = {
type = "number", default = 0, group = "date_and_time", index = 4,
label = S("Timezone offset"), tooltip = S("Offset to add to server time."),
},
mute_list = {
type = "list", default = {}, group = "spam", index = 1,
label = S("Mute list")
},
}
mail.settings_groups = {
{ name = "notifications", label = S("Notifications"), index = 1, parent = 0},
{ name = "message_list", label = S("Message list"), index = 2, parent = 0},
{ name = "box_fields", label = S("Fields"), index = 1, parent = "message_list"},
{ name = "spam", label = S("Spam"), index = 3, parent = 0},
{ name = "other", label = S("Other"), index = 4, parent = 0},
{ name = "date_and_time", label = S("Date and Time"), index = 1, parent = "other"}
}
for s, d in pairs(mail.settings) do
mail.selected_idxs[s] = {}
if d.type == "list" then
mail.selected_idxs["index_" .. s] = {}
end
end
function mail.settings.mute_list.check(name, value)
local valid_players = {}
for _, p in ipairs(value) do
if p ~= name and core.player_exists(p) then
table.insert(valid_players, p)
end
end
return valid_players
end
function mail.settings.mute_list.sync(name)
if core.get_modpath("beerchat") then
local players = {}
for other_player, _ in core.get_auth_handler().iterate() do
if beerchat.has_player_muted_player(name, other_player) then
table.insert(players, other_player)
end
end
return players
end
return nil
end
function mail.settings.mute_list.transfer(name, value)
if core.get_modpath("beerchat") then
for other_player, _ in core.get_auth_handler().iterate() do -- unmute all
if not beerchat.execute_callbacks("before_mute", name, other_player) then
return false
end
core.get_player_by_name(name):get_meta():set_string(
"beerchat:muted:" .. other_player, "")
end
for _, other_player in ipairs(value) do -- then mute only players in table
core.get_player_by_name(name):get_meta():set_string(
"beerchat:muted:" .. other_player, "true")
end
return true
end
return nil
end

39
util/spam.lua Normal file
View file

@ -0,0 +1,39 @@
local function caps_ratio(str)
local total_caps = 0
for i = 1, #str do -- iteration through each character
local c = str:sub(i,i)
if string.lower(c) ~= c then -- do not count digits as spam
total_caps = total_caps + 1
end
end
return total_caps/(#str or 1) -- avoid division by zero
end
local function words_ratio(str, ratio)
local words = {}
local split_str = str:split(" ")
for _, w in ipairs(split_str) do
if not words[w] then
words[w] = 0
else
words[w] = (words[w] or 0) + 1
end
end
for _, n in pairs(words) do
if n/#split_str >= ratio then
return true
end
end
return false
end
function mail.check_spam(message)
local spam_checks = {}
if caps_ratio(message.subject) == 1 or caps_ratio(message.body) > 0.4 then
table.insert(spam_checks, "caps")
end
if words_ratio(message.subject, 0.6) or words_ratio(message.body, 0.2) then
table.insert(spam_checks, "words")
end
return spam_checks
end

View file

@ -1,5 +1,5 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
function mail.time_ago(t)
local elapsed = os.time() - t