Compare commits

...

136 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
Athozus
9c041f5905
Release 1.3.0 2023-07-18 23:11:26 +02:00
Athozus
b4ccc168c6
Update Spanish translation (Chache) 2023-07-18 23:09:36 +02:00
Athozus
67108c6771
Update credits 2023-07-17 22:20:58 +02:00
Niklp09
09791d58a7 Update german translation 2023-07-17 21:54:23 +02:00
Athozus
a6f16c847e
Fix French translation 2023-07-17 19:55:44 +02:00
Athozus
f171a3d50a
Change highlighted color 2023-07-17 17:49:33 +02:00
Athozus
be8f0e590f
Add a setting to choose date format 2023-07-17 16:45:57 +02:00
Athozus
db2434c8f9
Update screenshot 2023-07-16 23:50:50 +02:00
Athozus
0248b051ef
Add a setting for automatic marking read
When a message is opened
2023-07-16 22:59:56 +02:00
Athozus
e038993f27
Add tooltips for detailling actions
Reply / Reply all / Forward buttons and Notifications settings
2023-07-16 20:05:23 +02:00
Athozus
37fad48ec8
Add a tooltip for elapsed time since the delivery
It adds the file util/time_ago.lua in order to re-use the function further
2023-07-16 19:42:08 +02:00
Athozus
fbdaaeec11
Move the selection list-check loop (fix #108) 2023-07-10 19:01:15 +02:00
Athozus
fe9aca40f0
Add translation for "No contacts" (fix #107) 2023-07-06 19:03:42 +02:00
Wuzzy
e470c58d83 Fix typos in German 2023-07-04 12:55:44 +02:00
Niklp09
c98cb401d8 update german translation (+templates) 2023-06-25 21:27:59 +02:00
Athozus
d09fafc563
Release 1.2.0 2023-06-25 20:10:07 +02:00
Athozus
67cbef88f7
Add a window to show long receivers list 2023-06-25 19:59:38 +02:00
Athozus
8917fd0b39
Rework French translations 2023-06-25 19:29:27 +02:00
Athozus
95475c7c59
Store colors and use a lighter selection color 2023-06-25 19:12:20 +02:00
Athozus
f5198b9187
Add empty trash button 2023-06-20 16:04:18 +02:00
BuckarooBanzay
8c20aeba5b cache storage entries and batch them for write-back 2023-06-19 17:25:43 +02:00
Athozus
984d8c34d9
Fix unselect all (9679251) 2023-06-18 19:40:11 +02:00
Athozus
968f402566
Fix trash restore crash
No message selected, makes an attempt to index nil value
2023-06-18 19:06:12 +02:00
Athozus
bfd3692280
Remove subject length limitation
Truncated with ... in message list, that don't break the formspec layout anymore
2023-06-18 16:45:52 +02:00
Athozus
8dadd8043c
Permit start composition of a message by passing receivers into /mail command (close #101) 2023-06-18 15:29:27 +02:00
Athozus
d53b07451c
Fix events while reading message in trash 2023-06-17 16:49:12 +02:00
Athozus
65764ee318
Rework message.lua events 2023-06-17 16:24:21 +02:00
Athozus
c6d8f8a069
Update credits 2023-06-17 15:57:26 +02:00
Athozus
31645f263b
Fix #93 (thanks to whosit) 2023-06-16 17:39:10 +02:00
Athozus
2e106e3df7
Add trash (#100)
* Add trash

* Add break at end of deleting loop

* Show trash tab only when trashing enabled

* Update translations
2023-06-15 19:28:04 +02:00
Buckaroo Banzai
de07f6b24b fix mail.delete_mail (again) 2023-06-06 19:58:41 +02:00
Buckaroo Banzai
42db40b7a7
partial fox for #97 (#98)
* partial fox for #97

* Fix #97

---------

Co-authored-by: BuckarooBanzay <BuckarooBanzay@users.noreply.github.com>
Co-authored-by: Athozus <athozus@gmail.com>
2023-06-05 10:25:34 +02:00
Athozus
fab6f1a1e5
Fix long strings 2023-06-04 15:49:31 +02:00
Athozus
660d460af2
Make get_setting outside loop (performance) 2023-06-04 15:45:50 +02:00
Athozus
c655ff32e2
Fix drafts deleting/duplicating (#96) 2023-06-04 15:45:50 +02:00
Athozus
7822ff3038
Replace sent by outbox (consistency with inbox) 2023-06-04 15:45:49 +02:00
Athozus
869cc2bbc3
Show number of results (x of x' selected) 2023-06-04 15:45:49 +02:00
nyomi
5605b07e30
Add Hungarian translation 2023-06-04 15:45:39 +02:00
Athozus
5e24b9274f
Fix half deletion of messages 2023-06-02 22:55:11 +02:00
Athozus
9679251249
Remove unnecessary condition
On (un)select all, both unselect and select start with the same block, then it is better to remove conditions that reduce performance and aren't useful.
2023-05-23 17:49:17 +02:00
Athozus
f8ace25104
Remove duplicated variable 2023-05-20 20:29:00 +02:00
BuckarooBanzay
02853fc3d0 deploy workaround for #84 2023-05-17 16:16:42 +02:00
Athozus
8dcfc7dc77
Rework README 2023-05-07 15:08:45 +02:00
Athozus
2f01739d35
Add simplified Chinese translation (TheTrueBeginner) 2023-05-07 12:29:33 +02:00
Athozus
955f7d123f
Add sound notification (#86)
* Add sound notification

* Change sound

Update translations
2023-05-06 21:30:18 +02:00
Athozus
a79ebd94b8
Add Brazilian Portuguese (APercy) 2023-05-06 15:46:21 +02:00
Athozus
f70ce537a7
Add Spanish translation (Chache) 2023-05-05 23:28:32 +02:00
Athozus
5e3cfedc5e
Translate formatted strings 2023-05-05 16:05:07 +02:00
Athozus
95ab299016
Fix strings (translations and checks) 2023-05-05 13:13:09 +02:00
Athozus
720029a73e
Add settings (#85)
* Add settings

* Fix luacheck

* Fix unupdatable sorting fields values (settings)

* Better settings names

* Better sort settings behaviour

* Rework set_setting()

* Rework settings storage

* Better get sorting filters (inbox/outbox)

* Fix reseting/saving settings
2023-05-05 11:38:19 +02:00
Athozus
a8632255b3
Update contributors & version (1.1.4) 2023-05-02 15:57:57 +02:00
Athozus
c670d48622
Update selection list after an action (fix #80, #81, #83) 2023-05-02 10:42:23 +02:00
imre84
ec92aaba05
crash fixes (#82) 2023-04-30 23:19:22 +03:00
Athozus
47b18f2138
Update HUD after deleting (fix #79) 2023-04-28 16:25:24 +02:00
Athozus
7449aaca52
Message reply fixes (#78)
* Fix reply buttons in message list

* Give a default boxtab value (fix trivial issues)
2023-04-26 12:11:27 +02:00
Athozus
1a84c66346
Update about.lua 2023-04-21 23:43:47 +02:00
Athozus
fc90443952
Fix message infos (#75)
* Fix weird showing of message informations in outbox.lua

* Fix reply button on message view
2023-04-21 23:24:52 +02:00
Athozus
2290c2838e
Fix wrong message view in outbox 2023-04-20 08:47:59 +02:00
Buckaroo Banzai
ed3c8b97a1
update testing harness / move contact-list function to util (#73)
* update testing harness / move contact-list function to util

* Use 5.x.1 versioning

---------

Co-authored-by: BuckarooBanzay <BuckarooBanzay@users.noreply.github.com>
Co-authored-by: Athozus <athozus@gmail.com>
2023-04-18 22:01:24 +02:00
63 changed files with 3517 additions and 815 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.0, 5.1.0, 5.2.0, 5.3.0, 5.4.0, 5.5.0, 5.6.0, 5.6.1, 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

@ -5,37 +5,58 @@ Mail mod for Minetest (ingame mod)
![](https://github.com/mt-mods/mail/workflows/luacheck/badge.svg)
[![ContentDB](https://content.minetest.net/packages/mt-mods/mail/shields/downloads/)](https://content.minetest.net/packages/mt-mods/mail/)
This is a fork of cheapies mail mod
This is a fork of @cheapie's mail mod.
It adds a mail-system that allows players to send each other messages in-game and via webmail (optional)
It adds a mail-system that allows players to send each other messages in-game and via webmail (optional).
# Screenshot
![](screenshot_1.1.0.png)
![Main view](screenshot_1.4.0_1.png)
![Message view](screenshot_1.4.0_2.png)
# Installation
## In-game mail mod
Install it like any other mod: copy the directory `mail_mod` to your "worldmods" folder or use the [contentdb](https://content.minetest.net)
Install it like any other mod: copy the directory `mail_mod` to your "worldmods" folder or use the [ContentDB](https://content.minetest.net)
## Webmail
To provide a web-based interface to receive/send mails you can use the [mtui](https://github.com/minetest-go/mtui) project
To provide a web-based interface to receive/send mails you can use the [mtui](https://github.com/minetest-go/mtui) project.
# Commands/Howto
To access your mail click on the inventory mail button or use the "/mail" command
Mails can be deleted, marked as read or unread, replied to and forwarded to another player
To access your mail type `/mail` command or click on the mail button in your inventory (`unified_inventory`).
Mails can be deleted, marked as read or unread, replied to and forwarded to another player. You can also manage your contacts and your mailing lists.
# Features
* Inbox page
* Outbox page
* Saved drafts
* Read/unread marks
* To/Cc/Bcc system
* Intuitive UI
* Contacts book
* Mailing lists
* Sorters/filters (new in 1.1.0)
* Multiple selection (new in 1.1.0)
* Settings
* Chat, on join, HUD and sound notifications
* Anti-spam detection
* Translated in : English, French, German, Chinese (both traditional and simplified), Spanish, Brazilian Portuguese, Hungarian, Indonesian.
# Compatibility / Migration
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>` modstorage (inbox, outbox, contacts)
* `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
@ -45,34 +66,55 @@ See the "LICENSE" file
# Textures
* textures/email_mail.png (https://github.com/rubenwardy/email.git WTFPL)
# Contributors
# Contributors / Credits
* Cheapie (Initial idea/project)
* Rubenwardy (Lua/UI improvements)
* BuckarooBanzay (Clean-ups, Refactoring)
* Athozus (Outbox, Maillists, UI, Drafts)
* Athozus (Outbox, Maillists, UI, Drafts, Trash, Settings)
* SX (Various fixes, UI)
* fluxionary (Minor fixups)
* Toby1710 (UX fixes)
* Peter Nerlich (CC, BCC)
* Emojigit (Chinese translation)
* Emojigit (Performance, Traditional Chinese translation)
* Niklp09 (German translation)
* Dennis Jenkins (UX fixes)
* Thomas Rudin (Maintenance)
* imre84 (UI fixes)
* Chache (Spanish translation)
* APercy (Brazilian Portuguese translation)
* Nuno Filipe Povoa (mail_notif.ogg - https://invent.kde.org/plasma/oxygen-sounds/-/blob/master/sounds/Oxygen-Im-Nudge.ogg)
* TheTrueBeginner (Simplified Chinese translation)
* 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
You can contribute by :
* Reporting an issue
* Give a review on Content DB
* Adding new features
* Fixing an issue
* Translate into a new language
* Add documentation
* Reporting an issue : Go to the [Issues](https://github.com/mt-mods/mail/issues) tab, click on the button **New issue** and type a short title then give many informations (Minetest version, tab where the bug occured, steps to reproduce the crash, etc.)
* Give a review on ContentDB : [Just write ;)](https://content.minetest.net/packages/mt-mods/mail/review/)
* Requesting new features : [Open an issue](https://github.com/mt-mods/mail/issues) and indicate what you need more in details.
* Adding those new features : [Open a pull request](https://github.com/mt-mods/mail/pulls), and if issue(s) are linked, ping them (#number).
* Fixing an issue : Same as before, open a pull request.
* Translate into a new language : copy `locale/template.txt` into `locale/mail.<codelang>.tr`, and add translated strings (syntax : `not translated=translated`). Then, open a pull request. We're also working on opening a [Weblate](https://weblate.org) (free web interface) to translate strings easier.
* Add documentation : adds `.md` (markdown) or `.txt` files and open a pull request.
* ...
You're encouraged to create a fork of this repo, then make your changes and create a pull request when it's done. If you do so, please also check "Git branches" section.
You're encouraged to create a fork of this repo, then make your changes and create a pull request when it's done. ~~If you do so, please also check "Git branches" section.~~ (obsolete). Request for merging into `master`, if needed we will push into another branch.
If you don't have a GitHub account, you can also contact maintainers to add manually your contributions.
# Git branches
***Caution : might be obsolete***
* master : main branch, where are pushed releases and tags
* dev : for new release works, A.B.C release to A.B+1.0
* A.B.X : for fix releases (no new features), A.B.C release to A.B.C+1

79
api.lua
View file

@ -1,5 +1,8 @@
-- see: mail.md
-- translation
local S = mail.S
local f = string.format
mail.registered_on_receives = {}
@ -7,42 +10,49 @@ function mail.register_on_receive(func)
mail.registered_on_receives[#mail.registered_on_receives + 1] = func
end
mail.receive_mail_message = "You have a new message from %s! Subject: %s\nTo view it, type /mail"
mail.read_later_message = "You can read your messages later by using the /mail command"
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
m.subject = m.subject or "(No subject)"
-- limit subject line
if string.len(m.subject) > 30 then
m.subject = string.sub(m.subject,1,27) .. "..."
end
-- 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 = {}
@ -59,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
))
@ -85,24 +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 = f(mail.receive_mail_message, m.from, m.subject)
for _, player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
if recipients[name] then
minetest.chat_send_player(name, mail_alert)
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
@ -116,18 +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)"
-- limit subject line
if string.len(m.subject) > 30 then
m.subject = string.sub(m.subject,1,27) .. "..."
end
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,6 +1,10 @@
minetest.register_chatcommand("mail",{
core.register_chatcommand("mail",{
description = "Open the mail interface",
func = function(name)
mail.show_mail_menu(name)
func = function(name, param)
if #param > 0 then -- if param is not empty
mail.show_compose(name, param) -- make a new message
else
mail.show_mail_menu(name) -- show main menu
end
end
})

View file

@ -1,11 +1,11 @@
version: "3.6"
version: "4.1"
services:
sut:
build:
context: ./test
args:
ENGINE_VERSION: ${ENGINE_VERSION:-5.5.0}
ENGINE_VERSION: ${ENGINE_VERSION:-5.7.0}
user: root
volumes:
- "./:/root/.minetest/worlds/world/worldmods/mail/"

89
gui.lua
View file

@ -1,82 +1,5 @@
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)
local formspec = {}
local contacts = mail.get_contacts(name)
if playernames == nil then
local length = 0
for k, contact, i, l in mail.pairsByKeys(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] = ","
local note = contact.note
-- display an ellipsis if the note spans multiple lines
local idx = string.find(note, '\n')
if idx ~= nil then
note = string.sub(note, 1, idx-1) .. ' ...'
end
formspec[#formspec + 1] = minetest.formspec_escape(note)
if type(selected) == "string" then
if string.lower(selected) == k then
selected = i
end
end
end
if length > 0 then
if selected and type(selected) == "number" then
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = tostring(selected + 1)
end
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "]label[2,4.5;No contacts]"
end
else
if type(playernames) == "string" then
playernames = mail.parse_player_list(playernames)
end
for i,c in ipairs(playernames) do
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(c)
formspec[#formspec + 1] = ","
if contacts[string.lower(c)] == nil then
formspec[#formspec + 1] = ""
else
local note = contacts[string.lower(c)].note
-- display an ellipsis if the note spans multiple lines
local idx = string.find(note, '\n')
if idx ~= nil then
note = string.sub(note, 1, idx-1) .. ' ...'
end
formspec[#formspec + 1] = minetest.formspec_escape(note)
end
if not selected then
if type(selected) == "string" then
if string.lower(selected) == string.lower(c) then
selected = i
end
end
end
end
if #playernames > 0 and selected and type(selected) == "number" then
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = tostring(selected + 1)
end
formspec[#formspec + 1] = "]"
end
return table.concat(formspec, "")
end
if minetest.get_modpath("unified_inventory") then
mail.receive_mail_message = mail.receive_mail_message ..
" or use the mail button in the inventory"
mail.read_later_message = mail.read_later_message ..
" or by using the mail button in the inventory"
if core.get_modpath("unified_inventory") then
unified_inventory.register_button("mail", {
type = "image",
@ -87,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

12
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
@ -48,7 +48,7 @@ function mail.hud_update(playername, messages)
end
end
if unreadcount == 0 then
if unreadcount == 0 or (not mail.get_setting(playername, "hud_notifications")) then
player:hud_change(data.imageid, "text", "")
player:hud_change(data.textid, "text", "")
else

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 = "",
@ -14,8 +17,10 @@ mail = {
-- per-user ephemeral data
selected_idxs = {
inbox = {},
sent = {},
outbox = {},
drafts = {},
trash = {},
message = {},
contacts = {},
maillists = {},
to = {},
@ -25,20 +30,22 @@ mail = {
sortfield = {},
sortdirection = {},
filter = {},
multipleselection = {}
multipleselection = {},
optionstab = {},
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/uuid.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")
@ -46,24 +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/message.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/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,55 +1,123 @@
# textdomain: mail
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
Save draft=Entwurf speichern
Send=Senden
Subject=Betreff
To=An
CC=CC
Delete=Löschen
New=Neu
No drafts=Keine Entwürfe
Edit=Bearbeiten
Subject=Betreff
Player name=Spielername
your contacts.=ihre Kontakte.
The contact=Der Kontakt
Save=Speichern
That name=Der Name
is already in=ist bereits in
name cannot=Name kann nicht
be empty.=leer sein.
Back=Zurück
Note=Notiz
Maillist name=Verteilerlistenname
Desc=Beschreibung
Players=Spieler
your maillists.=ihre Verteilerlisten.
The maillist=Die Verteilerliste
Mark Read=Als gelesen makieren
Mark Unread=Als ungelesen makieren
From=Von
(No description)=(Keine Beschreibung)
No maillist=Keine Verteilerliste
CC=CC
Read=Lesen
Ascending=
Descending=
Filter=
Allow multiple selection=
selected=
(Un)select all=
No mail=Keine Nachrichten
Name=Name
No drafts=Keine Entwürfe
Trash=Papierkorb
Inbox=Posteingang
Sent messages=Nachrichten senden
Outbox=Gesendet
Drafts=Entwürfe
Contacts=Kontakte
Mail lists=Verteilerlisten
About=Über
Options=Einstellungen
Close=Schließen
(No subject)=(Kein Betreff)
Date=Datum
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=Weiter
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
Read=Lesen
Ascending=Aufsteigend
Descending=Absteigend
(No description)=(Keine Beschreibung)
No maillist=Keine Verteilerliste
Receivers=Empfänger
(Un)mute sender=Absender stummschalten/entstummen
Add=Hinzufügen
Remove=Entfernen
Name=Name
To=An
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
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
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 field=Standardsortierfeld
Default sorting direction=Standardmäßige Sortierrichtung
Move deleted messages to trash=Gelöschte Nachrichten in den Papierkorb verschieben
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
days=Tagen
hours=Stunden
minuts=Minuten
seconds=Sekunden
@1 ago=Vor @1

128
locale/mail.es.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=¡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: @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
Group by name=
Group by contribution=
Note=Nota
Settings=Ajustes
About=Acerca de
BCC=CCO
Cancel=Cancelar
Save draft=Guardar borrador
Send=Enviar
Subject=Asunto
To=Para
CC=CC
Delete=Borrar
New=Nuevo
Edit=Editar
Back=Volver
Name=Nombre
No drafts=No hay borradores
Trash=Papelera
Inbox=Entrada
Outbox=Enviados
Drafts=Borradores
Contacts=Contactos
Mail lists=Listas de correo
Options=Opciones
Close=Cerrar
(No subject)=(Sin asunto)
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
days=días
hours=horas
minuts=minutos
seconds=segundos
@1 ago=hace @1
##### not used anymore #####
Version=Versión

View file

@ -1,55 +1,123 @@
# 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: @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
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
Subject=Objet
To=À
CC=Cc
Delete=Supprimer
New=Nouveau
No drafts=Pas de brouillons
Edit=Modifier
Subject=Objet
Player name=Nom du joueur
your contacts.=vos contacts.
The contact=Ce contact
Save=Sauvegarder
That name=Ce nom
is already in=existe déjà
name cannot=nom ne peut pas
be empty.=être vide.
Back=Retour
Note=Note
Maillist name=Nom de la liste de diffusion
Desc=Desc
Players=Joueurs
your maillists.=vos listes de diffusion.
The maillist=Cette liste de diffusion
Mark Read=Marquer comme lu
Mark Unread=Marquer non lu
From=De
(No description)=Sans description
No maillist=Aucune liste de diffusion
CC=Cc
Read=Lire
Ascending=Croissant
Descending=Décroissant
Filter=Filtre
Allow multiple selection=Autoriser la sélection multiple
selected=sélectionnés
(Un)select all=Tout (dé)selectionner
No mail=Aucun mail
Name=Nom
No drafts=Aucun brouillon
Trash=Corbeille
Inbox=Boîte de réception
Sent messages=Messages envoyés
Outbox=Envoyés
Drafts=Brouillons
Contacts=Contacts
Mail lists=Listes de diffusion
About=À propos
Options=Options
Close=Fermer
(No subject)=(Sans objet)
Date=Date
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
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
(Un)mute sender=(Dé)mettre en sourdine
Add=Ajouter
Remove=Enlever
Name=Nom
To=À
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
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 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
Mark a message as read when opened=Marquer un message comme lu lorsqu'il est ouvert
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
days=jours
hours=heures
minuts=minutes
seconds=secondes
@1 ago=Il y a @1

128
locale/mail.hu.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=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: @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
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
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
Trash=
Inbox=PostaLáda
Outbox=Elküldött
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
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=
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=
(Un)mute sender=
Add=Hozzáadás
Remove=Elvétel
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
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 field=Alap válogató terület
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=Értesítések
Message list=Üzenetek listája
Fields=
Spam=
Other=
Date and Time=
years=
months=
weeks=
days=
hours=
minuts=
seconds=
@1 ago=
##### 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

123
locale/mail.pt_BR.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=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: @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=
Group by name=
Group by contribution=
Note=Nota
Settings=Ajustes
About=Sobre
BCC=BCC
Cancel=Cancelar
Save draft=Salvar rascunho
Send=Enviar
Subject=Assunto
To=Para
CC=CC
Delete=Apagar
New=Novo
Edit=Editar
Back=Voltar
Name=Nome
No drafts=Sem rascunhos
Trash=
Inbox=Entrada
Outbox=Enviadas
Drafts=Rascunhos
Contacts=Contatos
Mail lists=Lista de correios
Options=Opções
Close=Fechar
(No subject)=(Sem assunto)
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=
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=
(Un)mute sender=
Add=Adicionar
Remove=Remover
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=
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 field=Ordenamento de campo padrão
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=Notificações
Message list=Lista de mensagens
Fields=
Spam=
Other=
Date and Time=
years=
months=
weeks=
days=
hours=
minuts=
seconds=
@1 ago=

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 тому

129
locale/mail.zh_CN.tr Normal file
View file

@ -0,0 +1,129 @@
# 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=
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=名字
#if new means new mail, it would be New=新邮件
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=已选中 @2 项中的 @1 项
(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前
##### not used anymore #####
Version=版本

View file

@ -1,55 +1,128 @@
# 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=
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=新建
No drafts=沒有草稿
Edit=編輯
Subject=主旨
Player name=玩家名稱
your contacts.=
The contact=
Save=儲存
That name=
is already in=
name cannot=
be empty.=
Back=返回
Note=備註
Maillist name=郵件列表名稱
Desc=描述
Players=玩家
your maillists.=
The maillist=
Mark Read=標記已讀
Mark Unread=標記未讀
From=寄件者
(No description)=(沒有描述)
No maillist=沒有郵件列表
CC=副本
Read=閱讀
Ascending=
Descending=
Filter=
Allow multiple selection=
selected=
(Un)select all=
No mail=沒有郵件
Name=名稱
No drafts=沒有草稿
Trash=垃圾箱
Inbox=收件箱
Sent messages=寄件備份
Outbox=寄件備份
Drafts=草稿
Contacts=聯繫人
Mail lists=郵件列表
About=關於
Options=選項
Close=關閉
(No subject)=(沒有主旨)
Date=日期
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=已選擇 @2 項中的 @1 項。
(Un)select all=(取消)選中所有
No mail=沒有郵件
Read=閱讀
Ascending=
Descending=
(No description)=(沒有描述)
No maillist=沒有郵件列表
Receivers=收件人
(Un)mute sender=(取消)屏蔽寄件人
Add=加入
Remove=移除
Name=名稱
To=收件人
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,55 +1,123 @@
# 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: @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=
Group by name=
Group by contribution=
Note=
Settings=
About=
BCC=
Cancel=
Save draft=
Send=
Subject=
To=
CC=
Delete=
New=
No drafts=
Edit=
Subject=
Player name=
your contacts.=
The contact=
Save=
That name=
is already in=
name cannot=
be empty.=
Back=
Note=
Maillist name=
Desc=
Players=
your maillists.=
The maillist=
Mark Read=
Mark Unread=
From=
(No description)=
No maillist=
CC=
Read=
Ascending=
Descending=
Filter=
Allow multiple selection=
selected=
(Un)select all=
No mail=
Name=
No drafts=
Trash=
Inbox=
Sent messages=
Outbox=
Drafts=
Contacts=
Mail lists=
About=
Options=
Close=
(No subject)=
Date=
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=
(Un)select all=
No mail=
Read=
Ascending=
Descending=
(No description)=
No maillist=
Receivers=
(Un)mute sender=
Add=
Remove=
Name=
To=
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=
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=

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,5 +1,8 @@
minetest.register_on_joinplayer(function(player)
minetest.after(2, function(name)
-- translation
local S = mail.S
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)
@ -12,10 +15,9 @@ minetest.register_on_joinplayer(function(player)
end
end
if unreadcount > 0 then
minetest.chat_send_player(name,
minetest.colorize("#00f529", "(" .. unreadcount .. ") You have mail! Type /mail to read"))
if unreadcount > 0 and mail.get_setting(name, "onjoin_notifications") then
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.2.0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

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

BIN
sounds/mail_notif.ogg Normal file

Binary file not shown.

View file

@ -8,26 +8,71 @@ local function populate_entry(e)
e.inbox = e.inbox or {}
e.outbox = e.outbox or {}
e.drafts = e.drafts or {}
e.trash = e.trash or {}
e.lists = e.lists or {}
e.settings = e.settings or {}
return e
end
local cache = {}
-- retrieve the storage entry for the player
function mail.get_storage_entry(playername)
local str = mail.storage:get_string(STORAGE_PREFIX .. playername)
local key = STORAGE_PREFIX .. playername
if cache[key] then
-- use cached entry
return cache[key]
end
local str = mail.storage:get_string(key)
local entry
if str == "" then
-- new entry
return populate_entry()
entry = populate_entry()
else
-- deserialize existing entry
local e = minetest.parse_json(str)
return populate_entry(e)
local e = core.parse_json(str)
entry = populate_entry(e)
end
-- cache for next time
cache[key] = entry
return entry
end
-- entries queued for saving
local save_queued_entries = {}
-- save the storage entry for the player
function mail.set_storage_entry(playername, entry)
mail.storage:set_string(STORAGE_PREFIX .. playername, minetest.write_json(entry))
local key = STORAGE_PREFIX .. playername
-- cache
cache[key] = entry
-- enqueue for writing
save_queued_entries[key] = entry
end
local function save_worker()
for key, entry in pairs(save_queued_entries) do
-- write to backend
mail.storage:set_string(key, core.write_json(entry))
end
-- clear queue
save_queued_entries = {}
-- clear cached entries
cache = {}
-- save every second
core.after(1, save_worker)
end
-- start save-worker loop
save_worker()
-- save on shutdown
core.register_on_shutdown(save_worker)
-- get a mail by id from the players in- or outbox
function mail.get_message(playername, msg_id)
local entry = mail.get_storage_entry(playername)
@ -41,6 +86,43 @@ function mail.get_message(playername, msg_id)
return msg
end
end
for _, msg in ipairs(entry.drafts) do
if msg.id == msg_id then
return msg
end
end
for _, msg in ipairs(entry.trash) do
if msg.id == msg_id then
return msg
end
end
end
-- get player boxes where a message appears
function mail.get_message_boxes(playername, msg_id)
local entry = mail.get_storage_entry(playername)
local boxes = {}
for _, msg in ipairs(entry.inbox) do
if msg.id == msg_id then
table.insert(boxes, "inbox")
end
end
for _, msg in ipairs(entry.outbox) do
if msg.id == msg_id then
table.insert(boxes, "outbox")
end
end
for _, msg in ipairs(entry.drafts) do
if msg.id == msg_id then
table.insert(boxes, "drafts")
end
end
for _, msg in ipairs(entry.trash) do
if msg.id == msg_id then
table.insert(boxes, "trash")
end
end
return boxes
end
local function safe_find(str, sub)
@ -72,16 +154,86 @@ 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)
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
-- deletes a mail by its id
function mail.delete_mail(playername, msg_ids, delete_in_trash)
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 i = #entry.inbox, 1, -1 do
for _, deleted_msg in ipairs(msg_ids) do
if entry.inbox[i].id == deleted_msg then
table.remove(entry.inbox, i)
break
end
end
end
for i = #entry.outbox, 1, -1 do
for _, deleted_msg in ipairs(msg_ids) do
if entry.outbox[i].id == deleted_msg then
table.remove(entry.outbox, i)
break
end
end
end
for i = #entry.drafts, 1, -1 do
for _, deleted_msg in ipairs(msg_ids) do
if entry.drafts[i].id == deleted_msg then
table.remove(entry.drafts, i)
break
end
end
end
if delete_in_trash then
for i = #entry.trash, 1, -1 do
for _, deleted_msg in ipairs(msg_ids) do
if entry.trash[i].id == deleted_msg then
table.remove(entry.trash, i)
break
end
end
end
end
@ -90,50 +242,46 @@ function mail.mark_read(playername, msg_ids)
return
end
-- marks a mail unread by its id
function mail.mark_unread(playername, msg_ids)
-- move to trash mails by id
function mail.trash_mail(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
for _, id in ipairs(msg_ids) do
local msg = mail.get_message(playername, id)
msg.previous_boxes = mail.get_message_boxes(playername, id)
table.insert(entry.trash, 1, msg)
end
mail.set_storage_entry(playername, entry)
mail.delete_mail(playername, msg_ids)
return
end
-- restore a mail from trash
function mail.restore_mail(playername, msg_id)
local entry = mail.get_storage_entry(playername)
for i, msg in ipairs(entry.trash) do
if msg.id == msg_id then
-- not anymore store previous boxes in json
local previous_boxes = msg.previous_boxes
msg.previous_boxes = nil
-- restore it in all previous boxes
for _, box in ipairs(previous_boxes) do
table.insert(entry[box], msg)
end
-- then delete it from trash
table.remove(entry.trash, i)
end
end
mail.set_storage_entry(playername, entry)
return
end
-- deletes a mail by its id
function mail.delete_mail(playername, msg_ids)
-- clear the trash
function mail.empty_trash(playername)
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 i, msg in ipairs(entry.inbox) do
for _, deleted_msg in ipairs(msg_ids) do
if msg.id == deleted_msg then
table.remove(entry.inbox, i)
end
end
end
for i, msg in ipairs(entry.outbox) do
for _, deleted_msg in ipairs(msg_ids) do
if msg.id == deleted_msg then
table.remove(entry.outbox, i)
end
end
end
for i, msg in ipairs(entry.drafts) do
for _, deleted_msg in ipairs(msg_ids) do
if msg.id == deleted_msg then
table.remove(entry.drafts, i)
end
end
end
entry.trash = {}
mail.set_storage_entry(playername, entry)
return
end
@ -176,6 +324,17 @@ function mail.get_contacts(playername)
return entry.contacts
end
-- get a contact
function mail.get_contact(playername, contactname)
local entry = mail.get_storage_entry(playername)
for _, existing_contact in ipairs(entry.contacts) do
if existing_contact.name == contactname then
return existing_contact
end
end
return false
end
-- returns the maillists of a player
function mail.get_maillists(playername)
local entry = mail.get_storage_entry(playername)
@ -187,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
@ -203,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
@ -219,33 +384,81 @@ 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)
else
seen[receiver] = true
core.log("verbose", ("mail: adding %q to receiver list during maillist expansion"):format(receiver))
table.insert(expanded_receivers, receiver)
end
end
receivers_string = mail.concat_player_list(receivers)
end
return receivers
end
function mail.pairsByKeys(t, f)
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, not_transfer)
local entry = mail.get_storage_entry(playername)
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)
local entry = mail.get_storage_entry(playername)
entry.settings = {}
mail.set_storage_entry(playername, entry)
end
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

View file

@ -1,4 +1,4 @@
ARG ENGINE_VERSION=5.5.0
ARG ENGINE_VERSION=5.7.0
FROM registry.gitlab.com/minetest/minetest/server:${ENGINE_VERSION}
# copy old v1 maildb for migration testing
@ -7,11 +7,9 @@ COPY ./mail.db /root/.minetest/worlds/world/mail.db
COPY ./old_v2_player.json /root/.minetest/worlds/world/mails/
COPY ./auth.sqlite /root/.minetest/worlds/world/auth.sqlite
USER root
RUN apk add git &&\
mkdir -p /root/.minetest/worlds/world/worldmods/ &&\
git clone https://github.com/BuckarooBanzay/mtt /root/.minetest/worlds/world/worldmods/mtt
ENTRYPOINT minetestserver --config /minetest.conf

BIN
textures/search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,43 +1,152 @@
-- translation
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,0;optionstab;]] .. S("Settings") .. "," .. S("About") .. [[;2;false;false]
button[9.35,0;0.75,0.5;back;X]
label[0,0;Mail]
label[0,0.4;Provided my mt-mods]
label[0,0.8;Version: 1.1.1]
label[0,1.4;Licenses:]
label[0.2,1.8;Expat (code), WTFPL (textures)]
label[0,2.4;https://github.com/mt-mods/mail]
label[0,2.8;https://content.minetest.net/packages/mt-mods/mail]
textarea[0.5,4.0;4,5.5;;Note;]] ..
[[NOTE: Communication using this system is NOT guaranteed to be private!]] ..
[[ Admins are able to view the messages of any player.]
tablecolumns[color;text;text]
table[5,0.75;4.9,5.5;contributors;]] ..
[[#999,Contributors,,]] ..
[[#FFD700,Cheapie,Initial idea/project,]] ..
[[#FFF,Rubenwardy,Lua/UI improvements,]] ..
[[#FFF,BuckarooBanzay,Clean-ups\, Refactoring,]] ..
[[#FFF,Athozus,Outbox\, Maillists\, UI\, Drafts,]] ..
[[#FFF,fluxionary,Minor fixups,]] ..
[[#FFF,SX,Various fixes\, UI,]] ..
[[#FFF,Toby1710,Ux fixes,]] ..
[[#FFF,Peter Nerlich,CC\, BCC]
]] .. mail.theme
box[0,0;3,0.45;]] .. mail.get_color("highlighted") .. [[]
label[0.2,0;Mail]
minetest.show_formspec(name, FORMNAME, formspec)
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.") .. [[]
button[0,5.7;2,0.5;github;GitHub]
button[2,5.7;2,0.5;contentdb;ContentDB]
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
if fields.back then
local playername = player:get_player_name()
if fields.back then
mail.show_mail_menu(playername)
elseif fields.optionstab == "1" then
mail.selected_idxs.optionstab[playername] = 1
mail.show_settings(playername)
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,30 +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
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({
@ -69,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
@ -93,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)
@ -115,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;#999,]] .. 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,10 +26,10 @@ 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] = k
mail.selected_idxs.contacts[name] = tonumber(k)
break
end
end
@ -58,16 +58,16 @@ 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] = k
mail.selected_idxs.contacts[name] = tonumber(k)
break
elseif k == mail.selected_idxs.contacts[name] then
mail.delete_contact(name, contacts[mail.selected_idxs.contacts[name]].name)
mail.selected_idxs.contacts[name] = nil
found = true
else
last = k
last = tonumber(k)
end
end
if found and not mail.selected_idxs.contacts[name] then

View file

@ -1,22 +1,27 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
local drafts_formspec = "size[8.5,10;]" .. mail.theme .. [[
tabheader[0.3,1;boxtab;]] .. S("Inbox") .. "," .. S("Sent messages").. "," .. S("Drafts") .. [[;3;false;false]
function mail.show_drafts(name)
local trash_tab = ""
if mail.get_setting(name, "trash_move_enable") then
trash_tab = "," .. S("Trash")
end
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;about;]] .. S("About") .. [[]
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;#999,]] .. S("To") .. "," .. S("Subject")
table[0,0.7;5.75,10.35;drafts;]] .. mail.get_color("header") .. "," .. S("To") .. "," .. S("Subject")
function mail.show_drafts(name)
local formspec = { drafts_formspec }
local entry = mail.get_storage_entry(name)
local messages = entry.drafts
@ -27,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)")
@ -48,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"
@ -13,25 +13,23 @@ function mail.show_edit_contact(name, contact_name, note, illegal_name_hint)
]]
if illegal_name_hint == "collision" then
formspec = formspec .. [[
label[4,1;]] .. S("That name") .. [[]
label[4,1.5;]] .. S("is already in") .. [[]
label[4,2;]] .. S("your contacts.") .. [[]
textarea[4.25,1;2.5,6;;;]] ..
S("That name is already in your contacts") .. [[]
]]
elseif illegal_name_hint == "empty" then
formspec = formspec .. [[
label[4,1;]] .. S("The contact") .. [[]
label[4,1.5;]] .. S("name cannot") .. [[]
label[4,2;]] .. S("be empty.") .. [[]
textarea[4.25,1;2.5,6;;;]] ..
S("The contact name cannot be empty.") .. [[]
]]
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
@ -40,19 +38,21 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local contacts = mail.get_contacts(name)
if fields.save then
if mail.selected_idxs.contacts[name] and mail.selected_idxs.contacts[name] ~= "#NEW#" then
local contact = contacts[mail.selected_idxs.contacts[name]]
if mail.selected_idxs.contacts[name] ~= string.lower(fields.name) then
if mail.selected_idxs.contacts[name] then
local contact = contacts[mail.selected_idxs.contacts[name]] or {name = ""}
if contact.name ~= fields.name or fields.name == "" then
-- name changed!
if #fields.name == 0 then
mail.show_edit_contact(name, contact.name, fields.note, "empty")
return true
elseif contacts[string.lower(fields.name)] ~= nil then
elseif mail.get_contact(name, fields.name) then
mail.show_edit_contact(name, contact.name, fields.note, "collision")
return true
else
contact.name = fields.name
contact.note = fields.note
mail.update_contact(name, contact)
contacts[mail.selected_idxs.contacts[name]] = nil
end

View file

@ -1,11 +1,9 @@
-- translation
local S = minetest.get_translator("mail")
local old_lists_names = {}
local S = mail.S
local FORMNAME = "mail:editmaillist"
function mail.show_edit_maillist(playername, maillist_name, desc, players, illegal_name_hint)
old_lists_names[playername] = maillist_name
local formspec = [[
size[6,7]
button[4,6.25;2,0.5;back;]] .. S("Back") .. [[]
@ -16,38 +14,68 @@ function mail.show_edit_maillist(playername, maillist_name, desc, players, illeg
]]
if illegal_name_hint == "collision" then
formspec = formspec .. [[
label[4,1;]] .. S("That name") .. [[]
label[4,1.5;]] .. S("is already in") .. [[]
label[4,2;]] .. S("your maillists.") .. [[]
textarea[4.25,1;2.5,6;;;]] ..
S("That name is already in your mailing lists.") .. [[]
]]
elseif illegal_name_hint == "empty" then
formspec = formspec .. [[
label[4,1;]] .. S("The maillist") .. [[]
label[4,1.5;]] .. S("name cannot") .. [[]
label[4,2;]] .. S("be empty.") .. [[]
textarea[4.25,1;2.5,6;;;]] ..
S("The mailing list name cannot be empty.") .. [[]
]]
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
local name = player:get_player_name()
local maillists = mail.get_maillists(name)
if fields.save then
local old_maillist = maillists[mail.selected_idxs.maillists[name]] or {name = ""}
if mail.selected_idxs.maillists[name] then
if old_maillist.name ~= fields.name or fields.name == "" then
-- name changed!
if #fields.name == 0 then
mail.show_edit_maillist(name, old_maillist.name, fields.desc, fields.players, "empty")
return true
elseif mail.get_maillist_by_name(name, fields.name) then
mail.show_edit_maillist(name, old_maillist.name, fields.desc, fields.players, "collision")
return true
else
mail.update_maillist(name, {
owner = name,
name = fields.name,
desc = fields.desc,
players = mail.parse_player_list(fields.players)
}, old_lists_names[name])
}, old_maillist.name)
end
else
mail.update_maillist(name, {
owner = name,
name = fields.name,
desc = fields.desc,
players = mail.parse_player_list(fields.players)
}, old_maillist.name)
end
else
mail.update_maillist(name, {
owner = name,
name = fields.name,
desc = fields.desc,
players = mail.parse_player_list(fields.players)
}, old_maillist.name)
end
mail.show_maillists(name)
elseif fields.back then

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
@ -10,16 +10,23 @@ local function messageGetter(messages, sortfield, ascending, filter)
end
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "mail:inbox" and formname ~= "mail:sent" and formname ~= "mail:drafts" then
return
elseif fields.quit then
local function nonempty(x)
return ((type(x)=="table")and(#x>0))
end
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
end
if fields.quit then
return true
end
-- Get player name and handle / convert common input fields
local name = player:get_player_name()
local filter = fields.filter or mail.selected_idxs.filter[name] or ""
local filter = (fields.search and fields.filter) or mail.selected_idxs.filter[name] or ""
local sortfieldindex = tonumber(fields.sortfield or mail.selected_idxs.sortfield[name]) or 3
local sortdirection = fields.sortdirection or mail.selected_idxs.sortdirection[name] or "1"
local inboxsortfield = ({"from","subject","time"})[sortfieldindex]
@ -27,7 +34,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
-- Be sure that inbox/outbox selected idxs aren't nil
mail.selected_idxs.inbox[name] = mail.selected_idxs.inbox[name] or {}
mail.selected_idxs.sent[name] = mail.selected_idxs.sent[name] or {}
mail.selected_idxs.outbox[name] = mail.selected_idxs.outbox[name] or {}
-- Store common player configuration for reuse
mail.selected_idxs.sortfield[name] = sortfieldindex
@ -40,25 +47,31 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
-- Avoid several selected after disabling the multiple selection
if not mail.selected_idxs.multipleselection[name] then
mail.selected_idxs.inbox[name] = { mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]] }
mail.selected_idxs.sent[name] = { mail.selected_idxs.sent[name][#mail.selected_idxs.sent[name]] }
mail.selected_idxs.outbox[name] = { mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]] }
end
-- split inbox and sent msgs for different tests
-- split inbox and outbox msgs for different tests
local entry = mail.get_storage_entry(name)
local messagesDrafts = entry.drafts
local getInbox = messageGetter(entry.inbox, inboxsortfield, sortdirection == "2", filter)
local getOutbox = messageGetter(entry.outbox, outboxsortfield, sortdirection == "2", filter)
local messagesTrash = entry.trash
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
@ -67,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
@ -75,51 +88,62 @@ 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].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
return true
end
if fields.sent then -- sent table
local evt = minetest.explode_table_event(fields.sent)
if fields.outbox then -- outbox table
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.sent[name] then
mail.selected_idxs.sent[name] = {}
if not mail.selected_idxs.outbox[name] then
mail.selected_idxs.outbox[name] = {}
end
local selected_id = 0
if mail.selected_idxs.sent[name] and #mail.selected_idxs.sent[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.sent[name]) do
if getOutbox()[evt.row-1].id == selected_msg then
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 outbox.id == selected_msg then
selected_id = i
table.remove(mail.selected_idxs.sent[name], i)
table.remove(mail.selected_idxs.outbox[name], i)
break
end
end
end
if selected_id == 0 then
table.insert(mail.selected_idxs.sent[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.sent[name] = { getOutbox()[evt.row-1].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
@ -127,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"
@ -138,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,
@ -150,23 +175,50 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
return true
end
if fields.trash then -- trash table
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"
end
mail.selected_idxs.sortfield[name] = evt.column-1 -- update column
mail.show_mail_menu(name)
return
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
end
if fields.boxtab == "1" then
mail.selected_idxs.boxtab[name] = 1
mail.show_inbox(name, sortfieldindex, sortdirection, filter)
elseif fields.boxtab == "2" then
mail.selected_idxs.boxtab[name] = 2
mail.show_sent(name, sortfieldindex, sortdirection, filter)
mail.show_outbox(name, sortfieldindex, sortdirection, filter)
elseif fields.boxtab == "3" then
mail.selected_idxs.boxtab[name] = 3
mail.show_drafts(name)
elseif fields.boxtab == "4" then
mail.selected_idxs.boxtab[name] = 4
mail.show_trash(name)
elseif fields.read then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] then -- inbox table
mail.show_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
elseif formname == "mail:sent" and mail.selected_idxs.sent[name] then -- sent table
mail.show_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
if formname == "mail:inbox" and nonempty(mail.selected_idxs.inbox[name]) then -- inbox table
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.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.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
@ -182,40 +234,68 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
elseif fields.delete then
local trash_enabled = mail.get_setting(name, "trash_move_enable")
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] then -- inbox table
if trash_enabled then
mail.trash_mail(name, mail.selected_idxs.inbox[name])
else
mail.delete_mail(name, mail.selected_idxs.inbox[name])
elseif formname == "mail:sent" and mail.selected_idxs.sent[name] then -- sent table
mail.delete_mail(name, mail.selected_idxs.sent[name])
end
mail.selected_idxs.inbox[name] = {}
elseif formname == "mail:outbox" and mail.selected_idxs.outbox[name] then -- outbox table
if trash_enabled then
mail.trash_mail(name, mail.selected_idxs.outbox[name])
else
mail.delete_mail(name, mail.selected_idxs.outbox[name])
end
mail.selected_idxs.outbox[name] = {}
elseif formname == "mail:drafts" and messagesDrafts[mail.selected_idxs.drafts[name]] then -- drafts table
if trash_enabled then
mail.trash_mail(name, messagesDrafts[mail.selected_idxs.drafts[name]].id)
else
mail.delete_mail(name, messagesDrafts[mail.selected_idxs.drafts[name]].id)
end
mail.selected_idxs.drafts[name] = nil
elseif formname == "mail:trash" and messagesTrash[mail.selected_idxs.trash[name]] then -- trash table
mail.delete_mail(name, messagesTrash[mail.selected_idxs.trash[name]].id, true)
end
mail.show_mail_menu(name, sortfieldindex, sortdirection, filter)
elseif fields.restore then
if messagesTrash[mail.selected_idxs.trash[name]] then
mail.restore_mail(name, messagesTrash[mail.selected_idxs.trash[name]].id)
end
mail.show_mail_menu(name, sortfieldindex, sortdirection, filter)
elseif fields.reply then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] and #mail.selected_idxs.inbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
mail.reply(name, message)
elseif formname == "mail:sent" and mail.selected_idxs.sent[name] then
local message = mail.get_message(name, mail.selected_idxs.sent[name][#mail.selected_idxs.sent[name]])
elseif
formname == "mail:outbox" and mail.selected_idxs.outbox[name] and #mail.selected_idxs.outbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]])
mail.reply(name, message)
end
elseif fields.replyall then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] and #mail.selected_idxs.inbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
mail.replyall(name, message)
elseif formname == "mail:sent" and mail.selected_idxs.sent[name] then
local message = mail.get_message(name, mail.selected_idxs.sent[name][#mail.selected_idxs.sent[name]])
elseif
formname == "mail:outbox" and mail.selected_idxs.outbox[name] and #mail.selected_idxs.outbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]])
mail.replyall(name, message)
end
elseif fields.forward then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] then
if formname == "mail:inbox" and mail.selected_idxs.inbox[name] and #mail.selected_idxs.inbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
mail.forward(name, message)
elseif formname == "mail:sent" and mail.selected_idxs.sent[name] then
local message = mail.get_message(name, mail.selected_idxs.sent[name][#mail.selected_idxs.sent[name]])
elseif
formname == "mail:outbox" and mail.selected_idxs.outbox[name] and #mail.selected_idxs.outbox[name] > 0 then
local message = mail.get_message(name, mail.selected_idxs.outbox[name][#mail.selected_idxs.outbox[name]])
mail.forward(name, message)
end
@ -233,43 +313,53 @@ 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)
elseif fields.empty then
mail.empty_trash(name)
mail.show_mail_menu(name)
elseif fields.contacts then
mail.show_contacts(name)
elseif fields.maillists then
mail.show_maillists(name)
elseif fields.about then
mail.show_about(name)
elseif fields.options then
mail.show_options(name)
elseif fields.selectall then
if formname == "mail:inbox" then
if not mail.selected_idxs.inbox[name] then
mail.selected_idxs.inbox[name] = {}
end
if #mail.selected_idxs.inbox[name] >= #getInbox() then -- if selection is full
mail.selected_idxs.inbox[name] = {}
else
mail.selected_idxs.inbox[name] = {} -- reset to avoid duplicates
mail.selected_idxs.multipleselection[name] = true
local selected_number = #mail.selected_idxs.inbox[name]
mail.selected_idxs.inbox[name] = {} -- reset for select, unselect and not existing
mail.selected_idxs.multipleselection[name] = true -- enable as the button were pressed
if selected_number < #getInbox() then -- then populate it if selection isn't full
for _, msg in ipairs(getInbox()) do
table.insert(mail.selected_idxs.inbox[name], msg.id)
end
end
elseif formname == "mail:sent" then
if not mail.selected_idxs.sent[name] then
mail.selected_idxs.sent[name] = {}
end
if #mail.selected_idxs.sent[name] >= #getOutbox() then -- if selection is full
mail.selected_idxs.sent[name] = {}
else
mail.selected_idxs.sent[name] = {} -- reset to avoid duplicates
mail.selected_idxs.multipleselection[name] = true
elseif formname == "mail:outbox" then
local selected_number = #mail.selected_idxs.outbox[name]
mail.selected_idxs.outbox[name] = {} -- reset for select, unselect and not existing
mail.selected_idxs.multipleselection[name] = true -- enable as the button were pressed
if selected_number < #getOutbox() then -- then populate it if selection isn't full
for _, msg in ipairs(getOutbox()) do
table.insert(mail.selected_idxs.sent[name], msg.id)
table.insert(mail.selected_idxs.outbox[name], msg.id)
end
end
end

View file

@ -1,15 +1,41 @@
-- 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]) or 3
sortdirection = sortdirection or mail.selected_idxs.sortdirection[name] or "1"
sortfieldindex = tonumber(sortfieldindex or mail.selected_idxs.sortfield[name])
or mail.get_setting(name, "defaultsortfield") or 3
sortdirection = tostring(sortdirection or mail.selected_idxs.sortdirection[name]
or mail.get_setting(name, "defaultsortdirection") or "1")
filter = filter or mail.selected_idxs.filter[name] or ""
mail.selected_idxs.inbox[name] = mail.selected_idxs.inbox[name] or {}
local inbox_formspec = "size[8.5,10;]" .. mail.theme .. [[
tabheader[0.3,1;boxtab;]] .. S("Inbox") .. "," .. S("Sent messages").. "," .. S("Drafts") .. [[;1;false;false]
local entry = mail.get_storage_entry(name)
local sortfield = ({"from","subject","time"})[sortfieldindex]
local messages = mail.sort_messages(entry.inbox, sortfield, sortdirection == "2", filter)
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
local is_present = false
for _, msg in ipairs(messages) do
if msg.id == selected_msg then
is_present = true
break
end
end
if not is_present then
table.remove(mail.selected_idxs.inbox[name], i)
end
end
end
local trash_tab = ""
if mail.get_setting(name, "trash_move_enable") then
trash_tab = "," .. S("Trash")
end
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]
button[6,0.10;2.5,0.5;new;]] .. S("New") .. [[]
button[6,0.95;2.5,0.5;read;]] .. S("Read") .. [[]
@ -17,37 +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;about;]] .. S("About") .. [[]
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") .. [[]
dropdown[0,8.4;2,0.5;sortfield;]] ..
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,9.5;2,0.5;sortfield;]] ..
S("From") .. "," .. S("Subject") .. "," .. S("Date") .. [[;]] .. sortfieldindex .. [[;true]
dropdown[2.0,8.4;2,0.5;sortdirection;]] ..
dropdown[2.0,9.5;2,0.5;sortdirection;]] ..
S("Ascending") .. "," .. S("Descending") .. [[;]] .. sortdirection .. [[;true]
field[4.25,8.85;1.4,0.5;filter;]] .. S("Filter") .. [[:;]] .. filter .. [[]
button[5.14,8.52;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;]] .. tostring(#mail.selected_idxs.inbox[name]) .. " " .. S("selected") .. [[]
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.35;inbox;#999,]] .. 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 }
local entry = mail.get_storage_entry(name)
local sortfield = ({"from","subject","time"})[sortfieldindex]
local messages = mail.sort_messages(entry.inbox, sortfield, sortdirection == "2", filter)
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
@ -58,43 +93,30 @@ function mail.show_inbox(name, sortfieldindex, sortdirection, filter)
end
end
if selected_id > 0 then
if not message.read then
if not mail.player_in_list(name, message.to) then
formspec[#formspec + 1] = ",#A39E5D"
else
formspec[#formspec + 1] = ",#A39E19"
table.insert(displayed_color, "selected")
end
else
if not mail.player_in_list(name, message.to) then
formspec[#formspec + 1] = ",#899888"
else
formspec[#formspec + 1] = ",#466432"
if not message.read and unread_color_enable then
table.insert(displayed_color, "important")
end
if not mail.player_in_list(name, message.to) and cc_color_enable then
table.insert(displayed_color, "additional")
end
else
if not message.read then
if not mail.player_in_list(name, message.to) then
formspec[#formspec + 1] = ",#FFD788"
else
formspec[#formspec + 1] = ",#FFD700"
if message.spam then
table.insert(displayed_color, "warning")
end
else
if not mail.player_in_list(name, message.to) then
formspec[#formspec + 1] = ",#CCCCDD"
else
if table.indexof(mute_list, message.from) >= 1 then
table.insert(displayed_color, "muted")
end
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)")
@ -104,5 +126,6 @@ function mail.show_inbox(name, sortfieldindex, sortdirection, filter)
else
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

37
ui/init.lua Normal file
View file

@ -0,0 +1,37 @@
-- 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)
local index = mail.selected_idxs.boxtab[playername] or 1
if not mail.selected_idxs.boxtab[playername] then
mail.selected_idxs.boxtab[playername] = 1
end
if index == 1 then
mail.show_inbox(playername, sortfield, sortdirection, filter)
elseif index == 2 then
mail.show_outbox(playername, sortfield, sortdirection, filter)
elseif index == 3 then
mail.show_drafts(playername)
elseif index == 4 then
mail.show_trash(playername)
end
end

View file

@ -1,12 +0,0 @@
-- helper function for tabbed overview
function mail.show_mail_menu(playername, sortfield, sortdirection, filter)
local index = mail.selected_idxs.boxtab[playername] or 1
if index == 1 then
mail.show_inbox(playername, sortfield, sortdirection, filter)
elseif index == 2 then
mail.show_sent(playername, sortfield, sortdirection, filter)
elseif index == 3 then
mail.show_drafts(playername)
end
end

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;#999,]] .. 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,56 +1,94 @@
-- 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;#466432]
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) .. [[]
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 cc = minetest.formspec_escape(message.cc) 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 = 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("%Y-%m-%d %X", 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 then
if not message.read and mail.get_setting(name, "auto_marking_read") then
-- mark as read
mail.mark_read(name, id)
end
minetest.show_formspec(name, FORMNAME, formspec)
core.show_formspec(name, FORMNAME, formspec)
end
function mail.reply(name, message)
local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body
mail.show_compose(name, message.from, "Re: "..message.subject, replyfooter)
if not message then
-- TODO: workaround for https://github.com/mt-mods/mail/issues/84
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
mail.show_compose(name, message.from, "Re: "..message.subject, interleave_msg(message.body))
end
function mail.replyall(name, message)
local replyfooter = "Type your reply here.\n\n--Original message follows--\n" ..message.body
if not message then
-- TODO: workaround for https://github.com/mt-mods/mail/issues/84
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
-- new recipients are the sender plus the original recipients, minus ourselves
local recipients = message.to or ""
@ -76,59 +114,61 @@ 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 message = mail.get_message(name, mail.selected_idxs.message[name])
if fields.back then
mail.show_mail_menu(name)
return true -- don't uselessly set messages
elseif fields.reply then
local message = ""
if mail.selected_idxs.inbox[name] then
message = mail.get_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
elseif mail.selected_idxs.sent[name] then
message = mail.get_message(name, mail.selected_idxs.sent[name][#mail.selected_idxs.sent[name]])
end
mail.reply(name, message)
elseif fields.replyall then
local message = ""
if mail.selected_idxs.inbox[name] then
message = mail.get_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
elseif mail.selected_idxs.sent[name] then
message = mail.get_message(name, mail.selected_idxs.sent[name][#mail.selected_idxs.sent[name]])
end
mail.replyall(name, message)
elseif fields.forward then
local message = ""
if mail.selected_idxs.inbox[name] then
message = mail.get_message(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
elseif mail.selected_idxs.sent[name] then
message = mail.get_message(name, mail.selected_idxs.sent[name][#mail.selected_idxs.sent[name]])
end
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.selected_idxs.inbox[name] then
mail.delete_mail(name, mail.selected_idxs.inbox[name][#mail.selected_idxs.inbox[name]])
elseif mail.selected_idxs.sent[name] then
mail.delete_mail(name, mail.selected_idxs.sent[name][#mail.selected_idxs.sent[name]])
if mail.get_setting(name, "trash_move_enable") and mail.selected_idxs.boxtab[name] ~= 4 then
mail.trash_mail(name, message.id)
else
mail.delete_mail(name, message.id, true)
end
mail.show_mail_menu(name)
elseif fields.receivers then
mail.show_receivers(name, message.id)
end
return true

13
ui/options.lua Normal file
View file

@ -0,0 +1,13 @@
-- helper function for tabbed options
function mail.show_options(playername)
local index = mail.selected_idxs.optionstab[playername] or 1
if not mail.selected_idxs.optionstab[playername] then
mail.selected_idxs.optionstab[playername] = 1
end
if index == 1 then
mail.show_settings(playername)
elseif index == 2 then
mail.show_about(playername)
end
end

View file

@ -1,15 +1,41 @@
-- translation
local S = minetest.get_translator("mail")
local S = mail.S
function mail.show_sent(name, sortfieldindex, sortdirection, filter)
sortfieldindex = tonumber(sortfieldindex or mail.selected_idxs.sortfield[name]) or 3
sortdirection = sortdirection or mail.selected_idxs.sortdirection[name] or "1"
function mail.show_outbox(name, sortfieldindex, sortdirection, filter)
sortfieldindex = tonumber(sortfieldindex or mail.selected_idxs.sortfield[name])
or mail.get_setting(name, "defaultsortfield") or 3
sortdirection = tostring(sortdirection or mail.selected_idxs.sortdirection[name]
or mail.get_setting(name, "defaultsortdirection") or "1")
filter = filter or mail.selected_idxs.filter[name] or ""
mail.selected_idxs.sent[name] = mail.selected_idxs.sent[name] or {}
mail.selected_idxs.outbox[name] = mail.selected_idxs.outbox[name] or {}
local sent_formspec = "size[8.5,10;]" .. mail.theme .. [[
tabheader[0.3,1;boxtab;]] .. S("Inbox") .. "," .. S("Sent messages").. "," .. S("Drafts") .. [[;2;false;false]
local entry = mail.get_storage_entry(name)
local sortfield = ({"to","subject","time"})[sortfieldindex]
local messages = mail.sort_messages(entry.outbox, sortfield, sortdirection == "2", filter)
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
local is_present = false
for _, msg in ipairs(messages) do
if msg.id == selected_msg then
is_present = true
break
end
end
if not is_present then
table.remove(mail.selected_idxs.outbox[name], i)
end
end
end
local trash_tab = ""
if mail.get_setting(name, "trash_move_enable") then
trash_tab = "," .. S("Trash")
end
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]
button[6,0.10;2.5,0.5;new;]] .. S("New") .. [[]
button[6,0.95;2.5,0.5;read;]] .. S("Read") .. [[]
@ -17,38 +43,41 @@ function mail.show_sent(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;about;]] .. S("About") .. [[]
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") .. [[]
dropdown[0,8.4;2,0.5;sortfield;]] ..
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,9.5;2,0.5;sortfield;]] ..
S("To") .. "," .. S("Subject") .. "," .. S("Date") .. [[;]] .. sortfieldindex .. [[;true]
dropdown[2.0,8.4;2,0.5;sortdirection;]] ..
dropdown[2.0,9.5;2,0.5;sortdirection;]] ..
S("Ascending") .. "," .. S("Descending") .. [[;]] .. sortdirection .. [[;true]
field[4.25,8.85;1.4,0.5;filter;]] .. S("Filter") .. [[:;]] .. filter .. [[]
button[5.14,8.52;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;]] .. tostring(#mail.selected_idxs.sent[name]) .. " " .. S("selected") .. [[]
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.35;sent;#999,]] .. S("To") .. "," .. S("Subject")
local formspec = { sent_formspec }
local entry = mail.get_storage_entry(name)
local sortfield = ({"to","subject","time"})[sortfieldindex]
local messages = mail.sort_messages(entry.outbox, sortfield, sortdirection == "2", filter)
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
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.sent[name] and #mail.selected_idxs.sent[name] > 0 then
for i, selected_msg in ipairs(mail.selected_idxs.sent[name]) do
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 message.id == selected_msg then
selected_id = i
break
@ -56,19 +85,23 @@ function mail.show_sent(name, sortfieldindex, sortdirection, filter)
end
end
if selected_id > 0 then
formspec[#formspec + 1] = ",#466432"
else
formspec[#formspec + 1] = ","
table.insert(displayed_color, "selected")
end
formspec[#formspec + 1] = "," .. mail.get_color(displayed_color)
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = minetest.formspec_escape(message.to)
if string.len(message.to) > 20 then
formspec[#formspec + 1] = core.formspec_escape(string.sub(message.to, 1, 17))
formspec[#formspec + 1] = "..."
else
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)")
@ -78,5 +111,6 @@ function mail.show_sent(name, sortfieldindex, sortdirection, filter)
else
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("No mail") .. "]"
end
minetest.show_formspec(name, "mail:sent", table.concat(formspec, ""))
core.show_formspec(name, "mail:outbox", table.concat(formspec, ""))
end

54
ui/receivers.lua Normal file
View file

@ -0,0 +1,54 @@
-- translation
local S = core.get_translator("mail")
local FORMNAME = "mail:receivers"
function mail.show_receivers(name, id)
local message = mail.get_message(name, id)
local formspec = [[
size[8,6]
box[0,0;7,1.1;]] .. mail.get_color("highlighted") .. [[]
button[7.25,0.15;0.75,0.5;back;X]
label[0.2,0.1;]] .. S("From") .. [[: %s]
label[0.2,0.5;]] .. S("Date") .. [[: %s]
tablecolumns[color;text]
table[0,1.5;3.8,4.5;to;%s]
tablecolumns[color;text]
table[4,1.5;3.8,4.5;cc;%s]
]] .. mail.theme
local from = core.formspec_escape(message.from) or ""
local to = mail.parse_player_list(message.to or "")
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.get_color("header") .. "," .. S("CC") .. ",,"
cc_str = cc_str .. table.concat(cc, ",,")
local date = type(message.time) == "number"
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)
core.show_formspec(name, FORMNAME, formspec)
end
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= FORMNAME then
return
end
local name = player:get_player_name()
local message_id = mail.selected_idxs.message[name]
if fields.back then
mail.show_message(name, message_id)
end
return true
end)

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;#999,]] .. 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;#999,]] .. 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;#999,]] .. 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

264
ui/settings.lua Normal file
View file

@ -0,0 +1,264 @@
-- translation
local S = mail.S
local FORMNAME = "mail:settings"
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)
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 formspec = [[
size[10,6;]
tabheader[0,0;optionstab;]] .. S("Settings") .. "," .. S("About") .. [[;1;false;false]
button[9.35,0;0.75,0.5;back;X]
tablecolumns[tree;text]
table[0,0.775;3,4.5;groups;]] .. groups_str .. [[;]] .. group_index .. [[]
box[0,0;3,0.45;]] .. mail.get_color("highlighted") .. [[]
label[0.2,0;]] .. mail.settings_groups[group_index].label .. [[]
button[0,5.65;2.5,0.5;reset;]] .. S("Reset") .. [[]
button[7.5,5.65;2.5,0.5;save;]] .. S("Save") .. [[]
]]
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, ",,") .. "]"
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 .. [[;-]
]]
if data.tooltip then
formspec = formspec .. "tooltip[field_" .. setting .. ";" .. data.tooltip .. "]"
end
y = y - 0.4
end
end
formspec = formspec .. mail.theme
core.show_formspec(name, FORMNAME, formspec)
end
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
elseif fields.optionstab == "2" then
mail.selected_idxs.optionstab[playername] = 2
mail.show_about(playername)
return
elseif fields.save then
-- 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)
elseif fields.reset then
mail.reset_settings(playername)
mail.show_settings(playername)
end
return
end)

53
ui/trash.lua Normal file
View file

@ -0,0 +1,53 @@
-- translation
local S = mail.S
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]
button[6,0.10;2.5,0.5;new;]] .. S("New") .. [[]
button[6,0.95;2.5,0.5;read;]] .. S("Read") .. [[]
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,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,10.35;trash;]] .. mail.get_color("header") .. "," .. S("From/To") .. "," .. S("Subject")
function mail.show_trash(name)
local formspec = { trash_formspec }
local entry = mail.get_storage_entry(name)
local messages = entry.trash
if messages[1] then
for _, message in ipairs(messages) do
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
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] = core.formspec_escape(string.sub(message.subject, 1, 27))
formspec[#formspec + 1] = "..."
else
formspec[#formspec + 1] = core.formspec_escape(message.subject)
end
else
formspec[#formspec + 1] = S("(No subject)")
end
end
if mail.selected_idxs.trash[name] then
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = tostring(mail.selected_idxs.trash[name] + 1)
end
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "]label[2.25,4.5;" .. S("Trash is empty") .. "]"
end
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

75
util/contact.lua Normal file
View file

@ -0,0 +1,75 @@
-- translation
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)
local formspec = {}
local contacts = mail.get_contacts(name)
if playernames == nil then
local length = 0
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] = core.formspec_escape(contact.name)
formspec[#formspec + 1] = ","
local note = contact.note
-- display an ellipsis if the note spans multiple lines
local idx = string.find(note, '\n')
if idx ~= nil then
note = string.sub(note, 1, idx-1) .. ' ...'
end
formspec[#formspec + 1] = core.formspec_escape(note)
if type(selected) == "string" then
if string.lower(selected) == k then
selected = i
end
end
end
if length > 0 then
if selected and type(selected) == "number" then
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = tostring(selected + 1)
end
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "]label[2,4.5;" .. S("No contacts") .. "]"
end
else
if type(playernames) == "string" then
playernames = mail.parse_player_list(playernames)
end
for i,c in ipairs(playernames) do
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = ","
formspec[#formspec + 1] = core.formspec_escape(c)
formspec[#formspec + 1] = ","
if contacts[string.lower(c)] == nil then
formspec[#formspec + 1] = ""
else
local note = contacts[string.lower(c)].note
-- display an ellipsis if the note spans multiple lines
local idx = string.find(note, '\n')
if idx ~= nil then
note = string.sub(note, 1, idx-1) .. ' ...'
end
formspec[#formspec + 1] = core.formspec_escape(note)
end
if not selected then
if type(selected) == "string" then
if string.lower(selected) == string.lower(c) then
selected = i
end
end
end
end
if #playernames > 0 and selected and type(selected) == "number" then
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = tostring(selected + 1)
end
formspec[#formspec + 1] = "]"
end
return table.concat(formspec, "")
end

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

29
util/time_ago.lua Normal file
View file

@ -0,0 +1,29 @@
-- translation
local S = mail.S
function mail.time_ago(t)
local elapsed = os.time() - t
local str = ""
local time_units = {
{ S("years"), 31536000 },
{ S("months"), 2592000 },
{ S("weeks"), 604800 },
{ S("days"), 86400 },
{ S("hours"), 3600 },
{ S("minuts"), 60 },
{ S("seconds"), 1 },
}
for _, u in ipairs(time_units) do
local n = math.modf(elapsed/u[2])
if n > 0 then
str = str .. " " .. n .. " " .. u[1]
elapsed = elapsed - n * u[2]
end
end
str = string.sub(str, 2, -1)
return S("@1 ago", str)
end