Compare commits

..

125 Commits

Author SHA1 Message Date
rubenwardy
26d44ba357 Add in-client views counter to stats pages 2025-04-27 14:09:40 +01:00
rubenwardy
c21a56585f Update github_username on sign in 2025-04-13 15:44:13 +01:00
rubenwardy
cd53696831 Fix not being able to disconnect GitHub accounts 2025-04-13 15:39:48 +01:00
rubenwardy
8777d2bfd3 Remove include_images from /for-client/reviews/ doc 2025-03-30 15:05:42 +01:00
rubenwardy
00cf79224d Document /for-client/reviews/ 2025-03-30 15:04:58 +01:00
rubenwardy
5b0d42173f Add terms of service to comply with Online Safety Act 2023 (#578) 2025-03-09 13:48:03 +00:00
rubenwardy
6891ee8b19 Remove ability to create private threads except for approval threads 2025-03-07 19:31:38 +00:00
rubenwardy
5f2b2ffdf1 Require screenshot approval for non-trusted members 2025-03-05 20:19:18 +00:00
rubenwardy
f0e67c93d6 Prohibit illegal drugs 2025-03-04 14:25:35 +00:00
rubenwardy
e5fd908b54 Fix bad links in Tamil translation 2025-02-11 18:43:52 +00:00
rubenwardy
3af7a19563 Enable Czech and Tamil languages 2025-02-11 18:37:15 +00:00
ninjum
e1f0792dce Translated using Weblate (Galician)
Currently translated at 7.2% (87 of 1200 strings)

Co-authored-by: ninjum <ninhum@gmx.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/gl/
Translation: Minetest/ContentDB
2025-02-08 23:00:46 +01:00
Ilia
2e91656245 Translated using Weblate (Persian)
Currently translated at 18.0% (217 of 1200 strings)

Co-authored-by: Ilia <iliaabbasi@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/fa/
Translation: Minetest/ContentDB
2025-02-08 23:00:46 +01:00
Тарас Арт
8378095343 Translated using Weblate (Ukrainian)
Currently translated at 82.2% (987 of 1200 strings)

Translated using Weblate (Ukrainian)

Currently translated at 77.6% (932 of 1200 strings)

Co-authored-by: Тарас Арт <fromkaniv@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/uk/
Translation: Minetest/ContentDB
2025-02-08 23:00:46 +01:00
Balázs Kovács
b6f67d4b0e Translated using Weblate (Hungarian)
Currently translated at 37.9% (455 of 1200 strings)

Co-authored-by: Balázs Kovács <kovacs.balazs.ktk@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/hu/
Translation: Minetest/ContentDB
2025-02-08 23:00:46 +01:00
Miguel
ffe808c915 Translated using Weblate (Spanish)
Currently translated at 100.0% (1200 of 1200 strings)

Co-authored-by: Miguel <mp0187595@tutamail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/es/
Translation: Minetest/ContentDB
2025-02-08 23:00:46 +01:00
தமிழ்நேரம்
6169f4c0e4 Translated using Weblate (Tamil)
Currently translated at 100.0% (1200 of 1200 strings)

Added translation using Weblate (Tamil)

Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/ta/
Translation: Minetest/ContentDB
2025-02-08 23:00:45 +01:00
Poesty Li
8c5e542268 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1200 of 1200 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/zh_Hans/
Translation: Minetest/ContentDB
2025-02-08 23:00:45 +01:00
reimu105
893b902314 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 37.6% (452 of 1200 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 37.4% (449 of 1200 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 37.2% (447 of 1200 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 36.7% (441 of 1200 strings)

Co-authored-by: reimu105 <peter112548@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/zh_Hant/
Translation: Minetest/ContentDB
2025-02-08 23:00:45 +01:00
Siber
2435e8e3d0 Translated using Weblate (Turkish)
Currently translated at 96.9% (1163 of 1200 strings)

Translated using Weblate (Turkish)

Currently translated at 96.9% (1163 of 1200 strings)

Translated using Weblate (Turkish)

Currently translated at 94.8% (1138 of 1200 strings)

Translated using Weblate (Turkish)

Currently translated at 82.9% (995 of 1200 strings)

Co-authored-by: Siber <anonloxu@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/tr/
Translation: Minetest/ContentDB
2025-02-08 23:00:45 +01:00
jolesh
628d44460d Translated using Weblate (Esperanto)
Currently translated at 15.5% (187 of 1200 strings)

Co-authored-by: jolesh <jolesh0815@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/eo/
Translation: Minetest/ContentDB
2025-02-08 23:00:45 +01:00
Tanavit MINETEST
fc1b7e500d Translated using Weblate (French)
Currently translated at 99.5% (1194 of 1200 strings)

Translated using Weblate (French)

Currently translated at 99.5% (1194 of 1200 strings)

Co-authored-by: Tanavit MINETEST <tanavit@posto.ovh>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/fr/
Translation: Minetest/ContentDB
2025-02-08 23:00:44 +01:00
ats
7cfbbbe7e6 Translated using Weblate (Portuguese)
Currently translated at 54.9% (659 of 1200 strings)

Co-authored-by: ats <athos2256@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/pt/
Translation: Minetest/ContentDB
2025-02-08 23:00:44 +01:00
Stepan Bazrov
bea743b536 Translated using Weblate (Russian)
Currently translated at 100.0% (1200 of 1200 strings)

Co-authored-by: Stepan Bazrov <bazrovstepan@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/ru/
Translation: Minetest/ContentDB
2025-02-08 23:00:44 +01:00
Jorge Rodríguez
6ccc575cb4 Translated using Weblate (Spanish)
Currently translated at 100.0% (1200 of 1200 strings)

Translated using Weblate (Spanish)

Currently translated at 99.6% (1196 of 1200 strings)

Co-authored-by: Jorge Rodríguez <mr.jrodriguez05@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/es/
Translation: Minetest/ContentDB
2025-02-08 23:00:44 +01:00
Muhammad Nuruddin
9e4be57754 Translated using Weblate (Malay)
Currently translated at 100.0% (1200 of 1200 strings)

Co-authored-by: Muhammad Nuruddin <nuruddin6106@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/ms/
Translation: Minetest/ContentDB
2025-02-08 23:00:44 +01:00
Vasilis Sarantidis
82b47628ae Translated using Weblate (Greek)
Currently translated at 18.2% (219 of 1200 strings)

Co-authored-by: Vasilis Sarantidis <bilsarantidis@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/el/
Translation: Minetest/ContentDB
2025-02-08 23:00:44 +01:00
Oleg
3bd62f4184 Translated using Weblate (Ukrainian)
Currently translated at 69.3% (832 of 1200 strings)

Co-authored-by: Oleg <bauyrakoleg@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/uk/
Translation: Minetest/ContentDB
2025-02-08 23:00:43 +01:00
ROllerozxa
9a98c2f6c2 Translated using Weblate (Swedish)
Currently translated at 96.6% (1160 of 1200 strings)

Translated using Weblate (Swedish)

Currently translated at 91.7% (1101 of 1200 strings)

Co-authored-by: ROllerozxa <rollerozxa@voxelmanip.se>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/sv/
Translation: Minetest/ContentDB
2025-02-08 23:00:43 +01:00
Tancrède
73c1706e6a Translated using Weblate (French)
Currently translated at 96.2% (1155 of 1200 strings)

Co-authored-by: Tancrède <tancrede.meulien@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/fr/
Translation: Minetest/ContentDB
2025-02-08 23:00:43 +01:00
Just Playing
649ee8bcd6 Translated using Weblate (Indonesian)
Currently translated at 100.0% (1200 of 1200 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (1200 of 1200 strings)

Co-authored-by: Just Playing <aryadhisuma@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/id/
Translation: Minetest/ContentDB
2025-02-08 23:00:43 +01:00
Matyáš Pilz
37e8f2dc28 Translated using Weblate (Czech)
Currently translated at 99.5% (1195 of 1200 strings)

Translated using Weblate (Czech)

Currently translated at 95.4% (1145 of 1200 strings)

Translated using Weblate (Czech)

Currently translated at 88.2% (1059 of 1200 strings)

Translated using Weblate (Czech)

Currently translated at 85.6% (1028 of 1200 strings)

Translated using Weblate (Czech)

Currently translated at 85.5% (1026 of 1200 strings)

Translated using Weblate (Czech)

Currently translated at 84.2% (1011 of 1200 strings)

Translated using Weblate (Czech)

Currently translated at 83.0% (996 of 1200 strings)

Translated using Weblate (Czech)

Currently translated at 78.8% (946 of 1200 strings)

Translated using Weblate (Czech)

Currently translated at 78.2% (939 of 1200 strings)

Translated using Weblate (Czech)

Currently translated at 76.6% (920 of 1200 strings)

Translated using Weblate (Czech)

Currently translated at 76.3% (916 of 1200 strings)

Translated using Weblate (Czech)

Currently translated at 70.4% (845 of 1200 strings)

Co-authored-by: Matyáš Pilz <matys.pilz@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/cs/
Translation: Minetest/ContentDB
2025-02-08 23:00:43 +01:00
BlackImpostor
4d9628a156 Translated using Weblate (Russian)
Currently translated at 100.0% (1200 of 1200 strings)

Co-authored-by: BlackImpostor <SkyBuilderOFFICAL@yandex.ru>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/ru/
Translation: Minetest/ContentDB
2025-02-08 23:00:43 +01:00
Wuzzy
0ff9a3838e Translated using Weblate (German)
Currently translated at 100.0% (1200 of 1200 strings)

Translated using Weblate (German)

Currently translated at 100.0% (1200 of 1200 strings)

Translated using Weblate (German)

Currently translated at 100.0% (1200 of 1200 strings)

Co-authored-by: Wuzzy <Wuzzy@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/de/
Translation: Minetest/ContentDB
2025-02-08 23:00:42 +01:00
rubenwardy
24eacb191d Fix crash in package_view_client when long desc is null 2025-01-26 16:14:22 +00:00
rubenwardy
23e9ad6ef5 Fix crash due to faulty engine version wildcard 2025-01-26 16:05:47 +00:00
rubenwardy
c7f26f706d Change wording of reviews hypertext helpfulness 2024-12-24 09:29:33 +00:00
rubenwardy
699eabef80 Fix statistics reporting the 5.10 client as web (not just the import script) 2024-11-27 02:29:08 +00:00
ROllerozxa
73376194e0 Fix statistics reporting the 5.10 client as web (#568) 2024-11-22 19:02:36 +00:00
rubenwardy
aafa56df95 Update reviews hypertext 2024-11-15 18:57:31 +00:00
rubenwardy
978c5d9704 Update translations 2024-11-10 15:53:39 +00:00
chocomint
c332e8f940 Translated using Weblate (Spanish)
Currently translated at 100.0% (1197 of 1197 strings)

Translation: Minetest/ContentDB
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/es/
2024-11-10 15:52:11 +00:00
rubenwardy
f116259f6a Update translations 2024-11-03 14:22:42 +00:00
chocomint
3f9902b001 Translated using Weblate (Spanish)
Currently translated at 94.1% (1127 of 1197 strings)

Co-authored-by: chocomint <silentxe1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/es/
Translation: Minetest/ContentDB
2024-11-03 15:21:48 +01:00
Jakub Z
a627276ab4 Translated using Weblate (Polish)
Currently translated at 84.4% (1011 of 1197 strings)

Co-authored-by: Jakub Z <mrkubax10@onet.pl>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/pl/
Translation: Minetest/ContentDB
2024-11-03 15:21:48 +01:00
Unacceptium
f72a66816a Translated using Weblate (Hungarian)
Currently translated at 37.9% (454 of 1197 strings)

Translated using Weblate (Hungarian)

Currently translated at 37.5% (450 of 1197 strings)

Co-authored-by: Unacceptium <unacceptium@proton.me>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/hu/
Translation: Minetest/ContentDB
2024-11-03 15:21:48 +01:00
Honzapkcz
508f7d7e2b Translated using Weblate (Czech)
Currently translated at 61.6% (738 of 1197 strings)

Translated using Weblate (Czech)

Currently translated at 61.5% (737 of 1197 strings)

Co-authored-by: Honzapkcz <honzapkc@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/cs/
Translation: Minetest/ContentDB
2024-11-03 15:21:48 +01:00
Matyáš Pilz
b86d372bd2 Translated using Weblate (Czech)
Currently translated at 68.7% (823 of 1197 strings)

Translated using Weblate (Czech)

Currently translated at 68.6% (822 of 1197 strings)

Translated using Weblate (Czech)

Currently translated at 61.2% (733 of 1197 strings)

Co-authored-by: Matyáš Pilz <matys.pilz@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/cs/
Translation: Minetest/ContentDB
2024-11-03 15:21:48 +01:00
BlackImpostor
c75fd51626 Translated using Weblate (Russian)
Currently translated at 100.0% (1197 of 1197 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (1197 of 1197 strings)

Co-authored-by: BlackImpostor <SkyBuilderOFFICAL@yandex.ru>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/ru/
Translation: Minetest/ContentDB
2024-11-03 15:21:48 +01:00
Wuzzy
6ad12288c3 Translated using Weblate (German)
Currently translated at 100.0% (1197 of 1197 strings)

Co-authored-by: Wuzzy <Wuzzy@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/de/
Translation: Minetest/ContentDB
2024-11-03 15:21:48 +01:00
Muhammad Rifqi Priyo Susanto
af2543a99e Translated using Weblate (Indonesian)
Currently translated at 100.0% (1197 of 1197 strings)

Co-authored-by: Muhammad Rifqi Priyo Susanto <muhammadrifqipriyosusanto@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/id/
Translation: Minetest/ContentDB
2024-11-03 15:21:48 +01:00
Yaya - Nurul Azeera Hidayah @ Muhammad Nur Hidayat Yasuyoshi
2c61032d15 Translated using Weblate (Malay)
Currently translated at 100.0% (1197 of 1197 strings)

Co-authored-by: Yaya - Nurul Azeera Hidayah @ Muhammad Nur Hidayat Yasuyoshi <translation@mnh48.moe>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/ms/
Translation: Minetest/ContentDB
2024-11-03 15:21:48 +01:00
rubenwardy
a54104aa82 Add forum_url to API, change to forum.luanti.org 2024-11-03 14:21:09 +00:00
rubenwardy
dd2e73b40f Change content.minetest.net to content.luanti.org 2024-10-29 22:53:28 +00:00
rubenwardy
a5ac4f38cf Add unique release name check 2024-10-28 22:50:27 +00:00
rubenwardy
2ff11dec0a Update translations 2024-10-15 21:47:13 +01:00
gallegonovato
8e1547ca3b Translated using Weblate (Spanish)
Currently translated at 93.2% (1114 of 1194 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/es/
Translation: Minetest/ContentDB
2024-10-15 22:38:56 +02:00
Jorge Rodríguez
757e182d1b Translated using Weblate (Spanish)
Currently translated at 93.2% (1113 of 1194 strings)

Co-authored-by: Jorge Rodríguez <mr.jrodriguez05@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/es/
Translation: Minetest/ContentDB
2024-10-15 22:38:56 +02:00
gallegonovato
5562ca6039 Translated using Weblate (Spanish)
Currently translated at 93.2% (1113 of 1194 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/es/
Translation: Minetest/ContentDB
2024-10-15 22:38:56 +02:00
Jorge Rodríguez
74cf577245 Translated using Weblate (Spanish)
Currently translated at 86.5% (1034 of 1194 strings)

Co-authored-by: Jorge Rodríguez <mr.jrodriguez05@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/es/
Translation: Minetest/ContentDB
2024-10-15 22:38:56 +02:00
Matyáš Pilz
79387309d8 Translated using Weblate (Czech)
Currently translated at 56.3% (673 of 1194 strings)

Translated using Weblate (Czech)

Currently translated at 54.2% (648 of 1194 strings)

Translated using Weblate (Czech)

Currently translated at 48.0% (574 of 1194 strings)

Co-authored-by: Matyáš Pilz <matys.pilz@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/cs/
Translation: Minetest/ContentDB
2024-10-15 22:38:55 +02:00
Pexauteau Santander
e4b81feb5c Translated using Weblate (Slovak)
Currently translated at 83.2% (994 of 1194 strings)

Translated using Weblate (Slovak)

Currently translated at 77.4% (925 of 1194 strings)

Co-authored-by: Pexauteau Santander <pexauteau@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/sk/
Translation: Minetest/ContentDB
2024-10-15 22:38:55 +02:00
3raven
58ac57e098 Translated using Weblate (French)
Currently translated at 96.6% (1154 of 1194 strings)

Co-authored-by: 3raven <elise_declerck@laposte.net>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/fr/
Translation: Minetest/ContentDB
2024-10-15 22:38:55 +02:00
ROllerozxa
abc2941756 Minetest -> Luanti (#564) 2024-10-15 21:36:50 +01:00
rubenwardy
1432384b63 Review hypertext: Fix gap before comments link 2024-10-08 13:54:30 +01:00
rubenwardy
52df207088 Review hypertext: Fix links in reviews not working 2024-10-08 13:52:08 +01:00
rubenwardy
7f834dbf8c Review hypertext: Fix incorrect icons 2024-10-08 13:43:19 +01:00
rubenwardy
9131b29b48 Review hypertext: Add no reviews message 2024-10-08 13:40:58 +01:00
rubenwardy
f621cd13d2 Review hypertext: Use placeholder element for icons 2024-10-08 13:24:20 +01:00
rubenwardy
69904dbe81 API: Sort tags and ContentWarnings by name 2024-10-06 21:03:37 +01:00
rubenwardy
d56430c0f0 Allow Discord webhook URLs to be an array 2024-10-06 21:03:13 +01:00
rubenwardy
f69bc8fc1e Fix ungraceful error on non-unique OAuthClient title 2024-09-29 13:35:49 +01:00
rubenwardy
5a173ee18b Fix another unchecked watcher append 2024-09-28 19:13:10 +01:00
rubenwardy
6429b2e26d Fix crash on mention on new thread 2024-09-28 19:07:32 +01:00
wsor4035
93f36adfea Fix missing zipgrep on Alpine (#562) 2024-09-28 17:41:01 +01:00
Francesco Rossi
25547c9f38 Translated using Weblate (Italian)
Currently translated at 100.0% (1194 of 1194 strings)

Co-authored-by: Francesco Rossi <zhu.gamedev@tutanota.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/it/
Translation: Minetest/ContentDB
2024-09-21 14:33:57 +02:00
Francesco Rossi
6425149d20 Translated using Weblate (Italian)
Currently translated at 76.8% (917 of 1194 strings)

Co-authored-by: Francesco Rossi <zhu.gamedev@tutanota.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/it/
Translation: Minetest/ContentDB
2024-09-20 10:24:28 +02:00
Giov4
4738e11ed0 Translated using Weblate (Italian)
Currently translated at 74.4% (889 of 1194 strings)

Co-authored-by: Giov4 <brancacciogiovanni1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/it/
Translation: Minetest/ContentDB
2024-09-20 10:24:28 +02:00
Muhammad Rifqi Priyo Susanto
ae67f6ce79 Translated using Weblate (Indonesian)
Currently translated at 100.0% (1194 of 1194 strings)

Co-authored-by: Muhammad Rifqi Priyo Susanto <muhammadrifqipriyosusanto@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/id/
Translation: Minetest/ContentDB
2024-09-20 10:24:28 +02:00
BlackImpostor
c23f004d35 Translated using Weblate (Russian)
Currently translated at 100.0% (1194 of 1194 strings)

Co-authored-by: BlackImpostor <SkyBuilderOFFICAL@yandex.ru>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/ru/
Translation: Minetest/ContentDB
2024-09-20 10:24:28 +02:00
Wuzzy
8effec2cbb Translated using Weblate (German)
Currently translated at 100.0% (1194 of 1194 strings)

Co-authored-by: Wuzzy <Wuzzy@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/de/
Translation: Minetest/ContentDB
2024-09-20 10:24:28 +02:00
gallegonovato
5afc429c25 Translated using Weblate (Spanish)
Currently translated at 86.0% (1027 of 1194 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/es/
Translation: Minetest/ContentDB
2024-09-20 10:24:28 +02:00
John Donne
d5552ad517 Translated using Weblate (French)
Currently translated at 91.6% (1094 of 1194 strings)

Translated using Weblate (French)

Currently translated at 91.0% (1087 of 1194 strings)

Co-authored-by: John Donne <akheron@zaclys.net>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/fr/
Translation: Minetest/ContentDB
2024-09-20 10:24:28 +02:00
rubenwardy
65a14ffdf1 Return JSON for collection based on Accept header
This will be used in the future to install collections inside Minetest
2024-09-15 14:35:23 +01:00
rubenwardy
837d0b5bc1 Link Checker: Allow 403 status codes
Cloudflare likes to break the Internet, so we'll have to ignore
403 errors from sites in the link checker.
2024-09-05 19:19:16 +01:00
rubenwardy
5b1417f432 Remove game support dependency cycle error message 2024-08-31 13:54:29 +01:00
rubenwardy
53a004c41c Remove unused experimental /api/welcome/v1/ API 2024-08-26 12:01:51 +01:00
rubenwardy
ac34939c99 Remove normalization of trailing line endings 2024-08-26 11:56:30 +01:00
rubenwardy
9aa8886309 Fix edit audit log entries being created for no changes 2024-08-26 11:53:13 +01:00
rubenwardy
1166cca357 Update translations 2024-08-26 00:50:05 +01:00
Čarvanavoki
395d3dd16b Added translation using Weblate (Belarusian)
Co-authored-by: Čarvanavoki <dmega5941@gmail.com>
2024-08-26 01:29:32 +02:00
José Douglas
009dfd07de Translated using Weblate (Portuguese)
Currently translated at 51.2% (610 of 1191 strings)

Co-authored-by: José Douglas <josedouglas20002014@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/pt/
Translation: Minetest/ContentDB
2024-08-26 01:29:32 +02:00
ninjum
ff07ff5b7f Translated using Weblate (Galician)
Currently translated at 5.2% (62 of 1191 strings)

Co-authored-by: ninjum <ninhum@gmx.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/gl/
Translation: Minetest/ContentDB
2024-08-26 01:29:32 +02:00
Just Playing
2b32cfe6fa Translated using Weblate (Indonesian)
Currently translated at 100.0% (1191 of 1191 strings)

Co-authored-by: Just Playing <aryadhisuma@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/id/
Translation: Minetest/ContentDB
2024-08-26 01:29:32 +02:00
hugoalh
b31e9e71b6 Translated using Weblate (Chinese (Traditional))
Currently translated at 36.7% (438 of 1191 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 34.8% (415 of 1191 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 32.1% (383 of 1191 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 28.2% (337 of 1191 strings)

Co-authored-by: hugoalh <hugoalh@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/zh_Hant/
Translation: Minetest/ContentDB
2024-08-26 01:29:32 +02:00
Muhammad Rifqi Priyo Susanto
94bf1973a0 Translated using Weblate (Indonesian)
Currently translated at 100.0% (1191 of 1191 strings)

Co-authored-by: Muhammad Rifqi Priyo Susanto <muhammadrifqipriyosusanto@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/id/
Translation: Minetest/ContentDB
2024-08-26 01:29:32 +02:00
John Donne
357dfe76e8 Translated using Weblate (French)
Currently translated at 90.4% (1077 of 1191 strings)

Translated using Weblate (French)

Currently translated at 89.0% (1060 of 1191 strings)

Co-authored-by: John Donne <akheron@zaclys.net>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/fr/
Translation: Minetest/ContentDB
2024-08-26 01:29:32 +02:00
BlackImpostor
5da955b3a5 Translated using Weblate (Russian)
Currently translated at 100.0% (1191 of 1191 strings)

Co-authored-by: BlackImpostor <SkyBuilderOFFICAL@yandex.ru>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/ru/
Translation: Minetest/ContentDB
2024-08-26 01:29:32 +02:00
Athozus
7584a867eb Translated using Weblate (French)
Currently translated at 88.5% (1055 of 1191 strings)

Translated using Weblate (French)

Currently translated at 82.2% (980 of 1191 strings)

Translated using Weblate (French)

Currently translated at 82.1% (979 of 1191 strings)

Co-authored-by: Athozus <athozus@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/fr/
Translation: Minetest/ContentDB
2024-08-26 01:29:32 +02:00
rubenwardy
9c77212f4a Filter for supported languages before parsing .tr files 2024-08-26 00:20:49 +01:00
rubenwardy
2b62224a5b Redirect to reports after attempting to recreate package 2024-08-26 00:17:36 +01:00
rubenwardy
bb561104f8 Add message to removal page about editing packages 2024-08-26 00:17:14 +01:00
rubenwardy
bdd9ab6a29 Add protonmail unsupported message 2024-08-16 22:12:25 +01:00
rubenwardy
d450d6bae3 Fix CI 2024-08-10 13:53:56 +01:00
rubenwardy
02cc464098 Switch to Alpine-based docker images 2024-08-10 13:52:02 +01:00
rubenwardy
563345eddd Hide privacy policy updated message 2024-08-10 13:50:32 +01:00
rubenwardy
e5e3230a16 Fix ungraceful error on BadZipFile 2024-08-05 12:11:44 +01:00
y5nw
ed4d4c67d9 Translated using Weblate (Chinese (Simplified))
Currently translated at 79.2% (944 of 1191 strings)

Co-authored-by: y5nw <y5nw@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/zh_Hans/
Translation: Minetest/ContentDB
2024-08-01 01:32:10 +02:00
Giov4
42df276e73 Translated using Weblate (Italian)
Currently translated at 74.5% (888 of 1191 strings)

Co-authored-by: Giov4 <brancacciogiovanni1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/it/
Translation: Minetest/ContentDB
2024-08-01 01:32:10 +02:00
BlackImpostor
bd17080f2a Translated using Weblate (Russian)
Currently translated at 100.0% (1191 of 1191 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (1191 of 1191 strings)

Co-authored-by: BlackImpostor <SkyBuilderOFFICAL@yandex.ru>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/ru/
Translation: Minetest/ContentDB
2024-08-01 01:32:09 +02:00
Wuzzy
2fb7ddaaee Translated using Weblate (German)
Currently translated at 100.0% (1191 of 1191 strings)

Co-authored-by: Wuzzy <Wuzzy@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/de/
Translation: Minetest/ContentDB
2024-08-01 01:32:09 +02:00
rubenwardy
ef868f776c Add LINK_CHECKER_IGNORED_URLS setting 2024-07-29 23:16:47 +01:00
rubenwardy
06979345c7 Fix thumbnails having incorrect mimetypes
Fixes #555
2024-07-27 22:17:04 +01:00
rubenwardy
e5de270e65 Fix short_description being cut when not needed 2024-07-27 22:10:04 +01:00
rubenwardy
031c3c4684 Fix incorrect message when marking thread as Changes Needed 2024-07-23 22:23:18 +01:00
rubenwardy
a9d31590e8 Fix issue with anonymous reports 2024-07-23 22:19:05 +01:00
rubenwardy
4c4a55872a Update translations 2024-07-23 22:12:38 +01:00
ssdaniel24
9387db5f8d Translated using Weblate (Russian)
Currently translated at 100.0% (1182 of 1182 strings)

Co-authored-by: ssdaniel24 <bo7oaonteg2m@mail.ru>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/ru/
Translation: Minetest/ContentDB
2024-07-23 23:12:09 +02:00
BlackImpostor
6a5c7d44bf Translated using Weblate (Russian)
Currently translated at 100.0% (1182 of 1182 strings)

Co-authored-by: BlackImpostor <SkyBuilderOFFICAL@yandex.ru>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/ru/
Translation: Minetest/ContentDB
2024-07-23 23:12:09 +02:00
y5nw
f1ace7fce8 Translated using Weblate (Chinese (Simplified))
Currently translated at 79.9% (945 of 1182 strings)

Co-authored-by: y5nw <y5nw@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/zh_Hans/
Translation: Minetest/ContentDB
2024-07-23 23:12:09 +02:00
Just Playing
20c946d127 Translated using Weblate (Indonesian)
Currently translated at 100.0% (1182 of 1182 strings)

Co-authored-by: Just Playing <aryadhisuma@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/minetest/contentdb/id/
Translation: Minetest/ContentDB
2024-07-23 23:12:09 +02:00
rubenwardy
bb2a1f3638 Add report button to account deletion section 2024-07-23 22:11:48 +01:00
rubenwardy
e603f29b47 Translations: Add specific help for games 2024-07-18 08:01:46 +01:00
rubenwardy
9c2ecd1e22 Show translation template if there aren't any content translations 2024-07-18 07:25:32 +01:00
rubenwardy
80c3416ca7 Fix issue with check all zip files 2024-07-09 00:14:04 +01:00
136 changed files with 60175 additions and 44050 deletions

2
.github/SECURITY.md vendored
View File

@@ -2,7 +2,7 @@
## Supported Versions
We only support the latest production version, deployed to <https://content.minetest.net>.
We only support the latest production version, deployed to <https://content.luanti.org>.
This is usually the latest `master` commit.
## Reporting a Vulnerability

View File

@@ -6,6 +6,8 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Install docker-compose
run: sudo apt-get install -y docker-compose
- uses: actions/checkout@v4
- name: Copy config
run: cp utils/ci/* .

View File

@@ -1,16 +1,20 @@
FROM python:3.10.11
FROM python:3.10.11-alpine
RUN groupadd -g 5123 cdb && \
useradd -r -u 5123 -g cdb cdb
RUN addgroup --gid 5123 cdb && \
adduser --uid 5123 -S cdb -G cdb
WORKDIR /home/cdb
RUN \
apk add --no-cache postgresql-libs git bash unzip && \
apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev g++
RUN mkdir /var/cdb
RUN chown -R cdb:cdb /var/cdb
COPY requirements.lock.txt requirements.lock.txt
RUN pip install -r requirements.lock.txt
RUN pip install gunicorn
RUN pip install -r requirements.lock.txt && \
pip install gunicorn
COPY utils utils
COPY config.cfg config.cfg

View File

@@ -80,6 +80,7 @@ app.config["WTF_CSRF_TIME_LIMIT"] = None
app.config["BABEL_TRANSLATION_DIRECTORIES"] = "../translations"
app.config["LANGUAGES"] = {
"en": "English",
"cs": "čeština",
"de": "Deutsch",
"es": "Español",
"fr": "Français",
@@ -90,6 +91,7 @@ app.config["LANGUAGES"] = {
"ru": "русский язык",
"sk": "Slovenčina",
"sv": "Svenska",
"ta": "தமிழ்",
"tr": "Türkçe",
"uk": "Українська",
"vi": "tiếng Việt",

View File

@@ -1,248 +1,252 @@
# THIS FILE IS AUTOGENERATED: utils/extract_translations.py
from flask_babel import gettext
from flask_babel import pgettext
# NOTE: tags: title for server_tools
pgettext("tags", "Server Moderation and Tools")
# NOTE: tags: description for server_tools
pgettext("tags", "Helps with server maintenance and moderation")
# NOTE: tags: title for pvp
pgettext("tags", "Player vs Player (PvP)")
# NOTE: tags: description for pvp
pgettext("tags", "Designed to be played competitively against other players")
# NOTE: tags: title for player_effects
pgettext("tags", "Player Effects / Power Ups")
# NOTE: tags: description for player_effects
pgettext("tags", "For content that changes player effects, including physics, for example: speed, jump height or gravity.")
# NOTE: tags: title for jam_game_2023
pgettext("tags", "Jam / Game 2023")
# NOTE: tags: description for jam_game_2023
pgettext("tags", "Entries to the 2023 Minetest Game Jam ")
# NOTE: tags: title for mapgen
pgettext("tags", "Mapgen / Biomes / Decoration")
# NOTE: tags: description for mapgen
pgettext("tags", "New mapgen or changes mapgen")
# NOTE: tags: title for world_tools
pgettext("tags", "World Maintenance and Tools")
# NOTE: tags: description for world_tools
pgettext("tags", "Tools to manage the world")
# NOTE: tags: title for inventory
pgettext("tags", "Inventory")
# NOTE: tags: description for inventory
pgettext("tags", "Changes the inventory GUI")
# NOTE: tags: title for developer_tools
pgettext("tags", "Developer Tools")
# NOTE: tags: description for developer_tools
pgettext("tags", "Tools for game and mod developers")
# NOTE: tags: title for april_fools
pgettext("tags", "Joke")
# NOTE: tags: description for april_fools
pgettext("tags", "For humorous content, meant as a novelty or joke, not to be taken seriously, and that is not meant to be used seriously or long-term.")
# NOTE: tags: title for singleplayer
pgettext("tags", "Singleplayer-focused")
# NOTE: tags: description for singleplayer
pgettext("tags", "Content that can be played alone")
# NOTE: tags: title for crafting
pgettext("tags", "Crafting")
# NOTE: tags: description for crafting
pgettext("tags", "Big changes to crafting gameplay")
# NOTE: tags: title for adventure__rpg
pgettext("tags", "Adventure / RPG")
# NOTE: tags: title for shooter
pgettext("tags", "Shooter")
# NOTE: tags: description for shooter
pgettext("tags", "First person shooters (FPS) and more")
# NOTE: tags: title for sound_music
pgettext("tags", "Sounds / Music")
# NOTE: tags: description for sound_music
pgettext("tags", "Focuses on or adds new sounds or musical things")
# NOTE: tags: title for tools
pgettext("tags", "Tools / Weapons / Armor")
# NOTE: tags: description for tools
pgettext("tags", "Adds or changes tools, weapons, and armor")
# NOTE: tags: title for plants_and_farming
pgettext("tags", "Plants and Farming")
# NOTE: tags: description for plants_and_farming
pgettext("tags", "Adds new plants or other farmable resources.")
# NOTE: tags: title for simulation
pgettext("tags", "Sims")
# NOTE: tags: description for simulation
pgettext("tags", "Mods and games that aim to simulate real life activity. Similar to SimCity/The Sims/OpenTTD/etc.")
# NOTE: tags: title for chat
pgettext("tags", "Chat / Commands")
# NOTE: tags: description for chat
pgettext("tags", "Focus on player chat/communication or console interaction.")
# NOTE: tags: title for multiplayer
pgettext("tags", "Multiplayer-focused")
# NOTE: tags: description for multiplayer
pgettext("tags", "Can/should only be used in multiplayer")
# NOTE: tags: title for complex_installation
pgettext("tags", "Complex installation")
# NOTE: tags: description for complex_installation
pgettext("tags", "Requires futher installation steps, such as installing LuaRocks or editing the trusted mod setting")
# NOTE: tags: title for magic
pgettext("tags", "Magic / Enchanting")
# NOTE: tags: title for building_mechanics
pgettext("tags", "Building Mechanics and Tools")
# NOTE: tags: description for building_mechanics
pgettext("tags", "Adds game mechanics or tools that change how players build.")
# NOTE: tags: title for oneofakind__original
pgettext("tags", "One-of-a-kind / Original")
# NOTE: tags: description for oneofakind__original
pgettext("tags", "For games and such that are of their own kind, distinct and original in nature to others of the same category.")
# NOTE: tags: title for building
pgettext("tags", "Building")
# NOTE: tags: description for building
pgettext("tags", "Focuses on building, such as adding new materials or nodes")
# NOTE: tags: title for decorative
pgettext("tags", "Decorative")
# NOTE: tags: description for decorative
pgettext("tags", "Adds nodes with no other purpose than for use in building")
# NOTE: tags: title for puzzle
pgettext("tags", "Puzzle")
# NOTE: tags: description for puzzle
pgettext("tags", "Focus on puzzle solving instead of combat")
# NOTE: tags: title for environment
pgettext("tags", "Environment / Weather")
# NOTE: tags: description for environment
pgettext("tags", "Improves the world, adding weather, ambient sounds, or other environment mechanics")
# NOTE: tags: title for less_than_px
pgettext("tags", "<16px")
# NOTE: tags: description for less_than_px
pgettext("tags", "Less than 16px")
# NOTE: tags: title for mobs
pgettext("tags", "Mobs / Animals / NPCs")
# NOTE: tags: description for mobs
pgettext("tags", "Adds mobs, animals, and non-player characters")
# NOTE: tags: title for custom_mapgen
pgettext("tags", "Custom mapgen")
# NOTE: tags: description for custom_mapgen
pgettext("tags", "Contains a completely custom mapgen implemented in Lua, usually requires worlds to be set to the 'singlenode' mapgen.")
# NOTE: tags: title for survival
pgettext("tags", "Survival")
# NOTE: tags: description for survival
pgettext("tags", "Written specifically for survival gameplay with a focus on game-balance, difficulty level, or resources available through crafting, mining, ...")
# NOTE: tags: title for education
pgettext("tags", "Education")
# NOTE: tags: description for education
pgettext("tags", "Either has educational value, or is a tool to help teachers ")
# NOTE: tags: title for storage
pgettext("tags", "Storage")
# NOTE: tags: description for storage
pgettext("tags", "Adds or improves item storage mechanics")
# NOTE: tags: title for library
pgettext("tags", "API / Library")
# NOTE: tags: description for library
pgettext("tags", "Primarily adds an API for other mods to use")
# NOTE: tags: title for hud
pgettext("tags", "HUD")
# NOTE: tags: description for hud
pgettext("tags", "For mods that grant the player extra information in the HUD")
# NOTE: tags: title for mini-game
pgettext("tags", "Mini-game")
# NOTE: tags: description for mini-game
pgettext("tags", "Adds a mini-game to be played within Minetest")
# NOTE: tags: title for pve
pgettext("tags", "Player vs Environment (PvE)")
# NOTE: tags: description for pve
pgettext("tags", "For content designed for one or more players that focus on combat against the world, mobs, or NPCs.")
# NOTE: tags: title for sports
pgettext("tags", "Sports")
# NOTE: tags: title for 16px
pgettext("tags", "16px")
# NOTE: tags: description for 16px
pgettext("tags", "For 16px texture packs")
# NOTE: tags: title for 64px
pgettext("tags", "64px")
# NOTE: tags: description for 64px
pgettext("tags", "For 64px texture packs")
# NOTE: tags: title for transport
pgettext("tags", "Transport")
# NOTE: tags: description for transport
pgettext("tags", "Adds or changes transportation methods. Includes teleportation, vehicles, ridable mobs, transport infrastructure and thematic content")
# NOTE: tags: title for seasonal
pgettext("tags", "Seasonal")
# NOTE: tags: description for seasonal
pgettext("tags", "For content generally themed around a certain season or holiday")
# NOTE: tags: title for food
pgettext("tags", "Food / Drinks")
# NOTE: tags: title for commerce
pgettext("tags", "Commerce / Economy")
# NOTE: tags: description for commerce
pgettext("tags", "Related to economies, money, and trading")
# NOTE: tags: title for gui
pgettext("tags", "GUI")
# NOTE: tags: description for gui
pgettext("tags", "For content whose main utility or features are provided within a GUI, on-screen menu, or similar")
# NOTE: tags: title for jam_combat_mod
pgettext("tags", "Jam / Combat 2020")
# NOTE: tags: description for jam_combat_mod
pgettext("tags", "For mods created for the Discord \"Combat\" modding event in 2020")
# NOTE: tags: title for mtg
pgettext("tags", "Minetest Game improved")
# NOTE: tags: description for mtg
pgettext("tags", "Forks of Minetest Game")
# NOTE: tags: title for jam_weekly_2021
pgettext("tags", "Jam / Weekly Challenges 2021")
# NOTE: tags: description for jam_weekly_2021
pgettext("tags", "For mods created for the Discord \"Weekly Challenges\" modding event in 2021")
# NOTE: tags: title for creative
pgettext("tags", "Creative")
# NOTE: tags: description for creative
pgettext("tags", "Written specifically or exclusively for use in creative mode. Adds content only available through a creative inventory, or provides tools that facilitate ingame creation and doesn't add difficulty or scarcity")
# NOTE: tags: title for technology
pgettext("tags", "Machines / Electronics")
# NOTE: tags: description for technology
pgettext("tags", "Adds machines useful in automation, tubes, or power.")
# NOTE: tags: title for 32px
pgettext("tags", "32px")
# NOTE: tags: description for 32px
pgettext("tags", "For 32px texture packs")
# NOTE: tags: title for strategy_rts
pgettext("tags", "Strategy / RTS")
# NOTE: tags: description for strategy_rts
pgettext("tags", "Games and mods with a heavy strategy component, whether real-time or turn-based")
# NOTE: tags: title for skins
pgettext("tags", "Player customization / Skins")
# NOTE: tags: description for skins
pgettext("tags", "Allows the player to customize their character by changing the texture or adding accessories.")
# NOTE: tags: title for jam_game_2021
pgettext("tags", "Jam / Game 2021")
# NOTE: tags: description for jam_game_2021
pgettext("tags", "Entries to the 2021 Minetest Game Jam")
# NOTE: tags: title for 128px
pgettext("tags", "128px+")
# NOTE: tags: description for 128px
pgettext("tags", "For 128px or higher texture packs")
# NOTE: tags: title for 16px
pgettext("tags", "16px")
# NOTE: tags: description for 16px
pgettext("tags", "For 16px texture packs")
# NOTE: tags: title for 32px
pgettext("tags", "32px")
# NOTE: tags: description for 32px
pgettext("tags", "For 32px texture packs")
# NOTE: tags: title for 64px
pgettext("tags", "64px")
# NOTE: tags: description for 64px
pgettext("tags", "For 64px texture packs")
# NOTE: tags: title for adventure__rpg
pgettext("tags", "Adventure / RPG")
# NOTE: tags: title for april_fools
pgettext("tags", "Joke")
# NOTE: tags: description for april_fools
pgettext("tags", "For humorous content, meant as a novelty or joke, not to be taken seriously, and that is not meant to be used seriously or long-term.")
# NOTE: tags: title for building
pgettext("tags", "Building")
# NOTE: tags: description for building
pgettext("tags", "Focuses on building, such as adding new materials or nodes")
# NOTE: tags: title for building_mechanics
pgettext("tags", "Building Mechanics and Tools")
# NOTE: tags: description for building_mechanics
pgettext("tags", "Adds game mechanics or tools that change how players build.")
# NOTE: tags: title for chat
pgettext("tags", "Chat / Commands")
# NOTE: tags: description for chat
pgettext("tags", "Focus on player chat/communication or console interaction.")
# NOTE: tags: title for commerce
pgettext("tags", "Commerce / Economy")
# NOTE: tags: description for commerce
pgettext("tags", "Related to economies, money, and trading")
# NOTE: tags: title for complex_installation
pgettext("tags", "Complex installation")
# NOTE: tags: description for complex_installation
pgettext("tags", "Requires futher installation steps, such as installing LuaRocks or editing the trusted mod setting")
# NOTE: tags: title for crafting
pgettext("tags", "Crafting")
# NOTE: tags: description for crafting
pgettext("tags", "Big changes to crafting gameplay")
# NOTE: tags: title for creative
pgettext("tags", "Creative")
# NOTE: tags: description for creative
pgettext("tags", "Written specifically or exclusively for use in creative mode. Adds content only available through a creative inventory, or provides tools that facilitate ingame creation and doesn't add difficulty or scarcity")
# NOTE: tags: title for custom_mapgen
pgettext("tags", "Custom mapgen")
# NOTE: tags: description for custom_mapgen
pgettext("tags", "Contains a completely custom mapgen implemented in Lua, usually requires worlds to be set to the 'singlenode' mapgen.")
# NOTE: tags: title for decorative
pgettext("tags", "Decorative")
# NOTE: tags: description for decorative
pgettext("tags", "Adds nodes with no other purpose than for use in building")
# NOTE: tags: title for developer_tools
pgettext("tags", "Developer Tools")
# NOTE: tags: description for developer_tools
pgettext("tags", "Tools for game and mod developers")
# NOTE: tags: title for education
pgettext("tags", "Education")
# NOTE: tags: description for education
pgettext("tags", "Either has educational value, or is a tool to help teachers ")
# NOTE: tags: title for environment
pgettext("tags", "Environment / Weather")
# NOTE: tags: description for environment
pgettext("tags", "Improves the world, adding weather, ambient sounds, or other environment mechanics")
# NOTE: tags: title for food
pgettext("tags", "Food / Drinks")
# NOTE: tags: title for gui
pgettext("tags", "GUI")
# NOTE: tags: description for gui
pgettext("tags", "For content whose main utility or features are provided within a GUI, on-screen menu, or similar")
# NOTE: tags: title for hud
pgettext("tags", "HUD")
# NOTE: tags: description for hud
pgettext("tags", "For mods that grant the player extra information in the HUD")
# NOTE: tags: title for inventory
pgettext("tags", "Inventory")
# NOTE: tags: description for inventory
pgettext("tags", "Changes the inventory GUI")
# NOTE: tags: title for jam_combat_mod
pgettext("tags", "Jam / Combat 2020")
# NOTE: tags: description for jam_combat_mod
pgettext("tags", "For mods created for the Discord \"Combat\" modding event in 2020")
# NOTE: tags: title for jam_game_2021
pgettext("tags", "Jam / Game 2021")
# NOTE: tags: description for jam_game_2021
pgettext("tags", "Entries to the 2021 Minetest Game Jam")
# NOTE: tags: title for jam_game_2022
pgettext("tags", " Jam / Game 2022")
# NOTE: tags: description for jam_game_2022
pgettext("tags", "Entries to the 2022 Minetest Game Jam ")
# NOTE: content_warnings: title for gore
pgettext("content_warnings", "Gore")
# NOTE: content_warnings: description for gore
pgettext("content_warnings", "Blood, etc")
# NOTE: content_warnings: title for gambling
pgettext("content_warnings", "Gambling")
# NOTE: content_warnings: description for gambling
pgettext("content_warnings", "Games of chance, gambling games, etc")
# NOTE: content_warnings: title for violence
pgettext("content_warnings", "Violence")
# NOTE: content_warnings: description for violence
pgettext("content_warnings", "Non-cartoon violence. May be towards fantasy or human-like characters")
# NOTE: content_warnings: title for horror
pgettext("content_warnings", "Fear / Horror")
# NOTE: content_warnings: description for horror
pgettext("content_warnings", "Shocking and scary content. May scare young children")
# NOTE: content_warnings: title for bad_language
pgettext("content_warnings", "Bad Language")
# NOTE: content_warnings: description for bad_language
pgettext("content_warnings", "Contains swearing")
# NOTE: tags: title for jam_game_2023
pgettext("tags", "Jam / Game 2023")
# NOTE: tags: description for jam_game_2023
pgettext("tags", "Entries to the 2023 Minetest Game Jam ")
# NOTE: tags: title for jam_game_2024
pgettext("tags", "Jam / Game 2024")
# NOTE: tags: description for jam_game_2024
pgettext("tags", "Entries to the 2024 Luanti Game Jam")
# NOTE: tags: title for jam_weekly_2021
pgettext("tags", "Jam / Weekly Challenges 2021")
# NOTE: tags: description for jam_weekly_2021
pgettext("tags", "For mods created for the Discord \"Weekly Challenges\" modding event in 2021")
# NOTE: tags: title for less_than_px
pgettext("tags", "<16px")
# NOTE: tags: description for less_than_px
pgettext("tags", "Less than 16px")
# NOTE: tags: title for library
pgettext("tags", "API / Library")
# NOTE: tags: description for library
pgettext("tags", "Primarily adds an API for other mods to use")
# NOTE: tags: title for magic
pgettext("tags", "Magic / Enchanting")
# NOTE: tags: title for mapgen
pgettext("tags", "Mapgen / Biomes / Decoration")
# NOTE: tags: description for mapgen
pgettext("tags", "New mapgen or changes mapgen")
# NOTE: tags: title for mini-game
pgettext("tags", "Mini-game")
# NOTE: tags: description for mini-game
pgettext("tags", "Adds a mini-game to be played within Luanti")
# NOTE: tags: title for mobs
pgettext("tags", "Mobs / Animals / NPCs")
# NOTE: tags: description for mobs
pgettext("tags", "Adds mobs, animals, and non-player characters")
# NOTE: tags: title for mtg
pgettext("tags", "Minetest Game improved")
# NOTE: tags: description for mtg
pgettext("tags", "Forks of Minetest Game")
# NOTE: tags: title for multiplayer
pgettext("tags", "Multiplayer-focused")
# NOTE: tags: description for multiplayer
pgettext("tags", "Can/should only be used in multiplayer")
# NOTE: tags: title for oneofakind__original
pgettext("tags", "One-of-a-kind / Original")
# NOTE: tags: description for oneofakind__original
pgettext("tags", "For games and such that are of their own kind, distinct and original in nature to others of the same category.")
# NOTE: tags: title for plants_and_farming
pgettext("tags", "Plants and Farming")
# NOTE: tags: description for plants_and_farming
pgettext("tags", "Adds new plants or other farmable resources.")
# NOTE: tags: title for player_effects
pgettext("tags", "Player Effects / Power Ups")
# NOTE: tags: description for player_effects
pgettext("tags", "For content that changes player effects, including physics, for example: speed, jump height or gravity.")
# NOTE: tags: title for puzzle
pgettext("tags", "Puzzle")
# NOTE: tags: description for puzzle
pgettext("tags", "Focus on puzzle solving instead of combat")
# NOTE: tags: title for pve
pgettext("tags", "Player vs Environment (PvE)")
# NOTE: tags: description for pve
pgettext("tags", "For content designed for one or more players that focus on combat against the world, mobs, or NPCs.")
# NOTE: tags: title for pvp
pgettext("tags", "Player vs Player (PvP)")
# NOTE: tags: description for pvp
pgettext("tags", "Designed to be played competitively against other players")
# NOTE: tags: title for seasonal
pgettext("tags", "Seasonal")
# NOTE: tags: description for seasonal
pgettext("tags", "For content generally themed around a certain season or holiday")
# NOTE: tags: title for server_tools
pgettext("tags", "Server Moderation and Tools")
# NOTE: tags: description for server_tools
pgettext("tags", "Helps with server maintenance and moderation")
# NOTE: tags: title for shooter
pgettext("tags", "Shooter")
# NOTE: tags: description for shooter
pgettext("tags", "First person shooters (FPS) and more")
# NOTE: tags: title for simulation
pgettext("tags", "Sims")
# NOTE: tags: description for simulation
pgettext("tags", "Mods and games that aim to simulate real life activity. Similar to SimCity/The Sims/OpenTTD/etc.")
# NOTE: tags: title for singleplayer
pgettext("tags", "Singleplayer-focused")
# NOTE: tags: description for singleplayer
pgettext("tags", "Content that can be played alone")
# NOTE: tags: title for skins
pgettext("tags", "Player customization / Skins")
# NOTE: tags: description for skins
pgettext("tags", "Allows the player to customize their character by changing the texture or adding accessories.")
# NOTE: tags: title for sound_music
pgettext("tags", "Sounds / Music")
# NOTE: tags: description for sound_music
pgettext("tags", "Focuses on or adds new sounds or musical things")
# NOTE: tags: title for sports
pgettext("tags", "Sports")
# NOTE: tags: title for storage
pgettext("tags", "Storage")
# NOTE: tags: description for storage
pgettext("tags", "Adds or improves item storage mechanics")
# NOTE: tags: title for strategy_rts
pgettext("tags", "Strategy / RTS")
# NOTE: tags: description for strategy_rts
pgettext("tags", "Games and mods with a heavy strategy component, whether real-time or turn-based")
# NOTE: tags: title for survival
pgettext("tags", "Survival")
# NOTE: tags: description for survival
pgettext("tags", "Written specifically for survival gameplay with a focus on game-balance, difficulty level, or resources available through crafting, mining, ...")
# NOTE: tags: title for technology
pgettext("tags", "Machines / Electronics")
# NOTE: tags: description for technology
pgettext("tags", "Adds machines useful in automation, tubes, or power.")
# NOTE: tags: title for tools
pgettext("tags", "Tools / Weapons / Armor")
# NOTE: tags: description for tools
pgettext("tags", "Adds or changes tools, weapons, and armor")
# NOTE: tags: title for transport
pgettext("tags", "Transport")
# NOTE: tags: description for transport
pgettext("tags", "Adds or changes transportation methods. Includes teleportation, vehicles, ridable mobs, transport infrastructure and thematic content")
# NOTE: tags: title for world_tools
pgettext("tags", "World Maintenance and Tools")
# NOTE: tags: description for world_tools
pgettext("tags", "Tools to manage the world")
# NOTE: content_warnings: title for alcohol_tobacco
pgettext("content_warnings", "Alcohol / Tobacco")
# NOTE: content_warnings: description for alcohol_tobacco
pgettext("content_warnings", "Contains alcohol and/or tobacco")
# NOTE: content_warnings: title for bad_language
pgettext("content_warnings", "Bad Language")
# NOTE: content_warnings: description for bad_language
pgettext("content_warnings", "Contains swearing")
# NOTE: content_warnings: title for drugs
pgettext("content_warnings", "Drugs")
# NOTE: content_warnings: description for drugs
pgettext("content_warnings", "Contains recreational drugs other than alcohol or tobacco")
# NOTE: content_warnings: title for gambling
pgettext("content_warnings", "Gambling")
# NOTE: content_warnings: description for gambling
pgettext("content_warnings", "Games of chance, gambling games, etc")
# NOTE: content_warnings: title for gore
pgettext("content_warnings", "Gore")
# NOTE: content_warnings: description for gore
pgettext("content_warnings", "Blood, etc")
# NOTE: content_warnings: title for horror
pgettext("content_warnings", "Fear / Horror")
# NOTE: content_warnings: description for horror
pgettext("content_warnings", "Shocking and scary content. May scare young children")
# NOTE: content_warnings: title for violence
pgettext("content_warnings", "Violence")
# NOTE: content_warnings: description for violence
pgettext("content_warnings", "Non-cartoon violence. May be towards fantasy or human-like characters")

View File

@@ -30,7 +30,7 @@ from app.logic.graphs import get_package_stats, get_package_stats_for_user, get_
from app.markdown import render_markdown
from app.models import Tag, PackageState, PackageType, Package, db, PackageRelease, Permission, \
MinetestRelease, APIToken, PackageScreenshot, License, ContentWarning, User, PackageReview, Thread, Collection, \
PackageAlias, Language
PackageAlias, Language, PackageDailyStats
from app.querybuilder import QueryBuilder
from app.utils import is_package_page, get_int_or_abort, url_set_query, abs_url, is_yes, get_request_date, cached, \
cors_allowed
@@ -39,6 +39,7 @@ from . import bp
from .auth import is_api_authd
from .support import error, api_create_vcs_release, api_create_zip_release, api_create_screenshot, \
api_order_screenshots, api_edit_package, api_set_cover_image
from ...rediscache import make_view_key, set_temp_key, has_key
@bp.route("/api/packages/")
@@ -99,6 +100,14 @@ def package_view(package):
@is_package_page
@cors_allowed
def package_view_client(package: Package):
ip = request.headers.get("X-Forwarded-For") or request.remote_addr
if ip is not None and (request.headers.get("User-Agent") or "").startswith("Minetest"):
key = make_view_key(ip, package)
if not has_key(key):
set_temp_key(key, "true")
PackageDailyStats.notify_view(package)
db.session.commit()
protocol_version = request.args.get("protocol_version")
engine_version = request.args.get("engine_version")
if protocol_version or engine_version:
@@ -113,9 +122,10 @@ def package_view_client(package: Package):
formspec_version = get_int_or_abort(request.args["formspec_version"])
include_images = is_yes(request.args.get("include_images", "true"))
html = render_markdown(data["long_description"])
page_url = package.get_url("packages.view", absolute=True)
data["long_description"] = html_to_minetest(html, page_url, formspec_version, include_images)
if data["long_description"] is not None:
html = render_markdown(data["long_description"])
data["long_description"] = html_to_minetest(html, page_url, formspec_version, include_images)
data["info_hypertext"] = package_info_as_hypertext(package, formspec_version)
@@ -150,7 +160,7 @@ def package_view_client_reviews(package: Package):
def package_hypertext(package):
formspec_version = get_int_or_abort(request.args["formspec_version"])
include_images = is_yes(request.args.get("include_images", "true"))
html = render_markdown(package.desc)
html = render_markdown(package.desc if package.desc else "")
page_url = package.get_url("packages.view", absolute=True)
return jsonify(html_to_minetest(html, page_url, formspec_version, include_images))
@@ -569,14 +579,14 @@ def package_scores():
@cors_allowed
@cached(60*60)
def tags():
return jsonify([tag.as_dict() for tag in Tag.query.all() ])
return jsonify([tag.as_dict() for tag in Tag.query.order_by(db.asc(Tag.name)).all()])
@bp.route("/api/content_warnings/")
@cors_allowed
@cached(60*60)
def content_warnings():
return jsonify([warning.as_dict() for warning in ContentWarning.query.all() ])
return jsonify([warning.as_dict() for warning in ContentWarning.query.order_by(db.asc(ContentWarning.name)).all() ])
@bp.route("/api/licenses/")
@@ -629,24 +639,6 @@ def homepage():
})
@bp.route("/api/welcome/v1/")
@cors_allowed
def welcome_v1():
featured = Package.query \
.filter(Package.type == PackageType.GAME, Package.state == PackageState.APPROVED,
Package.collections.any(
and_(Collection.name == "featured", Collection.author.has(username="ContentDB")))) \
.order_by(func.random()) \
.limit(5).all()
def map_packages(packages: List[Package]):
return [pkg.as_short_dict(current_app.config["BASE_URL"]) for pkg in packages]
return jsonify({
"featured": map_packages(featured),
})
@bp.route("/api/minetest_versions/")
@cors_allowed
def versions():

View File

@@ -112,9 +112,9 @@ def api_edit_package(token: APIToken, package: Package, data: dict, reason: str
reason += ", token=" + token.name
package = guard(do_edit_package)(token.owner, package, False, False, data, reason)
was_modified = guard(do_edit_package)(token.owner, package, False, False, data, reason)
return jsonify({
"success": True,
"package": package.as_dict(current_app.config["BASE_URL"])
"package": package.as_dict(current_app.config["BASE_URL"]),
"was_modified": was_modified,
})

View File

@@ -17,7 +17,7 @@
import re
import typing
from flask import Blueprint, request, redirect, render_template, flash, abort, url_for
from flask import Blueprint, request, redirect, render_template, flash, abort, url_for, jsonify
from flask_babel import lazy_gettext, gettext
from flask_login import current_user, login_required
from flask_wtf import FlaskForm
@@ -25,7 +25,7 @@ from wtforms import StringField, BooleanField, SubmitField, FieldList, HiddenFie
from wtforms.validators import InputRequired, Length, Optional, Regexp
from app.models import Collection, db, Package, Permission, CollectionPackage, User, UserRank, AuditSeverity
from app.utils import nonempty_or_none, normalize_line_endings
from app.utils import nonempty_or_none, normalize_line_endings, should_return_json
from app.utils.models import is_package_page, add_audit_log, create_session
bp = Blueprint("collections", __name__)
@@ -70,7 +70,10 @@ def view(author, name):
if not collection.check_perm(current_user, Permission.EDIT_COLLECTION):
items = [x for x in items if x.package.check_perm(current_user, Permission.VIEW_PACKAGE)]
return render_template("collections/view.html", collection=collection, items=items)
if should_return_json():
return jsonify([ item.package.as_key_dict() for item in items ])
else:
return render_template("collections/view.html", collection=collection, items=items)
class CollectionForm(FlaskForm):

View File

@@ -29,10 +29,10 @@ def _make_feed(title: str, feed_url: str, items: list):
return {
"version": "https://jsonfeed.org/version/1",
"title": title,
"description": gettext("Welcome to the best place to find Minetest mods, games, and texture packs"),
"home_page_url": "https://content.minetest.net/",
"description": gettext("Welcome to the best place to find Luanti mods, games, and texture packs"),
"home_page_url": "https://content.luanti.org/",
"feed_url": feed_url,
"icon": "https://content.minetest.net/favicon-128.png",
"icon": "https://content.luanti.org/favicon-128.png",
"expired": False,
"items": items,
}

View File

@@ -194,6 +194,10 @@ def create_edit_client(username, id_=None):
if form.validate_on_submit():
if is_new:
if OAuthClient.query.filter(OAuthClient.title.ilike(form.title.data.strip())).count() > 0:
flash(gettext("An OAuth client with that title already exists. Please choose a new title."), "danger")
return render_template("oauth/create_edit.html", user=user, form=form, client=client)
client = OAuthClient()
db.session.add(client)
client.owner = user
@@ -201,6 +205,7 @@ def create_edit_client(username, id_=None):
client.secret = random_string(32)
client.approved = current_user.rank.at_least(UserRank.EDITOR)
form.populate_obj(client)
verb = "Created" if is_new else "Edited"

View File

@@ -74,7 +74,7 @@ class AdvancedSearchForm(FlaskForm):
allow_blank=True, blank_value="",
get_pk=lambda a: a.id, get_label=lambda a: a.title)
hide = SelectMultipleField(lazy_gettext("Hide Tags and Content Warnings"), [Optional()])
engine_version = QuerySelectField(lazy_gettext("Minetest Version"),
engine_version = QuerySelectField(lazy_gettext("Luanti Version"),
query_factory=lambda: MinetestRelease.query.order_by(db.asc(MinetestRelease.id)),
allow_blank=True, blank_value="",
get_pk=lambda a: a.value, get_label=lambda a: a.name)

View File

@@ -266,6 +266,7 @@ def handle_create_edit(package: typing.Optional[Package], form: PackageForm, aut
flash(
gettext("Package already exists, but is removed. Please contact ContentDB staff to restore the package"),
"danger")
return redirect(url_for("report.report", url=package.get_url("packages.view")))
else:
flash(markupsafe.Markup(
f"<a class='btn btn-sm btn-danger float-end' href='{package.get_url('packages.view')}'>View</a>" +
@@ -458,6 +459,7 @@ def move_to_state(package):
@is_package_page
def translation(package):
return render_template("packages/translation.html", package=package,
has_content_translations=any([x.title or x.short_desc for x in package.translations.all()]),
tabs=get_package_tabs(current_user, package), current_tab="translation")
@@ -570,7 +572,7 @@ def edit_maintainers(package):
for user in users:
if not user in package.maintainers:
if thread:
if thread and user not in thread.watchers:
thread.watchers.append(user)
add_notification(user, current_user, NotificationType.MAINTAINER,
"Added you as a maintainer of {}".format(package.title), package.get_url("packages.view"), package)

View File

@@ -13,6 +13,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import os
from flask import render_template, request, redirect, flash, url_for, abort
@@ -31,6 +32,7 @@ from app.rediscache import has_key, set_temp_key, make_download_key
from app.tasks.importtasks import check_update_config
from app.utils import is_user_bot, is_package_page, nonempty_or_none, normalize_line_endings
from . import bp, get_package_tabs
from app.utils.version import is_minetest_v510
@bp.route("/packages/<author>/<name>/releases/", methods=["GET", "POST"])
@@ -59,9 +61,9 @@ class CreatePackageReleaseForm(FlaskForm):
upload_mode = RadioField(lazy_gettext("Method"), choices=[("upload", lazy_gettext("File Upload"))], default="upload")
vcs_label = StringField(lazy_gettext("Git reference (ie: commit hash, branch, or tag)"), default=None)
file_upload = FileField(lazy_gettext("File Upload"))
min_rel = QuerySelectField(lazy_gettext("Minimum Minetest Version"), [InputRequired()],
min_rel = QuerySelectField(lazy_gettext("Minimum Luanti Version"), [InputRequired()],
query_factory=lambda: get_mt_releases(False), get_pk=lambda a: a.id, get_label=lambda a: a.name)
max_rel = QuerySelectField(lazy_gettext("Maximum Minetest Version"), [InputRequired()],
max_rel = QuerySelectField(lazy_gettext("Maximum Luanti Version"), [InputRequired()],
query_factory=lambda: get_mt_releases(True), get_pk=lambda a: a.id, get_label=lambda a: a.name)
submit = SubmitField(lazy_gettext("Save"))
@@ -74,9 +76,9 @@ class EditPackageReleaseForm(FlaskForm):
url = StringField(lazy_gettext("URL"), [Optional()])
task_id = StringField(lazy_gettext("Task ID"), filters = [lambda x: x or None])
approved = BooleanField(lazy_gettext("Is Approved"))
min_rel = QuerySelectField(lazy_gettext("Minimum Minetest Version"), [InputRequired()],
min_rel = QuerySelectField(lazy_gettext("Minimum Luanti Version"), [InputRequired()],
query_factory=lambda: get_mt_releases(False), get_pk=lambda a: a.id, get_label=lambda a: a.name)
max_rel = QuerySelectField(lazy_gettext("Maximum Minetest Version"), [InputRequired()],
max_rel = QuerySelectField(lazy_gettext("Maximum Luanti Version"), [InputRequired()],
query_factory=lambda: get_mt_releases(True), get_pk=lambda a: a.id, get_label=lambda a: a.name)
submit = SubmitField(lazy_gettext("Save"))
@@ -127,9 +129,11 @@ def download_release(package, id):
ip = request.headers.get("X-Forwarded-For") or request.remote_addr
if ip is not None and not is_user_bot():
is_minetest = (request.headers.get("User-Agent") or "").startswith("Minetest")
user_agent = request.headers.get("User-Agent") or ""
is_minetest = user_agent.startswith("Luanti") or user_agent.startswith("Minetest")
is_v510 = is_minetest and is_minetest_v510(request.headers.get("User-Agent"))
reason = request.args.get("reason")
PackageDailyStats.update(package, is_minetest, reason)
PackageDailyStats.notify_download(package, is_minetest, is_v510, reason)
key = make_download_key(ip, release.package)
if not has_key(key):
@@ -214,10 +218,10 @@ def edit_release(package, id):
class BulkReleaseForm(FlaskForm):
set_min = BooleanField(lazy_gettext("Set Min"))
min_rel = QuerySelectField(lazy_gettext("Minimum Minetest Version"), [InputRequired()],
min_rel = QuerySelectField(lazy_gettext("Minimum Luanti Version"), [InputRequired()],
query_factory=lambda: get_mt_releases(False), get_pk=lambda a: a.id, get_label=lambda a: a.name)
set_max = BooleanField(lazy_gettext("Set Max"))
max_rel = QuerySelectField(lazy_gettext("Maximum Minetest Version"), [InputRequired()],
max_rel = QuerySelectField(lazy_gettext("Maximum Luanti Version"), [InputRequired()],
query_factory=lambda: get_mt_releases(True), get_pk=lambda a: a.id, get_label=lambda a: a.name)
only_change_none = BooleanField(lazy_gettext("Only change values previously set as none"))
submit = SubmitField(lazy_gettext("Update"))

View File

@@ -47,6 +47,9 @@ def report():
url = abs_url_samesite(url)
form = ReportForm(formdata=request.form) if current_user.is_authenticated else None
if form and request.method == "GET":
form.message.data = request.args.get("message", "")
if form and form.validate_on_submit():
if current_user.is_authenticated:
user_info = f"{current_user.username}"

View File

@@ -281,7 +281,6 @@ def view(id):
class ThreadForm(FlaskForm):
title = StringField(lazy_gettext("Title"), [InputRequired(), Length(3,100)])
comment = TextAreaField(lazy_gettext("Comment"), [InputRequired(), Length(10, 2000)], filters=[normalize_line_endings])
private = BooleanField(lazy_gettext("Private"))
btn_submit = SubmitField(lazy_gettext("Open Thread"))
@@ -296,14 +295,11 @@ def new():
if package is None:
abort(404)
def_is_private = request.args.get("private") or False
if package is None and not current_user.rank.at_least(UserRank.APPROVER):
abort(404)
is_review_thread = package and not package.approved
allow_private_change = not is_review_thread
if is_review_thread:
def_is_private = True
is_private_thread = is_review_thread
# Check that user can make the thread
if package and not package.check_perm(current_user, Permission.CREATE_THREAD):
@@ -326,7 +322,6 @@ def new():
# Set default values
elif request.method == "GET":
form.private.data = def_is_private
form.title.data = request.args.get("title") or ""
# Validate and submit
@@ -337,7 +332,7 @@ def new():
thread = Thread()
thread.author = current_user
thread.title = form.title.data
thread.private = form.private.data if allow_private_change else def_is_private
thread.private = is_private_thread
thread.package = package
db.session.add(thread)
@@ -367,7 +362,8 @@ def new():
add_notification(mentioned, current_user, NotificationType.NEW_THREAD,
msg, thread.get_view_url(), thread.package)
thread.watchers.append(mentioned)
if mentioned not in thread.watchers:
thread.watchers.append(mentioned)
notif_msg = "New thread '{}'".format(thread.title)
if package is not None:
@@ -384,7 +380,7 @@ def new():
return redirect(thread.get_view_url())
return render_template("threads/new.html", form=form, allow_private_change=allow_private_change, package=package)
return render_template("threads/new.html", form=form, package=package)
@bp.route("/users/<username>/comments/")

View File

@@ -25,7 +25,11 @@ bp = Blueprint("thumbnails", __name__)
ALLOWED_RESOLUTIONS = [(100, 67), (270, 180), (350, 233), (1100, 520)]
ALLOWED_EXTENSIONS = {"png", "webp", "jpg"}
ALLOWED_MIMETYPES = {
"png": "image/png",
"webp": "image/webp",
"jpg": "image/jpeg",
}
def mkdir(path):
@@ -76,10 +80,10 @@ def find_source_file(img):
period = source_filepath.rfind(".")
start = source_filepath[:period]
ext = source_filepath[period + 1:]
if ext not in ALLOWED_EXTENSIONS:
if ext not in ALLOWED_MIMETYPES:
abort(404)
for other_ext in ALLOWED_EXTENSIONS:
for other_ext in ALLOWED_MIMETYPES.keys():
other_path = f"{start}.{other_ext}"
if ext != other_ext and os.path.isfile(other_path):
return other_path
@@ -87,6 +91,15 @@ def find_source_file(img):
abort(404)
def get_mimetype(cache_filepath: str) -> str:
period = cache_filepath.rfind(".")
ext = cache_filepath[period + 1:]
mimetype = ALLOWED_MIMETYPES.get(ext)
if mimetype is None:
abort(404)
return mimetype
@bp.route("/thumbnails/<int:level>/<img>")
def make_thumbnail(img, level):
if level > len(ALLOWED_RESOLUTIONS) or level <= 0:
@@ -104,7 +117,7 @@ def make_thumbnail(img, level):
source_filepath = find_source_file(img)
resize_and_crop(source_filepath, cache_filepath, (w, h))
res = send_file(cache_filepath)
res = send_file(cache_filepath, mimetype=get_mimetype(cache_filepath))
res.headers["Cache-Control"] = "max-age=604800" # 1 week
return res

View File

@@ -104,7 +104,6 @@ class RegisterForm(FlaskForm):
email = StringField(lazy_gettext("Email"), [InputRequired(), Email()])
password = PasswordField(lazy_gettext("Password"), [InputRequired(), Length(12, 100)])
question = StringField(lazy_gettext("What is the result of the above calculation?"), [InputRequired()])
agree = BooleanField(lazy_gettext("I agree"), [DataRequired()])
submit = SubmitField(lazy_gettext("Register"))

View File

@@ -77,7 +77,7 @@ def claim_forums():
# Get signature
try:
profile = get_profile("https://forum.minetest.net", username)
profile = get_profile("https://forum.luanti.org", username)
sig = profile.signature if profile else None
except IOError as e:
if hasattr(e, 'message'):

View File

@@ -28,7 +28,6 @@ from app.blueprints.api.support import error, api_create_vcs_release
from app.logic.users import create_user
from app.models import db, User, APIToken, AuditSeverity
from app.utils import abs_url_for, add_audit_log, login_user_set_active, is_safe_url
from . import bp
from .common import get_packages_for_vcs_and_token
@@ -98,6 +97,7 @@ def github_callback(oauth_token):
flash(gettext("Authorization failed [err=gh-login-failed]"), "danger")
return redirect(url_for("users.login"))
user_by_github.github_username = github_username
add_audit_log(AuditSeverity.USER, user_by_github, "Logged in using GitHub OAuth",
url_for("users.profile", username=user_by_github.username))
db.session.commit()

View File

@@ -25,13 +25,13 @@ poor user experience, especially for first-time users.
ContentDB isn't just about supporting the in-game content downloader; it's common for technical users to find
and review packages using the ContentDB website, but install using Git rather than the in-game installer.
**ContentDB's purpose is to be a well-formatted source of information about mods, games,
and texture packs for Minetest**.
and texture packs for Luanti**.
## How do I learn how to make mods and games for Minetest?
## How do I learn how to make mods and games for Luanti?
You should read
[the official Minetest Modding Book](https://rubenwardy.com/minetest_modding_book/)
for a guide to making mods and games using Minetest.
[the official Luanti Modding Book](https://rubenwardy.com/minetest_modding_book/)
for a guide to making mods and games using Luanti.
<h2 id="donate">How can I support / donate to ContentDB?</h2>
@@ -45,5 +45,5 @@ For more information about the cost of ContentDB and what rubenwardy does, see h
## Sponsorships
Minetest and ContentDB are sponsored by <a href="https://sentry.io/" rel="nofollow">sentry.io</a>.
Luanti and ContentDB are sponsored by <a href="https://sentry.io/" rel="nofollow">sentry.io</a>.
This provides us with improved error logging and performance insights.

View File

@@ -4,7 +4,7 @@ toc: False
## Rules
* [Rules](/rules/)
* [Terms of Service](/terms/)
* [Package Inclusion Policy and Guidance](/policy_and_guidance/)
## General Help

View File

@@ -3,7 +3,7 @@ title: API
## Resources
* [How the Minetest client uses the API](https://github.com/minetest/contentdb/blob/master/docs/minetest_client.md)
* [How the Luanti client uses the API](https://github.com/minetest/contentdb/blob/master/docs/minetest_client.md)
## Responses and Error Handling
@@ -54,7 +54,7 @@ The response will be a dictionary with the following keys:
Not all endpoints require authentication, but it is done using Bearer tokens:
```bash
curl https://content.minetest.net/api/whoami/ \
curl https://content.luanti.org/api/whoami/ \
-H "Authorization: Bearer YOURTOKEN"
```
@@ -67,8 +67,8 @@ Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/).
* DELETE `/api/delete-token/`: Deletes the currently used token.
```bash
# Logout
curl -X DELETE https://content.minetest.net/api/delete-token/ \
# Logout
curl -X DELETE https://content.luanti.org/api/delete-token/ \
-H "Authorization: Bearer YOURTOKEN"
```
@@ -78,9 +78,12 @@ curl -X DELETE https://content.minetest.net/api/delete-token/ \
* GET `/api/packages/` (List)
* See [Package Queries](#package-queries)
* GET `/api/packages/<username>/<name>/` (Read)
* Redirects a JSON object with the keys documented by the PUT endpoint, below.
* Plus:
* `forum_url`: String or null.
* PUT `/api/packages/<author>/<name>/` (Update)
* Requires authentication.
* JSON dictionary with any of these keys (all are optional, null to delete Nullables):
* JSON object with any of these keys (all are optional, null to delete Nullables):
* `type`: One of `GAME`, `MOD`, `TXP`.
* `title`: Human-readable title.
* `name`: Technical name (needs permission if already approved).
@@ -99,9 +102,13 @@ curl -X DELETE https://content.minetest.net/api/delete-token/ \
* `video_url`: URL to a video.
* `donate_url`: URL to a donation page.
* `translation_url`: URL to send users interested in translating your package.
* `game_support`: Array of game support information objects. Not currently documented, as subject to change.
* `game_support`: Array of game support information objects. Not currently documented,
* Returns a JSON object with:
* `success`
* `package`: updated package
* `was_modified`: bool, whether anything changed
* GET `/api/packages/<username>/<name>/for-client/`
* Similar to the read endpoint, but optimised for the Minetest client
* Similar to the read endpoint, but optimised for the Luanti client
* `long_description` is given as a hypertext object, see `/hypertext/` below.
* `info_hypertext` is the info sidebar as a hypertext object.
* Query arguments
@@ -109,17 +116,31 @@ curl -X DELETE https://content.minetest.net/api/delete-token/ \
* `include_images`: Optional, defaults to true. If true, images use `<img>`. If false, they're linked.
* `protocol_version`: Optional, used to get the correct release.
* `engine_version`: Optional, used to get the correct release. Ex: `5.3.0`.
* GET `/api/packages/<username>/<name>/for-client/reviews/`
* Returns hypertext representing the package's reviews
* Query arguments
* `formspec_version`: Required. See /hypertext/ below.
* Returns JSON dictionary with following keys:
* `head`: markup for suggested styling and custom tags, prepend to the body before displaying.
* `body`: markup for long description.
* `links`: dictionary of anchor name to link URL.
* `images`: dictionary of img name to image URL.
* `image_tooltips`: dictionary of img name to tooltip text.
* The hypertext body contains some placeholders that should be replaced client-side:
* `<thumbsup>` with a thumbs up icon.
* `<neutral>` with a thumbs up icon.
* `<thumbsdown>` with a thumbs up icon.
* GET `/api/packages/<author>/<name>/hypertext/`
* Converts the long description to [Minetest Markup Language](https://github.com/minetest/minetest/blob/master/doc/lua_api.md#markup-language)
* Converts the long description to [Luanti Markup Language](https://github.com/minetest/minetest/blob/master/doc/lua_api.md#markup-language)
to be used in a `hypertext` formspec element.
* Query arguments:
* `formspec_version`: Required, maximum supported formspec version.
* `include_images`: Optional, defaults to true. If true, images use `<img>`. If false, they're linked.
* Returns JSON dictionary with following key:
* Returns JSON dictionary with following keys:
* `head`: markup for suggested styling and custom tags, prepend to the body before displaying.
* `body`: markup for long description.
* `links`: dictionary of anchor name to link URL.
* `images`: dictionary of img name to image URL
* `images`: dictionary of img name to image URL.
* `image_tooltips`: dictionary of img name to tooltip text.
* GET `/api/packages/<username>/<name>/dependencies/`
* Returns dependencies, with suggested candidates
@@ -153,6 +174,7 @@ curl -X DELETE https://content.minetest.net/api/delete-token/ \
* `reason_new`: list of integers per day.
* `reason_dependency`: list of integers per day.
* `reason_update`: list of integers per day.
* `views_minetest`: list of integers per day.
* GET `/api/package_stats/`
* Returns last 30 days of daily stats for _all_ packages.
* An object with the following keys:
@@ -163,20 +185,20 @@ curl -X DELETE https://content.minetest.net/api/delete-token/ \
You can download a package by building one of the two URLs:
```
https://content.minetest.net/packages/${author}/${name}/download/`
https://content.minetest.net/packages/${author}/${name}/releases/${release}/download/`
https://content.luanti.org/packages/${author}/${name}/download/`
https://content.luanti.org/packages/${author}/${name}/releases/${release}/download/`
```
Examples:
```bash
# Edit package
curl -X PUT https://content.minetest.net/api/packages/username/name/ \
curl -X PUT https://content.luanti.org/api/packages/username/name/ \
-H "Authorization: Bearer YOURTOKEN" -H "Content-Type: application/json" \
-d '{ "title": "Foo bar", "tags": ["pvp", "survival"], "license": "MIT" }'
# Remove website URL
curl -X PUT https://content.minetest.net/api/packages/username/name/ \
curl -X PUT https://content.luanti.org/api/packages/username/name/ \
-H "Authorization: Bearer YOURTOKEN" -H "Content-Type: application/json" \
-d '{ "website": null }'
```
@@ -198,8 +220,8 @@ Filter query parameters:
* `license`: Filter by [license name](#licenses). Multiple licenses are OR-ed together, ie: `&license=MIT&license=LGPL-2.1-only`
* `game`: Filter by [Game Support](/help/game_support/), ex: `Warr1024/nodecore`. (experimental, doesn't show items that support every game currently).
* `lang`: Filter by translation support, eg: `en`/`de`/`ja`/`zh_TW`.
* `protocol_version`: Only show packages supported by this Minetest protocol version.
* `engine_version`: Only show packages supported by this Minetest engine version, eg: `5.3.0`.
* `protocol_version`: Only show packages supported by this Luanti protocol version.
* `engine_version`: Only show packages supported by this Luanti engine version, eg: `5.3.0`.
Sorting query parameters:
@@ -212,7 +234,7 @@ Format query parameters:
* `limit`: Return at most `limit` packages.
* `fmt`: How the response is formatted.
* `keys`: author/name only.
* `short`: stuff needed for the Minetest client.
* `short`: stuff needed for the Luanti client.
* `vcs`: `short` but with `repo`.
@@ -232,8 +254,8 @@ Format query parameters:
* `url`: download URL
* `commit`: commit hash or null
* `downloads`: number of downloads
* `min_minetest_version`: dict or null, minimum supported minetest version (inclusive).
* `max_minetest_version`: dict or null, minimum supported minetest version (inclusive).
* `min_minetest_version`: dict or null, minimum supported Luanti version (inclusive).
* `max_minetest_version`: dict or null, minimum supported Luanti version (inclusive).
* `size`: size of zip file, in bytes.
* `package`
* `author`: author username
@@ -242,8 +264,8 @@ Format query parameters:
* GET `/api/updates/` (Look-up table)
* Returns a look-up table from package key (`author/name`) to latest release id
* Query arguments
* `protocol_version`: Only show packages supported by this Minetest protocol version.
* `engine_version`: Only show packages supported by this Minetest engine version, eg: `5.3.0`.
* `protocol_version`: Only show packages supported by this Luanti protocol version.
* `engine_version`: Only show packages supported by this Luanti engine version, eg: `5.3.0`.
* GET `/api/packages/<username>/<name>/releases/` (List)
* Returns array of release dictionaries, see above, but without package info.
* GET `/api/packages/<username>/<name>/releases/<id>/` (Read)
@@ -258,7 +280,7 @@ Format query parameters:
* For zip upload release creation:
* `file`: multipart file to upload, like `<input type="file" name="file">`.
* `commit`: (Optional) Source Git commit hash, for informational purposes.
* You can set min and max Minetest Versions [using the content's .conf file](/help/package_config/).
* You can set min and max Luanti Versions [using the content's .conf file](/help/package_config/).
* DELETE `/api/packages/<username>/<name>/releases/<id>/` (Delete)
* Requires authentication.
* Deletes release.
@@ -267,7 +289,7 @@ Examples:
```bash
# Create release from Git
curl -X POST https://content.minetest.net/api/packages/username/name/releases/new/ \
curl -X POST https://content.luanti.org/api/packages/username/name/releases/new/ \
-H "Authorization: Bearer YOURTOKEN" -H "Content-Type: application/json" \
-d '{
"method": "git",
@@ -278,17 +300,17 @@ curl -X POST https://content.minetest.net/api/packages/username/name/releases/ne
}'
# Create release from zip upload
curl -X POST https://content.minetest.net/api/packages/username/name/releases/new/ \
curl -X POST https://content.luanti.org/api/packages/username/name/releases/new/ \
-H "Authorization: Bearer YOURTOKEN" \
-F title="My Release" -F file=@path/to/file.zip
# Create release from zip upload with commit hash
curl -X POST https://content.minetest.net/api/packages/username/name/releases/new/ \
curl -X POST https://content.luanti.org/api/packages/username/name/releases/new/ \
-H "Authorization: Bearer YOURTOKEN" \
-F title="My Release" -F commit="8ef74deec170a8ce789f6055a59d43876d16a7ea" -F file=@path/to/file.zip
# Delete release
curl -X DELETE https://content.minetest.net/api/packages/username/name/releases/3/ \
curl -X DELETE https://content.luanti.org/api/packages/username/name/releases/3/ \
-H "Authorization: Bearer YOURTOKEN"
```
@@ -329,26 +351,26 @@ Examples:
```bash
# Create screenshot
curl -X POST https://content.minetest.net/api/packages/username/name/screenshots/new/ \
curl -X POST https://content.luanti.org/api/packages/username/name/screenshots/new/ \
-H "Authorization: Bearer YOURTOKEN" \
-F title="My Release" -F file=@path/to/screnshot.png
# Create screenshot and set it as the cover image
curl -X POST https://content.minetest.net/api/packages/username/name/screenshots/new/ \
curl -X POST https://content.luanti.org/api/packages/username/name/screenshots/new/ \
-H "Authorization: Bearer YOURTOKEN" \
-F title="My Release" -F file=@path/to/screnshot.png -F is_cover_image="true"
# Delete screenshot
curl -X DELETE https://content.minetest.net/api/packages/username/name/screenshots/3/ \
curl -X DELETE https://content.luanti.org/api/packages/username/name/screenshots/3/ \
-H "Authorization: Bearer YOURTOKEN"
# Reorder screenshots
curl -X POST https://content.minetest.net/api/packages/username/name/screenshots/order/ \
curl -X POST https://content.luanti.org/api/packages/username/name/screenshots/order/ \
-H "Authorization: Bearer YOURTOKEN" -H "Content-Type: application/json" \
-d "[13, 2, 5, 7]"
# Set cover image
curl -X POST https://content.minetest.net/api/packages/username/name/screenshots/cover-image/ \
curl -X POST https://content.luanti.org/api/packages/username/name/screenshots/cover-image/ \
-H "Authorization: Bearer YOURTOKEN" -H "Content-Type: application/json" \
-d "{ 'cover_image': 123 }"
```
@@ -433,6 +455,7 @@ Example:
* `reason_new`: list of integers per day.
* `reason_dependency`: list of integers per day.
* `reason_update`: list of integers per day.
* `views_minetest`: list of integers per day.
## Topics
@@ -458,7 +481,7 @@ Supported query parameters:
## Collections
* GET `/api/collections/`
* Query args:
* Query args:
* `author`: collection author username.
* `package`: collections that contain the package.
* Returns JSON array of collection entries:
@@ -468,7 +491,7 @@ Supported query parameters:
* `short_description`
* `created_at`: creation time in iso format.
* `private`: whether collection is private, boolean.
* `package_count`: number of packages, integer.
* `package_count`: number of packages, integer.
* GET `/api/collections/<username>/<name>/`
* Returns JSON object for collection:
* `author`: author username.
@@ -498,7 +521,7 @@ Supported query parameters:
### Content Warnings
* GET `/api/content_warnings/` ([View](/api/content_warnings/))
* List of objects with
* List of objects with
* `name`: technical name
* `title`: human-readable title
* `description`: tag description or null
@@ -506,14 +529,14 @@ Supported query parameters:
### Licenses
* GET `/api/licenses/` ([View](/api/licenses/))
* List of objects with:
* List of objects with:
* `name`
* `is_foss`: whether the license is foss
### Minetest Versions
### Luanti Versions
* GET `/api/minetest_versions/` ([View](/api/minetest_versions/))
* List of objects with:
* List of objects with:
* `name`: Version name.
* `is_dev`: boolean, is dev version.
* `protocol_version`: protocol version number.
@@ -521,7 +544,7 @@ Supported query parameters:
### Languages
* GET `/api/languages/` ([View](/api/languages/))
* List of objects with:
* List of objects with:
* `id`: language code.
* `title`: native language name.
* `has_contentdb_translation`: whether ContentDB has been translated into this language.
@@ -552,13 +575,11 @@ Supported query parameters:
* `pop_txp`: popular textures
* `pop_game`: popular games
* `high_reviewed`: highest reviewed
* GET `/api/welcome/v1/` ([View](/api/welcome/v1/)) - in-menu welcome dialog. Experimental (may change without warning)
* `featured`: featured games
* GET `/api/cdb_schema/` ([View](/api/cdb_schema/))
* Get JSON Schema of `.cdb.json`, including licenses, tags and content warnings.
* See [JSON Schema Reference](https://json-schema.org/).
* POST `/api/hypertext/`
* Converts HTML or Markdown to [Minetest Markup Language](https://github.com/minetest/minetest/blob/master/doc/lua_api.md#markup-language)
* Converts HTML or Markdown to [Luanti Markup Language](https://github.com/minetest/minetest/blob/master/doc/lua_api.md#markup-language)
to be used in a `hypertext` formspec element.
* Post data: HTML or Markdown as plain text.
* Content-Type: `text/html` or `text/markdown`.

View File

@@ -8,8 +8,8 @@ Expand on the title with the short description. You have a limited number
of characters, use them wisely!
```ini
# Bad, we know this is a mod for Minetest. Doesn't give much information other than "food"
description = The food mod for Minetest
# Bad, we know this is a mod for Luanti. Doesn't give much information other than "food"
description = The food mod for Luanti
# Much better, says what is actually in this mod!
description = Adds soup, cakes, bakes and juices
```
@@ -20,7 +20,7 @@ A good thumbnail goes a long way to making a package more appealing. It's one of
a user sees before clicking on your package. Make sure it's possible to tell what a
thumbnail is when it's small.
For a preview of what your package will look like inside Minetest, see
For a preview of what your package will look like inside Luanti, see
Edit Package > Screenshots.
## Screenshots
@@ -36,7 +36,7 @@ The target audience of your package page is end users.
The long description should explain what your package is about,
why the user should choose it, and how to use it if they download it.
[NodeCore](https://content.minetest.net/packages/Warr1024/nodecore/) is a good
[NodeCore](https://content.luanti.org/packages/Warr1024/nodecore/) is a good
example of what to do. For inspiration, you might want to look at how games on
Steam write their descriptions.
@@ -55,18 +55,18 @@ The following are redundant and should probably not be included:
* API reference (unless your mod is a library only)
* Development instructions for your package (this should be in the repo's README)
* Screenshots that are already uploaded (unless you want to embed a recipe image in a specific place)
* Note: you should avoid images in the long description as they won't be visible inside Minetest,
* Note: you should avoid images in the long description as they won't be visible inside Luanti,
when support for showing the long description is added.
## Localize / Translate your package
According to Google Play, 64% of Minetest Android users don't have English as their main language.
According to Google Play, 64% of Luanti Android users don't have English as their main language.
Adding translation support to your package increases accessibility. Using content translation, you
can also translate your ContentDB page. See Edit Package > Translation for more information.
<p>
<a class="btn btn-primary me-2" href="https://rubenwardy.com/minetest_modding_book/en/quality/translations.html">
{{ _("Translation - Minetest Modding Book") }}
{{ _("Translation - Luanti Modding Book") }}
</a>
<a class="btn btn-primary" href="https://api.minetest.net/translations/#translating-content-meta">
{{ _("Translating content meta - lua_api.md") }}

View File

@@ -6,7 +6,7 @@ your client to use new flags.
## Flags
Minetest allows you to specify a comma-separated list of flags to hide in the
Luanti allows you to specify a comma-separated list of flags to hide in the
client:
```
@@ -17,7 +17,7 @@ A flag can be:
* `nonfree`: can be used to hide packages which do not qualify as
'free software', as defined by the Free Software Foundation.
* `wip`: packages marked as Work in Progress
* `wip`: packages marked as Work in Progress
* `deprecated`: packages marked as Deprecated
* A content warning, given below.
* `*`: hides all content warnings.
@@ -33,8 +33,8 @@ without making a release.
Packages with mature content will be tagged with a content warning based
on the content type.
* `alcohol_tobacco`: alcohol or tobacco.
* `bad_language`: swearing.
* `drugs`: drugs or alcohol.
* `gambling`
* `gore`: blood, etc.
* `horror`: shocking and scary content.

View File

@@ -48,7 +48,7 @@ It's common to do this in README.md or LICENSE.md like so:
* conquer_arrow_*.png from [Simple Shooter](https://github.com/stujones11/shooter) by Stuart Jones, CC0 1.0.
* conquer_arrow.b3d from [Simple Shooter](https://github.com/stujones11/shooter) by Stuart Jones, CC-BY-SA 3.0.
* conquer_arrow_head.png from MTG, CC-BY-SA 3.0.
* health_*.png from [Gauges](https://content.minetest.net/packages/Calinou/gauges/) by Calinou, CC0.
* health_*.png from [Gauges](https://content.luanti.org/packages/Calinou/gauges/) by Calinou, CC0.
```
if you have a lot of media, then you can split it up by author like so:
@@ -75,7 +75,7 @@ Your Name, CC BY-SA 4.0:
* [Kenney game assets](https://www.kenney.nl/assets) - everything
* [Free Sound](https://freesound.org/) - sounds
* [PolyHaven](https://polyhaven.com/) - 3d models and textures.
* Other Minetest mods/games
* Other Luanti mods/games
Don't assume the author has correctly licensed their work.
Make sure they have clearly indicated the source in a list [like above](#list-the-sources-of-your-media).

View File

@@ -48,11 +48,11 @@ There are a number of methods:
* [Webhooks](/help/release_webhooks/): you can configure your Git host to send a webhook to ContentDB, and create an update immediately.
* the [API](/help/api/): This is especially powerful when combined with CI/CD and other API endpoints.
### How do I learn how to make mods and games for Minetest?
### How do I learn how to make mods and games for Luanti?
You should read
[the official Minetest Modding Book](https://rubenwardy.com/minetest_modding_book/)
for a guide to making mods and games using Minetest.
[the official Luanti Modding Book](https://rubenwardy.com/minetest_modding_book/)
for a guide to making mods and games using Luanti.
### How do I install something from here?

View File

@@ -7,10 +7,10 @@ title: Featured Packages
## What are Featured Packages?
Featured Packages are shown at the top of the ContentDB homepage. In the future,
featured packages may be shown inside the Minetest client.
featured packages may be shown inside the Luanti client.
The purpose is to promote content that demonstrates a high quality of what is
possible in Minetest. The selection should be varied, and should vary over time.
possible in Luanti. The selection should be varied, and should vary over time.
The featured content should be content that we are comfortable recommending to
a first time player.
@@ -47,7 +47,7 @@ other packages to be featured, or for another reason.
* MUST: Be 100% free and open source (as marked as Free on ContentDB).
* MUST: Work out-of-the-box (no weird setup or settings required).
* MUST: Be compatible with the latest stable Minetest release.
* MUST: Be compatible with the latest stable Luanti release.
* SHOULD: Use public source control (such as Git).
* SHOULD: Have at least 3 reviews, and be largely positive.
@@ -94,7 +94,7 @@ is available.
### Usability
* MUST: Unsupported mapgens are disabled in game.conf.
* SHOULD: Passes the Beginner Test: A newbie to the game (but not Minetest) wouldn't get completely
* SHOULD: Passes the Beginner Test: A newbie to the game (but not Luanti) wouldn't get completely
stuck within the first 5 minutes of playing.
* SHOULD: Have good documentation. This may include one or more of:
* A craftguide, or other in-game learning system

View File

@@ -11,6 +11,6 @@ You can follow updates from ContentDB in your RSS feed reader. If in doubt, copy
Follow new releases for a package:
```
https://content.minetest.net/packages/AUTHOR/NAME/releases_feed.atom
https://content.minetest.net/packages/AUTHOR/NAME/releases_feed.json
https://content.luanti.org/packages/AUTHOR/NAME/releases_feed.atom
https://content.luanti.org/packages/AUTHOR/NAME/releases_feed.json
```

View File

@@ -17,7 +17,7 @@ You can use `unsupported_games` to specify games that your package doesn't work
with, which is useful for overriding ContentDB's automatic detection.
Both of these are comma-separated lists of game technical ids. Any `_game`
suffixes are ignored, just like in Minetest.
suffixes are ignored, just like in Luanti.
supported_games = minetest_game, repixture
unsupported_games = lordofthetest, nodecore, whynot

View File

@@ -1,5 +1,5 @@
title: How to install mods, games, and texture packs
description: A guide to installing mods, games, and texture packs in Minetest.
description: A guide to installing mods, games, and texture packs in Luanti.
## Installing from the main menu (recommended)
@@ -7,8 +7,8 @@ description: A guide to installing mods, games, and texture packs in Minetest.
1. Open the mainmenu
2. Go to the Content tab and click "Browse online content".
If you don't see this, then you need to update Minetest to v5.
3. Search for the package you want to install, and click "Install".
If you don't see this, then you need to update Luanti to v5.
3. Search for the package you want to install, and click "Install".
4. When installing a mod, you may be shown a dialog about dependencies here.
Make sure the base game dropdown box is correct, and then click "Install".
@@ -16,7 +16,7 @@ description: A guide to installing mods, games, and texture packs in Minetest.
<div class="col-md-6">
<figure>
<a href="/static/installing_content_tab.png">
<img class="w-100" src="/static/installing_content_tab.png" alt="Screenshot of the content tab in minetest">
<img class="w-100" src="/static/installing_content_tab.png" alt="Screenshot of the content tab in Luanti">
</a>
<figcaption class="text-muted ps-1">
1. Click Browser Online Content in the content tab.
@@ -26,7 +26,7 @@ description: A guide to installing mods, games, and texture packs in Minetest.
<div class="col-md-6">
<figure>
<a href="/static/installing_cdb_dialog.png">
<img class="w-100" src="/static/installing_cdb_dialog.png" alt="Screenshot of the content tab in minetest">
<img class="w-100" src="/static/installing_cdb_dialog.png" alt="Screenshot of the content tab in Luanti">
</a>
<figcaption class="text-muted ps-1">
2. Search for the package and click "Install".
@@ -38,7 +38,7 @@ description: A guide to installing mods, games, and texture packs in Minetest.
Troubleshooting:
* I can't find it in the ContentDB dialog (Browse online content)
* Make sure that you're on the latest version of Minetest.
* Make sure that you're on the latest version of Luanti.
* Are you using Android? Packages with content warnings are hidden by default on android,
you can show them by removing `android_default` from the `contentdb_flag_blacklist` setting.
* Does the webpage show "Non-free" warnings? Non-free content is hidden by default from all clients,
@@ -51,14 +51,14 @@ Troubleshooting:
1. Mods: Enable the content using "Select Mods" when selecting a world.
2. Games: choose a game when making a world.
3. Texture packs: Content > Select pack > Click enable.
3. Texture packs: Content > Select pack > Click enable.
<div class="row mt-5">
<div class="col-md-6">
<figure>
<a href="/static/installing_select_mods.png">
<img class="w-100" src="/static/installing_select_mods.png" alt="Screenshot of Select Mods in Minetest">
<img class="w-100" src="/static/installing_select_mods.png" alt="Screenshot of Select Mods in Luanti">
</a>
<figcaption class="text-muted ps-1">
Enable mods using the Select Mods dialog.
@@ -76,7 +76,7 @@ Troubleshooting:
3. Find the user data directory.
In 5.4.0 and above, you can click "Open user data directory" in the Credits tab.
Otherwise:
* Windows: whereever you extracted or installed Minetest to.
* Windows: wherever you extracted or installed Luanti to.
* Linux: usually `~/.minetest/`
4. Open or create the folder for the type of content (`mods`, `games`, or `textures`)
5. Git clone there

View File

@@ -13,7 +13,7 @@ and they will be subject to limited promotion.
**ContentDB does not allow certain non-free licenses, and will limit the promotion
of packages with non-free licenses.**
Minetest is free and open source software, and is only as big as it is now
Luanti is free and open source software, and is only as big as it is now
because of this. It's pretty amazing you can take nearly any published mod and modify it
to how you like - add some features, maybe fix some bugs - and then share those
modifications without the worry of legal issues. The project, itself, relies on open
@@ -24,9 +24,9 @@ If you have played nearly any game with a large modding scene, you will find
that most mods are legally ambiguous. A lot of them don't even provide the
source code to allow you to bug fix or extend as you need.
Limiting the promotion of problematic licenses helps Minetest avoid ending up in
Limiting the promotion of problematic licenses helps Luanti avoid ending up in
such a state. Licenses that prohibit redistribution or modification are
completely banned from ContentDB and the Minetest forums. Other non-free licenses
completely banned from ContentDB and the Luanti forums. Other non-free licenses
will be subject to limited promotion - they won't be shown by default in
the client.
@@ -37,7 +37,7 @@ you spread it.
## What's so bad about licenses that forbid commercial use?
Please read [reasons not to use a Creative Commons -NC license](https://freedomdefined.org/Licenses/NC).
Here's a quick summary related to Minetest content:
Here's a quick summary related to Luanti content:
1. They make your work incompatible with a growing body of free content, even if
you do want to allow derivative works or combinations.
@@ -68,7 +68,7 @@ Users can opt in to showing non-free software, if they wish:
The [`platform_default` flag](/help/content_flags/) is used to control what content
each platforms shows. It doesn't hide anything on Desktop, but hides all mature
content on Android. You may wish to remove all text from that setting completely,
content on Android. You may wish to remove all text from that setting completely,
leaving it blank. See [Content Warnings](/help/content_flags/#content-warnings)
for information on mature content.

View File

@@ -27,7 +27,7 @@ ContentDB supports the Authorization Code OAuth2 method.
Get the user to open the following URL in a web browser:
```
https://content.minetest.net/oauth/authorize/
https://content.luanti.org/oauth/authorize/
?response_type=code
&client_id={CLIENT_ID}
&redirect_uri={REDIRECT_URL}
@@ -52,7 +52,7 @@ Next, you'll need to exchange the auth for an access token.
Do this by making a POST request to the `/oauth/token/` API:
```bash
curl -X POST https://content.minetest.net/oauth/token/ \
curl -X POST https://content.luanti.org/oauth/token/ \
-F grant_type=authorization_code \
-F client_id="CLIENT_ID" \
-F client_secret="CLIENT_SECRET" \
@@ -98,6 +98,6 @@ Possible errors:
Next, you should check the access token works by getting the user information:
```bash
curl https://content.minetest.net/api/whoami/ \
curl https://content.luanti.org/api/whoami/ \
-H "Authorization: Bearer YOURTOKEN"
```

View File

@@ -42,8 +42,8 @@ ContentDB understands the following information:
* `description` - A short description to show in the client.
* `depends` - Comma-separated hard dependencies.
* `optional_depends` - Comma-separated soft dependencies.
* `min_minetest_version` - The minimum Minetest version this runs on, see [Min and Max Minetest Versions](#min_max_versions).
* `max_minetest_version` - The maximum Minetest version this runs on, see [Min and Max Minetest Versions](#min_max_versions).
* `min_minetest_version` - The minimum Luanti version this runs on, see [Min and Max Luanti Versions](#min_max_versions).
* `max_minetest_version` - The maximum Luanti version this runs on, see [Min and Max Luanti Versions](#min_max_versions).
and for mods only:
@@ -68,7 +68,7 @@ It should be a JSON dictionary with one or more of the following optional keys:
* `tags`: List of tag names, see [/api/tags/](/api/tags/).
* `content_warnings`: List of content warning names, see [/api/content_warnings/](/api/content_warnings/).
* `license`: A license name, see [/api/licenses/](/api/licenses/).
* `media_license`: A license name.
* `media_license`: A license name.
* `long_description`: Long markdown description.
* `repo`: Source repository (eg: Git).
* `website`: Website URL.
@@ -106,11 +106,11 @@ See [Git Update Detection](/help/update_config/).
You can also use [GitLab/GitHub webhooks](/help/release_webhooks/) or the [API](/help/api/)
to create releases.
### Min and Max Minetest Versions
### Min and Max Luanti Versions
<a name="min_max_versions" />
When creating a release, the `.conf` file will be read to determine what Minetest
When creating a release, the `.conf` file will be read to determine what Luanti
versions the release supports. If the `.conf` doesn't specify, then it is assumed
that it supports all versions.

View File

@@ -20,7 +20,7 @@ The process is as follows:
3. The git host posts a webhook notification to ContentDB, using the API token assigned to it.
4. ContentDB checks the API token and issues a new release.
* If multiple packages match, then only the first will have a release created.
### Branch filtering
By default, "New commit" or "push" based webhooks will only work on "master"/"main" branches.
@@ -39,7 +39,7 @@ Tag-based webhooks are accepted on any branch.
1. Create a ContentDB API Token at [Profile > API Tokens: Manage](/user/tokens/).
2. Copy the access token that was generated.
3. Go to the GitLab repository's settings > Webhooks > Add Webhook.
4. Set the payload URL to `https://content.minetest.net/github/webhook/`
4. Set the payload URL to `https://content.luanti.org/github/webhook/`
5. Set the content type to JSON.
6. Set the secret to the access token that you copied.
7. Set the events
@@ -53,7 +53,7 @@ Tag-based webhooks are accepted on any branch.
1. Create a ContentDB API Token at [Profile > API Tokens: Manage](/user/tokens/).
2. Copy the access token that was generated.
3. Go to the GitLab repository's settings > Webhooks.
4. Set the URL to `https://content.minetest.net/gitlab/webhook/`
4. Set the URL to `https://content.luanti.org/gitlab/webhook/`
6. Set the secret token to the ContentDB access token that you copied.
7. Set the events
* If you want a rolling release, choose "Push events".
@@ -67,5 +67,5 @@ Tag-based webhooks are accepted on any branch.
See the [Package Configuration and Releases Guide](/help/package_config/) for
documentation on configuring the release creation.
From the Git repository, you can set the min/max Minetest versions, which files are included,
From the Git repository, you can set the min/max Luanti versions, which files are included,
and update the package meta.

View File

@@ -39,5 +39,5 @@ Clicking "Save" on "Update Settings" will mark a package as up-to-date.
See the [Package Configuration and Releases Guide](/help/package_config/) for
documentation on configuring the release creation.
From the Git repository, you can set the min/max Minetest versions, which files are included,
From the Git repository, you can set the min/max Luanti versions, which files are included,
and update the package meta.

View File

@@ -31,6 +31,8 @@ including ones not covered by this document, and to ban users who abuse this ser
Sexually-orientated content is not permitted.
If in doubt at what this means, [contact us by raising a report](/report/).
Content which depicts or encourages the use of illegal drugs (under the laws of the United Kingdom) is not permitted.
Mature content is permitted providing that it is labelled correctly.
See [Content Flags](/help/content_flags/).
@@ -107,7 +109,7 @@ of the [Free Software Foundation](https://www.gnu.org/philosophy/free-sw.en.html
It is highly recommended that you use a Free and Open Source software (FOSS)
license. FOSS licenses result in a sharing community and will increase the
number of potential users your package has. Using a closed source license will
result in your package not being shown in Minetest by default. See the help page
result in your package not being shown in Luanti by default. See the help page
on [non-free licenses](/help/non_free/) for more information.
It is recommended that you use a proper license for code with a warranty

View File

@@ -56,7 +56,7 @@ Please avoid giving other personal information as we do not want it.
* Only the admin has access to the HTTP requests.
The logs may be shared with others to aid in debugging, but care will be taken to remove any personal information.
* Encrypted backups may be shared with selected Minetest staff members (moderators + core devs).
* Encrypted backups may be shared with selected Luanti staff members (moderators + core devs).
The keys and the backups themselves are given to different people,
requiring at least two staff members to read a backup.
* Email addresses are visible to moderators and the admin.

View File

@@ -1,15 +0,0 @@
title: Rules
The following are the rules for user behaviour on ContentDB, including reviews,
threads, comments, and profiles. For packages, see the
[Package Inclusion Policy](/policy_and_guidance/).
1. **Be respectful:** attacks towards any person or group, slurs,
trolling/baiting, and other toxic behavior are not welcome.
2. **Assume good faith:** communication over the Internet is hard, try to assume
good faith when eg: responding to reviews.
3. **No sexual content** and ensure you keep discussion appropriate given the
package's [content warnings](/help/content_flags/).
You can report things by clicking [report](/report/) in the footer of pages you
want to report.

133
app/flatpages/terms.md Normal file
View File

@@ -0,0 +1,133 @@
title: Terms of Service
Also see the [Package Inclusion Policy](/policy_and_guidance/).
## Content
### Prohibited content
You must not post/transmit anything which is illegal under the laws in any part of the United Kingdom.
You must not (or use the service to) facilitate or commit any offence under the laws in any part of the United Kingdom.
This includes, in particular, terrorism content (as set out in Schedule 5, Online Safety Act 2023),
child sexual exploitation and abuse content (as set out in Schedule 6, Online Safety Act 2023), and
content that amounts to an offence specified in Schedule 7, Online Safety Act 2023.
Prohibited content includes:
* Pornographic content. This includes content of such a nature that it is reasonable to assume that it was produced
solely or principally for the purpose of sexual arousal.
* Content which encourages, promotes or provides instructions for suicide
* Content which encourages, promotes or provides instructions for an act of deliberate self-injury
* Content which encourages, promotes or provides instructions for an eating disorder or behaviours associated with an eating disorder
* Content which is abusive and which targets any of the following characteristics: race, religion, sex,
sexual orientation, disability, gender reassignment.
* Content which incites hatred against people:
* of a particular race, religion, sex or sexual orientation
* who have a disability
* who have the characteristic of gender reassignment
* Content which encourages, promotes or provides instructions for an act of serious violence against a person
* Bullying content
* Content which:
* depicts real or realistic serious violence against a person
* depicts the real or realistic serious injury of a person in graphic detail
* Content which:
* depicts real or realistic serious violence against an animal
* depicts the real or realistic serious injury of an animal in graphic detail
* realistically depicts serious violence against a fictional creature or the serious injury of a fictional
creature in graphic detail
* Content which encourages, promotes or provides instructions for a challenge or stunt highly likely to result in
serious injury to the person who does it or to someone else
* Content which encourages a person to ingest, inject, inhale or in any other way self-administer:
* a physically harmful substance
* a substance in such a quantity as to be physically harmful
### Protecting users from illegal content
We provide this service free of charge, and on the basis that we may:
* take down, or restrict access to, anything that you generate, upload or share; and
* suspend or ban you from using all or part of the service
if we think that this is reasonable to protect you, other users, the service, or us. This applies, in particular,
to prohibited content.
If we are alerted by a person to the presence of any illegal content, or we become aware of it in any other way,
we will swiftly take down that content.
To minimise the length of time for which any illegal content within the scope of the Online Safety Act 2023 is present:
* in respect of terrorism content, we offer an easy-to-access and use reporting function and will swiftly remove such content when we become aware of it.
* in respect of child sexual exploitation or abuse content, we offer an easy-to-access and use reporting function and will swiftly remove such content when we become aware of it.
* in respect of other content that amounts to an offence specified in Schedule 7, Online Safety Act 2023, we offer an easy-to-access and use reporting function and will swiftly remove such content when we become aware of it.
### Protecting children
We protect all children from the kinds of content listed in "Prohibited Content" by:
* prohibiting that type of content from our service; and
* swiftly taking down that content, if we are alerted by a person to its presence, or we become aware of it in any other way.
### Proactive technology
We do not use proactive technology to detect illegal content.
## Limitation of Liability
THE SERVICE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL WE BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SERVICE OR THE USE OR OTHER DEALINGS IN THE SERVICE.
We reserve the right to ban or suspend your account, or take down your content, for any reason.
## Jurisdiction and Governing Law
This service is subject to the jurisdiction of the United Kingdom.
## Complaints
### Reporting content
You may report content by clicking the report flag next to a comment or "Report" on the page containing the content.
You can also make reports by [contacting the admin]({{ admin_contact_url }}).
### Complaints and Appeals
You may send a complaint / request an appeal by [contacting the admin]({{ admin_contact_url }}).
### Your right to bring a claim
This clause applies only to users within the United Kingdom.
The Online Safety Act 2023 says that you have a right to bring a claim for breach of contract if:
* anything that you generate, upload or share is taken down, or access to it is restricted, in breach of the terms of service, or
* you are suspended or banned from using the service in breach of the terms of service.
This does not apply to emails, SMS messages, MMS messages, one-to-one live aural communications,
comments and reviews (together with any further comments on such comments or reviews), or content which identifies
you as a user (e.g. a user name or profile picture).
Whether or not a contract exists between you and us is a question of fact. If we do not have a contractual
relationship with you in respect of the service, there can be no breach of contract and, as such, this cannot apply.
It is for a court to determine:
- if there is a contract between you and us and, if so, its terms
- if there has been a breach by us of that contract
- if that breach has caused you any recoverable loss
- the size (e.g. value) of your loss
This clause is subject to "Limitation of liability" and "Jurisdiction".
## Acknowledgements
This terms of service was written based on [a template](https://onlinesafetyact.co.uk/online_safety_act_terms/)
created by Neil Brown, CC BY-SA 4.0.

View File

@@ -13,7 +13,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import sys
from typing import List, Dict, Optional, Tuple
import sqlalchemy
@@ -90,7 +90,6 @@ class GSPackage:
return self.user_unsupported_games
def add_error(self, error: str):
print(f"ERROR {self.name}: {error}")
return self.errors.add(error)
@@ -136,12 +135,10 @@ class GameSupport:
return [package for package in self.packages.values() if modname in package.depends]
def _get_supported_games_for_modname(self, depend: str, visited: list[str]):
print(f"_get_supported_games_for_modname {depend} visited {', '.join(visited)}", file=sys.stderr)
dep_supports_all = False
for_dep = set()
for provider in self.get_all_that_provide(depend):
found_in = self._get_supported_games(provider, visited)
print(f" - provider for {depend}: {provider.name}: {found_in}", file=sys.stderr)
if found_in is None:
# Unsupported, keep going
pass
@@ -154,7 +151,6 @@ class GameSupport:
return dep_supports_all, for_dep
def _get_supported_games_for_deps(self, package: GSPackage, visited: list[str]) -> Optional[set[str]]:
print(f"_get_supported_games_for_deps package {package.name} visited {', '.join(visited)}", file=sys.stderr)
ret = set()
for depend in package.depends:
@@ -177,21 +173,18 @@ class GameSupport:
return ret
def _get_supported_games(self, package: GSPackage, visited: list[str]) -> Optional[set[str]]:
print(f"_get_supported_games package {package.name} visited {', '.join(visited)}", file=sys.stderr)
if package.id_ in visited:
first_idx = visited.index(package.id_)
visited = visited[first_idx:]
err = f"Dependency cycle detected: {' -> '.join(visited)} -> {package.id_}"
for id_ in visited:
package2 = self.get(id_)
package2.add_error(err)
# first_idx = visited.index(package.id_)
# visited = visited[first_idx:]
# err = f"Dependency cycle detected: {' -> '.join(visited)} -> {package.id_}"
# for id_ in visited:
# package2 = self.get(id_)
# package2.add_error(err)
return None
if package.type == PackageType.GAME:
print(f"_get_supported_games package {package.name} is game", file=sys.stderr)
return {package.name}
elif package.is_confirmed:
print(f"_get_supported_games package {package.name} is confirmed", file=sys.stderr)
return package.supported_games
visited = visited.copy()
@@ -228,7 +221,6 @@ class GameSupport:
while len(to_update) > 0:
current_package = to_update.pop()
print(f"on_update package {current_package.name}", file=sys.stderr)
if current_package.id_ in self.packages and current_package.type != PackageType.GAME:
self._get_supported_games(current_package, [])

View File

@@ -28,7 +28,7 @@ def daterange(start_date, end_date):
keys = ["platform_minetest", "platform_other", "reason_new",
"reason_dependency", "reason_update"]
"reason_dependency", "reason_update", "views_minetest"]
def flatten_data(stats):
@@ -78,7 +78,8 @@ def get_package_stats_for_user(user: User, start_date: Optional[datetime.date],
func.sum(PackageDailyStats.platform_other).label("platform_other"),
func.sum(PackageDailyStats.reason_new).label("reason_new"),
func.sum(PackageDailyStats.reason_dependency).label("reason_dependency"),
func.sum(PackageDailyStats.reason_update).label("reason_update")) \
func.sum(PackageDailyStats.reason_update).label("reason_update"),
func.sum(PackageDailyStats.views_minetest).label("views_minetest")) \
.filter(PackageDailyStats.package.has(author_id=user.id))
if start_date:

View File

@@ -107,7 +107,7 @@ def validate(data: dict):
def do_edit_package(user: User, package: Package, was_new: bool, was_web: bool, data: dict,
reason: str = None):
reason: str = None) -> bool:
if not package.check_perm(user, Permission.EDIT_PACKAGE):
raise LogicError(403, lazy_gettext("You don't have permission to edit this package"))
@@ -192,9 +192,11 @@ def do_edit_package(user: User, package: Package, was_new: bool, was_web: bool,
raise LogicError(400, "Unknown warning: " + warning_id)
package.content_warnings.append(warning)
was_modified = was_new
if not was_new:
after_dict = package.as_dict("/")
diff = diff_dictionaries(before_dict, after_dict)
was_modified = len(diff) > 0
if reason is None:
msg = "Edited {}".format(package.title)
@@ -208,6 +210,7 @@ def do_edit_package(user: User, package: Package, was_new: bool, was_web: bool,
severity = AuditSeverity.NORMAL if user in package.maintainers else AuditSeverity.EDITOR
add_audit_log(severity, user, msg, package.get_url("packages.view"), package, json.dumps(diff, indent=4))
db.session.commit()
if was_modified:
db.session.commit()
return package
return was_modified

View File

@@ -28,7 +28,7 @@ from app.tasks.importtasks import make_vcs_release, check_zip_release
from app.utils import AuditSeverity, add_audit_log, nonempty_or_none, normalize_line_endings
def check_can_create_release(user: User, package: Package):
def check_can_create_release(user: User, package: Package, name: str):
if not package.check_perm(user, Permission.MAKE_RELEASE):
raise LogicError(403, lazy_gettext("You don't have permission to make releases"))
@@ -37,10 +37,13 @@ def check_can_create_release(user: User, package: Package):
if count >= 5:
raise LogicError(429, lazy_gettext("You've created too many releases for this package in the last 5 minutes, please wait before trying again"))
if PackageRelease.query.filter_by(package_id=package.id, name=name).count() > 0:
raise LogicError(403, lazy_gettext("A release with this name already exists"))
def do_create_vcs_release(user: User, package: Package, name: str, title: Optional[str], release_notes: Optional[str], ref: str,
min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason: str = None):
check_can_create_release(user, package)
check_can_create_release(user, package, name)
rel = PackageRelease()
rel.package = package
@@ -69,7 +72,7 @@ def do_create_vcs_release(user: User, package: Package, name: str, title: Option
def do_create_zip_release(user: User, package: Package, name: str, title: Optional[str], release_notes: Optional[str], file,
min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason: str = None,
commit_hash: str = None):
check_can_create_release(user, package)
check_can_create_release(user, package, name)
if commit_hash:
commit_hash = commit_hash.lower()

View File

@@ -158,7 +158,7 @@ class ForumTopic(db.Model):
@property
def url(self):
return "https://forum.minetest.net/viewtopic.php?t=" + str(self.topic_id)
return "https://forum.luanti.org/viewtopic.php?t=" + str(self.topic_id)
def get_repo_url(self):
if self.link is None:

View File

@@ -457,7 +457,7 @@ class Package(db.Model):
if self.forums is None:
return None
return "https://forum.minetest.net/viewtopic.php?t=" + str(self.forums)
return "https://forum.luanti.org/viewtopic.php?t=" + str(self.forums)
enable_game_support_detection = db.Column(db.Boolean, nullable=False, default=True)
@@ -679,6 +679,7 @@ class Package(db.Model):
"website": self.website,
"issue_tracker": self.issueTracker,
"forums": self.forums,
"forum_url": self.forums_url,
"video_url": self.video_url,
"video_thumbnail_url": self.get_video_thumbnail_url(True),
"donate_url": self.donate_url_actual,
@@ -811,7 +812,7 @@ class Package(db.Model):
elif perm == Permission.APPROVE_SCREENSHOT:
return (is_maintainer or is_approver) and \
user.rank.at_least(UserRank.MEMBER if self.approved else UserRank.NEW_MEMBER)
user.rank.at_least(UserRank.TRUSTED_MEMBER if self.approved else UserRank.NEW_MEMBER)
elif perm == Permission.EDIT_MAINTAINERS or perm == Permission.DELETE_PACKAGE:
return is_owner or user.rank.at_least(UserRank.EDITOR)
@@ -1070,8 +1071,7 @@ class MinetestRelease(db.Model):
if version:
parts = version.strip().split(".")
if len(parts) >= 2:
major_minor = parts[0] + "." + parts[1]
query = MinetestRelease.query.filter(MinetestRelease.name.like("{}%".format(major_minor)))
query = MinetestRelease.query.filter(func.replace(MinetestRelease.name, "-dev", "") == "{}.{}".format(parts[0], parts[1]))
if protocol_num:
query = query.filter_by(protocol=protocol_num)
@@ -1437,8 +1437,11 @@ class PackageDailyStats(db.Model):
reason_dependency = db.Column(db.Integer, nullable=False, default=0)
reason_update = db.Column(db.Integer, nullable=False, default=0)
views_minetest = db.Column(db.Integer, nullable=False, default=0)
v510 = db.Column(db.Integer, nullable=False, default=0)
@staticmethod
def update(package: Package, is_minetest: bool, reason: str):
def notify_download(package: Package, is_minetest: bool, is_v510: bool, reason: str):
date = datetime.datetime.utcnow().date()
to_update = dict()
@@ -1462,6 +1465,26 @@ class PackageDailyStats(db.Model):
to_update[field_reason] = getattr(PackageDailyStats, field_reason) + 1
kwargs[field_reason] = 1
if is_v510:
to_update["v510"] = PackageDailyStats.v510 + 1
kwargs["v510"] = 1
stmt = insert(PackageDailyStats).values(**kwargs)
stmt = stmt.on_conflict_do_update(
index_elements=[PackageDailyStats.package_id, PackageDailyStats.date],
set_=to_update
)
conn = db.session.connection()
conn.execute(stmt)
@staticmethod
def notify_view(package: Package):
date = datetime.datetime.utcnow().date()
to_update = {"views_minetest": PackageDailyStats.views_minetest + 1}
kwargs = {"package_id": package.id, "date": date, "views_minetest": 1}
stmt = insert(PackageDailyStats).values(**kwargs)
stmt = stmt.on_conflict_do_update(
index_elements=[PackageDailyStats.package_id, PackageDailyStats.date],

View File

@@ -260,7 +260,7 @@ class User(db.Model, UserMixin):
return "/static/bot_avatar.png"
else:
from app.utils.gravatar import get_gravatar
return get_gravatar(self.email or f"{self.username}@content.minetest.net")
return get_gravatar(self.email or f"{self.username}@content.luanti.org")
def check_perm(self, user, perm):
if not user.is_authenticated:

View File

@@ -171,7 +171,7 @@ async function load_data() {
const data = {
datasets: [
{ label: "Web / other", data: getData(json.platform_other) },
{ label: "Minetest", data: getData(json.platform_minetest) },
{ label: "Luanti", data: getData(json.platform_minetest) },
],
};
setup_chart(ctx, data, annotations);
@@ -228,6 +228,16 @@ async function load_data() {
};
new Chart(ctx, config);
}
{
const ctx = document.getElementById("chart-views").getContext("2d");
const data = {
datasets: [
{ label: "Luanti", data: getData(json.views_minetest) },
],
};
setup_chart(ctx, data, annotations);
}
}

View File

@@ -26,7 +26,7 @@ window.addEventListener("load", () => {
try {
const pasteData = e.clipboardData.getData('text');
const url = new URL(pasteData);
if (url.hostname === "forum.minetest.net") {
if (url.hostname === "forum.luanti.org") {
forumsField.value = url.searchParams.get("t");
e.preventDefault();
}
@@ -37,7 +37,7 @@ window.addEventListener("load", () => {
const openForums = document.getElementById("forums-button");
openForums.addEventListener("click", () => {
window.open("https://forum.minetest.net/viewtopic.php?t=" + forumsField.value, "_blank");
window.open("https://forum.luanti.org/viewtopic.php?t=" + forumsField.value, "_blank");
});
function setupHints(id, hints) {

View File

@@ -3,7 +3,7 @@
<ShortName>ContentDB</ShortName>
<LongName>ContentDB</LongName>
<InputEncoding>UTF-8</InputEncoding>
<Description>Search mods, games, and textures for Minetest.</Description>
<Tags>Minetest Mod Game Subgame Search</Tags>
<Url type="text/html" method="get" template="https://content.minetest.net/packages?q={searchTerms}"/>
<Description>Search mods, games, and textures for Luanti.</Description>
<Tags>Luanti Minetest Mod Game Subgame Search</Tags>
<Url type="text/html" method="get" template="https://content.luanti.org/packages?q={searchTerms}"/>
</OpenSearchDescription>

View File

@@ -15,6 +15,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from . import redis_client
from .models import Package
# This file acts as a facade between the rest of the code and redis,
# and also means that the rest of the code avoids knowing about `app`
@@ -23,10 +24,14 @@ from . import redis_client
EXPIRY_TIME_S = 2*7*24*60*60 # 2 weeks
def make_download_key(ip, package):
def make_download_key(ip: str, package: Package):
return "{}/{}/{}".format(ip, package.author.username, package.name)
def make_view_key(ip: str, package: Package):
return "view/{}/{}/{}".format(ip, package.author.username, package.name)
def set_temp_key(key, v):
redis_client.set(key, v, ex=EXPIRY_TIME_S)

View File

@@ -57,7 +57,7 @@ def _get_or_create_user(forums_username: str, cache: Optional[dict] = None) -> O
def check_forum_account(forums_username, force_replace_pic=False):
print("### Checking " + forums_username, file=sys.stderr)
try:
profile = get_profile("https://forum.minetest.net", forums_username)
profile = get_profile("https://forum.luanti.org", forums_username)
except OSError as e:
print(e, file=sys.stderr)
return
@@ -88,13 +88,13 @@ def check_forum_account(forums_username, force_replace_pic=False):
db.session.commit()
if pic:
pic = urljoin("https://forum.minetest.net/", pic)
pic = urljoin("https://forum.luanti.org/", pic)
print(f"####### Picture: {pic}", file=sys.stderr)
print(f"####### User pp {user.profile_pic}", file=sys.stderr)
pic_needs_replacing = user.profile_pic is None or user.profile_pic == "" or \
user.profile_pic.startswith("https://forum.minetest.net") or force_replace_pic
if pic_needs_replacing and pic.startswith("https://forum.minetest.net"):
user.profile_pic.startswith("https://forum.luanti.org") or force_replace_pic
if pic_needs_replacing and pic.startswith("https://forum.luanti.org"):
print(f"####### Queueing", file=sys.stderr)
set_profile_picture_from_url.delay(user.username, pic)

View File

@@ -20,7 +20,7 @@ import os
import shutil
import sys
from json import JSONDecodeError
from zipfile import ZipFile
from zipfile import ZipFile, BadZipFile
import gitdb
from flask import url_for
@@ -268,11 +268,8 @@ def update_translations(package: Package, tree: PackageTreeNode):
)
conn.execute(stmt)
raw_translations = tree.get_translations(tree.get("textdomain", tree.name))
raw_translations = tree.get_translations(tree.get("textdomain", tree.name), allowed_languages=allowed_languages)
for raw_translation in raw_translations:
if raw_translation.language not in allowed_languages:
continue
to_update = {
"title": raw_translation.entries.get(tree.get("title", package.title)),
"short_desc": raw_translation.entries.get(tree.get("description", package.short_desc)),
@@ -306,13 +303,16 @@ def _check_zip_file(temp_dir: str, zf: ZipFile) -> bool:
def _safe_extract_zip(temp_dir: str, archive_path: str) -> bool:
with ZipFile(archive_path, 'r') as zf:
if not _check_zip_file(temp_dir, zf):
return False
try:
with ZipFile(archive_path, 'r') as zf:
if not _check_zip_file(temp_dir, zf):
return False
# Extract all
for member in zf.infolist():
zf.extract(member, temp_dir)
# Extract all
for member in zf.infolist():
zf.extract(member, temp_dir)
except BadZipFile as e:
raise TaskError(str(e))
return True
@@ -342,16 +342,15 @@ def check_zip_release(self, id, path):
def check_all_zip_files():
result = []
with get_temp_dir() as temp:
releases = PackageRelease.query.all()
for release in releases:
with ZipFile(release.file_path, 'r') as zf:
if not _check_zip_file(temp, zf):
print(f"Unsafe zip file for {release.package.get_id} at {release.file_path}", file=sys.stderr)
result.append({
"package": release.package.get_id(),
"file": release.file_path,
})
releases = PackageRelease.query.all()
for release in releases:
with ZipFile(release.file_path, 'r') as zf:
if not _check_zip_file("/tmp/example", zf):
print(f"Unsafe zip file for {release.package.get_id()} at {release.file_path}", file=sys.stderr)
result.append({
"package": release.package.get_id(),
"file": release.file_path,
})
return json.dumps(result)

View File

@@ -235,9 +235,12 @@ class PackageTreeNode:
# Calculate short description
if "description" in result:
desc = result["description"]
idx = desc.find(".") + 1
cutIdx = min(len(desc), 200 if idx < 5 else idx)
result["short_description"] = desc[:cutIdx]
if len(desc) > 200:
idx = desc.find(".") + 1
idx = min(len(desc), 200 if idx < 5 else idx)
result["short_description"] = desc[:idx]
else:
result["short_description"] = desc
if "name" in result:
self.name = result["name"]
@@ -313,10 +316,15 @@ class PackageTreeNode:
return ret
def get_translations(self, textdomain: str) -> list[Translation]:
def get_translations(self, textdomain: str, allowed_languages: set[str]) -> list[Translation]:
ret = []
for name in glob.glob(f"{self.baseDir}/**/locale/{textdomain}.*.tr", recursive=True):
parts = os.path.basename(name).split(".")
lang = parts[-2]
if lang not in allowed_languages:
continue
try:
ret.append(parse_tr(name))
except SyntaxError as e:

View File

@@ -24,6 +24,7 @@ from typing import Optional
import requests
import urllib3
from app import app
from sqlalchemy import or_, and_
from app.markdown import get_links, render_markdown
@@ -44,7 +45,7 @@ def update_package_scores():
def desc_contains(desc: str, search_str: str):
if search_str.startswith("https://forum.minetest.net/viewtopic.php?%t="):
if search_str.startswith("https://forum.luanti.org/viewtopic.php?%t="):
reg = re.compile(search_str.replace(".", "\\.").replace("/", "\\/").replace("?", "\\?").replace("%", ".*"))
return reg.search(desc)
else:
@@ -57,7 +58,7 @@ def notify_about_git_forum_links():
.filter(Package.repo.is_not(None), Package.state == PackageState.APPROVED).all()]
for pair in db.session.query(Package, Package.forums) \
.filter(Package.forums.is_not(None), Package.state == PackageState.APPROVED).all():
package_links.append((pair[0], f"https://forum.minetest.net/viewtopic.php?%t={pair[1]}"))
package_links.append((pair[0], f"https://forum.luanti.org/viewtopic.php?%t={pair[1]}"))
clauses = [and_(Package.id != pair[0].id, Package.desc.ilike(f"%{pair[1]}%")) for pair in package_links]
packages = Package.query.filter(Package.desc != "", Package.desc.is_not(None), Package.state == PackageState.APPROVED, or_(*clauses)).all()
@@ -110,12 +111,15 @@ def clear_removed_packages(all_packages: bool):
def _url_exists(url: str) -> str:
try:
headers = {
"User-Agent": "Mozilla/5.0 (compatible; ContentDB link checker; +https://content.minetest.net/)",
"User-Agent": "Mozilla/5.0 (compatible; ContentDB link checker; +https://content.luanti.org/)",
}
with requests.get(url, stream=True, headers=headers, timeout=10) as response:
response.raise_for_status()
return ""
except requests.exceptions.HTTPError as e:
if e.response.status_code == 403:
return ""
print(f" - [{e.response.status_code}] <{url}>", file=sys.stderr)
return str(e.response.status_code)
except requests.exceptions.ConnectionError:
@@ -125,6 +129,8 @@ def _url_exists(url: str) -> str:
def _check_for_dead_links(package: Package) -> dict[str, str]:
ignored_urls = set(app.config.get("LINK_CHECKER_IGNORED_URLS", ""))
links: set[Optional[str]] = {
package.repo,
package.website,
@@ -150,6 +156,9 @@ def _check_for_dead_links(package: Package) -> dict[str, str]:
if url.scheme != "http" and url.scheme != "https":
continue
if url.hostname in ignored_urls:
continue
res = _url_exists(link)
if res != "":
bad_urls[link] = res
@@ -180,7 +189,7 @@ def check_package_on_submit(package_id: int):
msg = _check_package(package)
if msg:
marked = f"Marked {package.title} as Changed Needed"
marked = f"Marked {package.title} as {PackageState.CHANGES_NEEDED.value}"
system_user = get_system_user()
post_to_approval_thread(package, system_user, marked, is_status_update=True, create_thread=True)

View File

@@ -25,10 +25,13 @@ from app.tasks import celery
@celery.task()
def post_discord_webhook(username: Optional[str], content: str, is_queue: bool, title: Optional[str] = None, description: Optional[str] = None, thumbnail: Optional[str] = None):
discord_url = app.config.get("DISCORD_WEBHOOK_QUEUE" if is_queue else "DISCORD_WEBHOOK_FEED")
if discord_url is None:
discord_urls = app.config.get("DISCORD_WEBHOOK_QUEUE" if is_queue else "DISCORD_WEBHOOK_FEED")
if discord_urls is None:
return
if isinstance(discord_urls, str):
discord_urls = [discord_urls]
json = {
"content": content[0:2000],
}
@@ -52,7 +55,8 @@ def post_discord_webhook(username: Optional[str], content: str, is_queue: bool,
json["embeds"] = [embed]
res = requests.post(discord_url, json=json, headers={"Accept": "application/json"})
if not res.ok:
raise Exception(f"Failed to submit Discord webhook {res.json}")
res.raise_for_status()
for url in discord_urls:
res = requests.post(url, json=json, headers={"Accept": "application/json"})
if not res.ok:
raise Exception(f"Failed to submit Discord webhook {res.json}")
res.raise_for_status()

View File

@@ -4,7 +4,7 @@
{% if version %}
Edit {{ version.name }}
{% else %}
New Minetest Version
New Luanti Version
{% endif %}
{% endblock %}

View File

@@ -1,13 +1,13 @@
{% extends "base.html" %}
{% block title %}
{{ _("Minetest Versions") }}
{{ _("Luanti Versions") }}
{% endblock %}
{% block content %}
<a class="btn btn-primary float-end" href="{{ url_for('admin.create_edit_version') }}">{{ _("New Version") }}</a>
<h1>{{ _("Minetest Versions") }}</h1>
<h1>{{ _("Luanti Versions") }}</h1>
<div class="list-group">
{% for v in versions %}

View File

@@ -252,14 +252,14 @@
<footer class="my-5 pt-5">
<p class="pt-3 mb-1">
ContentDB &copy; 2018-23 to <a href="{{ url_for('flatpage', path='about') }}">rubenwardy</a>
ContentDB &copy; 2018-24 to <a href="{{ url_for('flatpage', path='about') }}">rubenwardy</a>
</p>
<ul class="list-inline my-1">
<li class="list-inline-item"><a href="{{ url_for('flatpage', path='help') }}">{{ _("Help") }}</a></li>
<li class="list-inline-item"><a href="{{ url_for('flatpage', path='about') }}">{{ _("About") }}</a></li>
<li class="list-inline-item"><a href="{{ url_for('flatpage', path='help/contact_us') }}">{{ _("Contact Us") }}</a></li>
<li class="list-inline-item"><a href="{{ url_for('flatpage', path='rules') }}">{{ _("Rules") }}</a></li>
<li class="list-inline-item"><a href="{{ url_for('flatpage', path='terms') }}">{{ _("Terms of Service") }}</a></li>
<li class="list-inline-item"><a href="{{ url_for('flatpage', path='policy_and_guidance') }}">{{ _("Policy and Guidance") }}</a></li>
<li class="list-inline-item"><a href="{{ url_for('donate.donate') }}#contentdb">{{ _("Donate") }}</a></li>
<li class="list-inline-item"><a href="{{ url_for('flatpage', path='help/api') }}">{{ _("API") }}</a></li>
@@ -285,9 +285,11 @@
<input type="submit" class="btn btn-sm btn-secondary" value="{{ _('Hide non-free packages') }}">
{% endif %}
</form>
<p class="text-warning">
{{ _("Our privacy policy has been updated (%(date)s)", date="2024-04-30") }}
</p>
{% if false %}
<p class="text-warning">
{{ _("Our privacy policy has been updated (%(date)s)", date="2024-04-30") }}
</p>
{% endif %}
{% if debug %}
<p style="color: red">

View File

@@ -5,7 +5,7 @@
{% endblock %}
{% block description %}
{{ _("Welcome to the best place to find Minetest mods, games, and texture packs") }}
{{ _("Welcome to the best place to find Luanti mods, games, and texture packs") }}
{% endblock %}
{% block scriptextra %}
@@ -13,10 +13,10 @@
{
"@context": "https://schema.org",
"@type": "WebSite",
"url": "https://content.minetest.net/",
"url": "https://content.luanti.org/",
"potentialAction": {
"@type": "SearchAction",
"target": "https://content.minetest.net/packages?q={search_term_string}",
"target": "https://content.luanti.org/packages?q={search_term_string}",
"query-input": "required name=search_term_string"
}
}

View File

@@ -2,7 +2,7 @@
<script src="/static/libs/chart.min.js"></script>
<script src="/static/libs/chartjs-adapter-date-fns.bundle.min.js"></script>
<script src="/static/libs/chartjs-plugin-annotation.min.js"></script>
<script src="/static/js/package_charts.js?v=2"></script>
<script src="/static/js/package_charts.js?v=3"></script>
{% endmacro %}
@@ -103,10 +103,10 @@
<h3 class="mt-5">{{ _("Downloads by Reason") }}</h3>
<ul>
<li>{{ _("<b>New Install</b>: the user clicked [Install] inside of Minetest.") }}</li>
<li>{{ _("<b>New Install</b>: the user clicked [Install] inside of Luanti.") }}</li>
<li>{{ _("<b>Dependency</b>: was installed automatically to fulfill a dependency.") }}</li>
<li>{{ _("<b>Update</b>: download was to update the package.") }}</li>
<li>{{ _("<b>Other / Unknown</b>: downloaded by a web browser or an outdated Minetest version (before 5.5).") }}</li>
<li>{{ _("<b>Other / Unknown</b>: downloaded by a web browser or an outdated Luanti version (before 5.5).") }}</li>
</ul>
<p class="text-muted">
{{ _("This is a stacked area graph. For total downloads, look at the combined height.") }}
@@ -118,6 +118,12 @@
</div>
</div>
<h3 class="mt-5">{{ _("Views inside Luanti") }}</h3>
<p>
{{ _("Number of package page views inside the Luanti client. v5.10 and later only.") }}
</p>
<canvas id="chart-views" class="chart"></canvas>
<h3 style="margin-top: 6em;">{{ _("Need more stats?") }}</h3>
<p>
{{ _("Check out the ContentDB Grafana dashboard for CDB-wide stats") }}

View File

@@ -14,7 +14,7 @@
[{{ topic.type.text }}]
</td>
<td>
<a href="https://forum.minetest.net/viewtopic.php?t={{ topic.topic_id}}">{{ topic.title }}</a>
<a href="https://forum.luanti.org/viewtopic.php?t={{ topic.topic_id}}">{{ topic.title }}</a>
{% if topic.wip %}[{{ _("WIP") }}]{% endif %}
</td>
{% if show_author %}
@@ -42,7 +42,7 @@
{% macro render_topics(topics, current_user) -%}
<div class="list-group">
{% for topic in topics %}
<a class="list-group-item list-group-item-action" href="https://forum.minetest.net/viewtopic.php?t={{ topic.topic_id}}">
<a class="list-group-item list-group-item-action" href="https://forum.luanti.org/viewtopic.php?t={{ topic.topic_id}}">
<span class="float-end text-muted">
{{ topic.created_at | date }}
</span>

View File

@@ -23,7 +23,7 @@
{% for t in similar_topics %}
<li>
[{{ t.type.text }}]
<a href="https://forum.minetest.net/viewtopic.php?t={{ t.topic_id }}">
<a href="https://forum.luanti.org/viewtopic.php?t={{ t.topic_id }}">
{{ _("%(title)s by %(display_name)s", title=t.title, display_name=t.author.display_name) }}
</a>
{% if t.wip %}[{{ _("WIP") }}]{% endif %}

View File

@@ -64,8 +64,8 @@
<form method="POST" action="" enctype="multipart/form-data">
{{ form.hidden_tag() }}
{{ render_field(form.title) }}
{{ render_field(form.description, hint=_("Shown to users when you request access to their account")) }}
{{ render_field(form.title, hint=_("Titles must be globally unique. For example, what's the name of your application?")) }}
{{ render_field(form.description, hint=_("Shown to users when you request access to their account. For example, what does your application do?")) }}
{{ render_field(form.redirect_url) }}
{{ render_field(form.app_type, hint=_("Where will you store your client_secret?")) }}

View File

@@ -137,7 +137,7 @@
{{ render_field(form.issueTracker, class_="pkg_meta", hint=_("Where should users report issues?")) }}
{{ render_field_prefix_button(form.forums, class_="pkg_meta",
pattern="[0-9]+",
prefix="forum.minetest.net/viewtopic.php?t=",
prefix="forum.luanti.org/viewtopic.php?t=",
placeholder=_("Paste a forum topic URL"),
has_view=True) }}
{{ render_field(form.video_url, class_="pkg_meta", hint=_("YouTube videos will be shown in an embed.")) }}

View File

@@ -51,7 +51,7 @@
{% endif %}
{% endif %}
<h3 class="mt-5">{{ _("Supported Minetest versions") }}</h3>
<h3 class="mt-5">{{ _("Supported Luanti versions") }}</h3>
<div class="row">
{{ render_field(form.min_rel, class_="col-sm-6") }}
@@ -67,12 +67,12 @@
<strong>
{{ _("Are you sure your package doesn't work on versions after %(version)s?", version=last.label) }}
</strong>
{{ _("Only set the maximum version if you know that it doesn't work on newer Minetest versions.") }}
{{ _("Only set the maximum version if you know that it doesn't work on newer Luanti versions.") }}
{{ _("Don't set the maximum version just because you haven't tested it on newer versions.") }}
<p>
<p>
{{ _("Set the minimum and maximum Minetest versions supported.
{{ _("Set the minimum and maximum Luanti versions supported.
This release will be hidden to clients outside of that range. ") }}
<br />
{{ _("Leave both as None if in doubt.") }}

View File

@@ -59,7 +59,7 @@
tips on customising releases.") }}
</p>
<h3 class="mt-5">{{ _("3. Supported Minetest versions") }}</h3>
<h3 class="mt-5">{{ _("3. Supported Luanti versions") }}</h3>
<div class="row">
{{ render_field(form.min_rel, class_="col-sm-6") }}
@@ -75,7 +75,7 @@
<strong>
{{ _("Are you sure your package doesn't work on versions after %(version)s?", version=last.label) }}
</strong>
{{ _("Only set the maximum version if you know that it doesn't work on newer Minetest versions.") }}
{{ _("Only set the maximum version if you know that it doesn't work on newer Luanti versions.") }}
{{ _("Don't set the maximum version just because you haven't tested it on newer versions.") }}
<p>
@@ -86,7 +86,7 @@
</p>
<p>
{{ _("Set the minimum and maximum Minetest versions supported.
{{ _("Set the minimum and maximum Luanti versions supported.
This release will be hidden to clients outside of that range. ") }}
<br>
{{ _("Leave both as None if in doubt.") }}

View File

@@ -12,7 +12,7 @@
<p>
{{ _("A release is a single downloadable version of your %(title)s.", title=package.type.text.lower()) }}
{{ _("You need to create releases even if you use a rolling release development cycle, as Minetest needs them to check for updates.") }}
{{ _("You need to create releases even if you use a rolling release development cycle, as Luanti needs them to check for updates.") }}
</p>
{% if package.repo %}

View File

@@ -7,6 +7,7 @@
{% block content %}
<h2 class="mt-0">{{ self.title() }}</h2>
{% if package.approved %}
<form method="POST" action="">
<h3>{{ _("Change maintenance state") }}</h3>
<p>
@@ -29,6 +30,7 @@
{% endfor %}
</p>
</form>
{% endif %}
<form method="POST" action="" class="mt-5">
<h3>{{ _("Remove") }}</h3>
@@ -44,6 +46,11 @@
{{ _("Unapproving a package will put it back into Draft, where
it can be submitted for approval again.") }}
</p>
{% else %}
<p>
<strong>{{ _("You don't need to delete a package just to change something.") }}</strong>
{{ _("Click 'Edit' at the top right of the package page.") }}
</p>
{% endif %}
{% if hard_deps %}

View File

@@ -17,7 +17,7 @@
<h1>{{ _("Post a review for %(title)s by %(author)s", title=self.link(), author=package.author.display_name) }}</h1>
<p class="alert alert-primary">
{{ _("Please make sure you read ContentDB's <a href='/rules/'>rules</a>") }}
{{ _("Please make sure you read ContentDB's <a href='/terms/'>Terms of Service</a>") }}
</p>
{% if package.issueTracker %}

View File

@@ -34,7 +34,7 @@
{% for t in similar_topics %}
<li>
[{{ t.type.value }}]
<a href="https://forum.minetest.net/viewtopic.php?t={{ t.topic_id }}">
<a href="https://forum.luanti.org/viewtopic.php?t={{ t.topic_id }}">
{{ _("%(title)s by %(display_name)s", title=t.title, display_name=t.author.display_name) }}
</a>
{% if t.wip %}[{{ _("WIP") }}]{% endif %}

View File

@@ -20,24 +20,12 @@
</p>
<p>
<a class="btn btn-primary me-2" href="https://rubenwardy.com/minetest_modding_book/en/quality/translations.html">
{{ _("Translation - Minetest Modding Book") }}
{{ _("Translation - Luanti Modding Book") }}
</a>
<a class="btn btn-primary" href="https://api.minetest.net/translations/#translating-content-meta">
{{ _("Translating content meta - lua_api.md") }}
</a>
</p>
<h3 id="template">{{ _("Translation template") }}</h3>
<p>
{{ _("To quickly add support for ContentDB package translation, create a file at %(location)s with the following content:",
location="<code>locale/template.txt</code>"|safe) }}
</p>
<pre><code># textdomain: {{ package.name }}
{{ package.title | replace("@", "@@") | replace("=", "@=") }} =
{{ package.short_desc | replace("@", "@@") | replace("=", "@=") }} =
</code></pre>
{% else %}
<p>
{{ _("%(title)s is available in %(num)d languages.", title=package.title, num=num) }}
@@ -89,5 +77,34 @@
</div>
{% endfor %}
{% endif %}
{% if not has_content_translations %}
{% set translation_template_path %}
{% if package.type == package.type.GAME %}
<code>mods/mymod/locale/template.txt</code>
{% else %}
<code>locale/template.txt</code>
{% endif %}
{% endset %}
<h3 id="template">{{ _("Translation template") }}</h3>
<p>
{{ _("To quickly add support for ContentDB package translation, create a file at %(location)s with the following content:",
location=translation_template_path) }}
</p>
<pre><code># textdomain: {{ package.name }}
{{ package.title | replace("@", "@@") | replace("=", "@=") }} =
{{ package.short_desc | replace("@", "@@") | replace("=", "@=") }} =
</code></pre>
{% if package.type == package.type.GAME %}
<p>{{ _("With games, you also need to name the textdomain in game.conf:") }}</p>
<pre><code>textdomain = mymod</code></pre>
<p>{{ _("Replace mymod with the name of mod / textdomain you chose.") }}</p>
{% endif %}
{% endif %}
</div>
{% endblock %}

View File

@@ -53,11 +53,11 @@
{% if release and (release.min_rel or release.max_rel) %}
<small class="count display-block">
{% if release.min_rel and release.max_rel %}
{{ _("Minetest %(min)s - %(max)s", min=release.min_rel.name, max=release.max_rel.name) }}
{{ _("Luanti %(min)s - %(max)s", min=release.min_rel.name, max=release.max_rel.name) }}
{% elif release.min_rel %}
{{ _("For Minetest %(min)s and above", min=release.min_rel.name) }}
{{ _("For Luanti %(min)s and above", min=release.min_rel.name) }}
{% elif release.max_rel %}
{{ _("Minetest %(max)s and below", max=release.max_rel.name) }}
{{ _("Luanti %(max)s and below", max=release.max_rel.name) }}
{% endif %}
</small>
{% endif %}

View File

@@ -40,19 +40,6 @@
</div>
</div>
{% if allow_private_change %}
{{ render_checkbox_field(form.private, class_="my-3") }}
{% elif form.private.data %}
<p>
Private.
</p>
{% endif %}
{% if allow_private_change or form.private.data %}
<p>
{{ _("Only you, the package author, and users of Approver rank and above can read private threads.") }}
</p>
{% endif %}
{{ render_submit_field(form.btn_submit) }}
</form>

View File

@@ -5,7 +5,7 @@
{%- endblock %}
{% block description -%}
{{ _("Help make Minetest more accessible by translating packages into other languages.") }}
{{ _("Help make Luanti more accessible by translating packages into other languages.") }}
{% endblock %}
{% macro render_packages(packages) %}

View File

@@ -5,7 +5,7 @@
{% endblock %}
{% block ruben_link %}
<a href="https://forum.minetest.net/ucp.php?i=pm&mode=compose&u=2051">rubenwardy</a>
<a href="https://forum.luanti.org/ucp.php?i=pm&mode=compose&u=2051">rubenwardy</a>
{% endblock %}
{% block pane %}
@@ -36,7 +36,7 @@
<td>Forums</td>
<td>
{% if user.forums_username %}
<a href="https://forum.minetest.net/memberlist.php?mode=viewprofile&un={{ user.forums_username }}">
<a href="https://forum.luanti.org/memberlist.php?mode=viewprofile&un={{ user.forums_username }}">
{{ user.forums_username }}
</a>
{% else %}
@@ -59,12 +59,11 @@
{{ _("View ContentDB's GitHub Permissions") }}
</a>
{% endif %}
{% if user.forums_username %}
<form method="post" action="{{ url_for('users.disconnect_github', username=user.username) }}" class="d-inline-block">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="submit" class="btn btn-secondary" value="{{ _('Disconnect') }}" />
</form>
{% endif %}
<form method="post" action="{{ url_for('users.disconnect_github', username=user.username) }}" class="d-inline-block">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="submit" class="btn btn-secondary" value="{{ _('Disconnect') }}" />
</form>
{% elif user == current_user %}
<a class="btn btn-secondary" href="{{ url_for('vcs.github_start') }}">
{{ _("Link Github") }}
@@ -85,11 +84,13 @@
{% else %}
<p>
{{ _("Account Deletion and Deactivation isn't available to users yet.") }}
{{ _("Please contact the admin.") }}
{{ _("Please raise a report to request account deletion.") }}
</p>
<p>
<a class="btn btn-secondary" href="{{ url_for('report.report', url=url_current(), message="Delete my account") }}">{{ _("Report") }}</a>
</p>
{% endif %}
<h3 class="mt-5">{{ _("Recent Account Actions") }}</h3>
{% from "macros/audit_log.html" import render_audit_log %}

View File

@@ -15,6 +15,9 @@
{{ render_field(form.email, tabindex=220,
hint=_("Your email is needed to recover your account if you forget your password and to send (configurable) notifications. ") +
_("Your email will never be shared with a third-party.")) }}
<p>
{{ _("Note: protonmail is unsupported by ContentDB. <a href='https://forum.luanti.org/viewtopic.php?t=30709'>More info</a>.") }}
</p>
{% endif %}
{% if form.old_password %}

View File

@@ -7,7 +7,7 @@
{% block content %}
<h1>{{ self.title() }}</h1>
<h2>{{ _("Do you have an account on the Minetest Forums?") }}</h2>
<h2>{{ _("Do you have an account on the Luanti Forums?") }}</h2>
<p>
{{ _("ContentDB will link your account to your forum account if you have one, but you don't need one.") }}
@@ -20,7 +20,7 @@
<a class="btn btn-primary me-3" href="{{ url_for('users.register') }}">
{{ _("<b>No</b>, I don't have one") }}
</a>
<a class="btn btn-secondary" href="https://forum.minetest.net/ucp.php?mode=register">
<a class="btn btn-secondary" href="https://forum.luanti.org/ucp.php?mode=register">
{{ _("Create forum account") }}
</a>
</p>

View File

@@ -19,6 +19,10 @@ Create Account from Forums User
{{ _("You can still <a href='%(url)s'>sign up without one</a>.", url=url_for('users.register')) }}
</p>
<p>
{{ _("By signing up, you agree to the <a href='/terms/' target='_blank'>Terms of Service</a> and <a href='/privacy_policy/' target='_blank'>Privacy Policy</a>.") }}
</p>
<div class="row mt-5">
<div class="col-sm-6">
<div class="card">
@@ -41,7 +45,7 @@ Create Account from Forums User
<p>
{{ _("You'll need to have the GitHub field in your forum profile filled out.") }}
{{ _("Log into the forum and <a href='https://forum.minetest.net/ucp.php?i=173'>do that here</a>.") }}
{{ _("Log into the forum and <a href='https://forum.luanti.org/ucp.php?i=173'>do that here</a>.") }}
</p>
<input class="btn btn-primary" type="submit" value="{{ _('Next: log in with GitHub') }}">
@@ -68,7 +72,7 @@ Create Account from Forums User
placeholder="{{ _('Forum username') }}" pattern="[a-zA-Z0-9._ -]+" title="{{ _('Only a-zA-Z0-9._ allowed') }}" required>
<p>
{{ _("Go to <a href='https://forum.minetest.net/ucp.php?i=profile&mode=signature'>User Control Panel &gt; Profile &gt; Edit signature</a>") }}
{{ _("Go to <a href='https://forum.luanti.org/ucp.php?i=profile&mode=signature'>User Control Panel &gt; Profile &gt; Edit signature</a>") }}
</p>
<p>

View File

@@ -71,7 +71,7 @@
</span>
{% if user.forums_username %}
<a class="btn" href="https://forum.minetest.net/memberlist.php?mode=viewprofile&un={{ user.forums_username }}">
<a class="btn" href="https://forum.luanti.org/memberlist.php?mode=viewprofile&un={{ user.forums_username }}">
<i class="fas fa-comments"></i>
<span class="count">
{{ _("Forums") }}

View File

@@ -11,7 +11,7 @@
<div class="row">
<div class="col-md-2">
{% if user.forums_username %}
<a href="https://forum.minetest.net/ucp.php?i=profile&mode=avatar">
<a href="https://forum.luanti.org/ucp.php?i=profile&mode=avatar">
{% elif user.email %}
<a href="https://en.gravatar.com/">
{% endif %}

View File

@@ -23,6 +23,9 @@
{{ render_field(form.email,
hint=_("Your email is needed to recover your account if you forget your password and to send (configurable) notifications. ") +
_("Your email will never be shared with a third-party.")) }}
<p>
{{ _("Note: protonmail is unsupported by ContentDB. <a href='https://forum.luanti.org/viewtopic.php?t=30709'>More info</a>.") }}
</p>
{{ render_field(form.password, hint=_("Must be at least 12 characters long.")) }}
@@ -31,15 +34,10 @@
</p>
{{ render_field(form.question, hint=_("Please prove that you are human")) }}
{% set label %}
{{ _("I agree to the ") }}
<a href="{{ url_for('flatpage', path='privacy_policy') }} ">
{{ _("Privacy Policy") }}
</a>
{% endset %}
{{ render_checkbox_field(form.agree, label=label, class_="my-4") }}
<p>
{{ _("By signing up, you agree to the <a href='/terms/' target='_blank'>Terms of Service</a> and <a href='/privacy_policy/' target='_blank'>Privacy Policy</a>.") }}
</p>
{# Submit button #}
<p>
{{ render_submit_field(form.submit, tabindex=180) }}
</p>

View File

@@ -23,6 +23,9 @@
{{ _("Your email is needed to recover your account if you forget your password, and to send (configurable) notifications.") }}
{{ _("Your email will never be shared with a third-party.") }}
</p>
<p>
{{ _("Note: protonmail is unsupported by ContentDB. <a href='https://forum.luanti.org/viewtopic.php?t=30709'>More info</a>.") }}
</p>
{% if user.email_verifications.filter_by(is_password_reset=False).count() > 0 %}
<p>

View File

@@ -217,14 +217,6 @@ def test_cycle_fails_safely():
"""
A dependency cycle shouldn't completely break the graph if a mod is
available elsewhere
a -> d
game has d
cycle:
d -> b
b -> c
c -> b
"""
support = GameSupport()
support.add(make_game("game1", ["default", "mod_d"]))
@@ -249,68 +241,6 @@ def test_cycle_fails_safely():
}
def test_cycle_not_fulfill_with_conflict():
"""
Test that cycles aren't fulfilled by installing a mod multiple times, which would conflict
a -> b -> a
game1 has a
b should be {game1}
a should be unfulfilled
"""
support = GameSupport()
support.add(make_game("game1", ["default", "mod_a"]))
modB = support.add(make_mod("mod_b", ["mod_b"], ["mod_a"]))
modA = support.add(make_mod("mod_a", ["mod_a"], ["mod_b"]))
support.on_first_run()
assert modB.is_confirmed
assert modB.detected_supported_games == {"game1"}
# Can't install mod_a and game1 at the same time
assert not modA.is_confirmed
assert modA.detected_supported_games == {}
assert support.all_errors == {
"author/mod_a: Dependency cycle detected: author/mod_b -> author/mod_a -> author/mod_b",
"author/mod_b: Dependency cycle detected: author/mod_b -> author/mod_a -> author/mod_b",
}
def test_cycle_not_fulfill_with_conflict2():
"""
Test that cycles aren't fulfilled by installing a mod multiple times, which would conflict
a -> b -> a
game1 has a
b should be {game1}
a should be unfulfilled
"""
support = GameSupport()
support.add(make_game("game1", ["default"]))
modB = support.add(make_mod("mod_b", ["mod_b"], ["mod_a"]))
modA2 = support.add(make_mod("mod_a", ["mod_a"], ["default"]))
modA = support.add(make_mod("mod_a", ["mod_a"], ["mod_b"]))
support.on_first_run()
assert modB.is_confirmed
assert modB.detected_supported_games == {"game1"}
assert modA2.is_confirmed
assert modA2.detected_supported_games == {"game1"}
# Can't install modA and modA2 at the same time
assert not modA.is_confirmed
assert modA.detected_supported_games == {}
assert support.all_errors == {
"author/mod_a: Dependency cycle detected: author/mod_b -> author/mod_a -> author/mod_b",
"author/mod_b: Dependency cycle detected: author/mod_b -> author/mod_a -> author/mod_b",
}
def test_update():
"""
Test updating a mod will update mods that depend on it

View File

@@ -0,0 +1,30 @@
# ContentDB
# Copyright (C) rubenwardy
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from app.utils.version import is_minetest_v510
def test_is_minetest_v510():
assert not is_minetest_v510("Minetest/5.9.1 (Windows/10.0.22621 x86_64)")
assert not is_minetest_v510("Minetest/")
assert not is_minetest_v510("Minetest/5.9.1")
assert is_minetest_v510("Minetest/5.10.0")
assert is_minetest_v510("Minetest/5.10.1")
assert is_minetest_v510("Minetest/5.11.0")
assert is_minetest_v510("Minetest/5.10")
assert not is_minetest_v510("Minetest/6.12")

View File

@@ -56,7 +56,7 @@ def normalize_line_endings(value: Optional[str]) -> Optional[str]:
if value is None:
return None
return value.replace("\r\n", "\n").strip()
return value.replace("\r\n", "\n")
def should_return_json():

View File

@@ -322,15 +322,28 @@ def package_reviews_as_hypertext(package: Package, formspec_version: int = 7):
links[f"link_{link_counter}"] = url
return f"<action name=link_{link_counter}>{escape_hypertext(label)}</action>"
for review in package.reviews:
body += make_link(package.get_url("packages.review", absolute=True), gettext("Leave a review"))
body += "\n\n"
reviews = package.reviews.all()
for review in reviews:
review: PackageReview
html = render_markdown(review.thread.first_reply.comment)
content = html_to_minetest(html, package.get_url("packages.view", absolute=True),
formspec_version, False, f"review_{review.id}_")["body"].strip()
formspec_version, False, f"review_{review.id}_")
links.update(content["links"])
comment_body = content["body"].rstrip()
author = make_link(abs_url_for("users.profile", username=review.author.username), review.author.display_name)
rating = ["👎", "👎", "-", "👍", "👍"][review.rating - 1]
comments = make_link(abs_url_for("threads.view", id=review.thread.id), "Comments")
body += f"{author} {review.rating}\n<big>{escape_hypertext(review.thread.title)}</big>\n{content}\n{comments}\n\n"
rating = ["<thumbsdown>", "<thumbsdown>", "<neutral>", "<thumbsup>", "<thumbsup>"][review.rating - 1]
num_comments = review.thread.replies.count()
comments = make_link(abs_url_for("threads.view", id=review.thread.id), f"Comments [{num_comments}]")
positive, negative, _ = review.get_totals()
helpful = f"Review helpfulness: +{positive} / -{negative}"
body += f"{author} {rating}\n<big>{escape_hypertext(review.thread.title)}</big>\n{comment_body}\n{comments}{helpful}\n\n"
if len(reviews) == 0:
body += escape_hypertext(gettext("No reviews available."))
return {
"head": HEAD,

View File

@@ -124,7 +124,7 @@ def parse_forum_list_page(id, page, out, extra=None):
start = page*num_per_page+1
print(" - Fetching page {} (topics {}-{})".format(page, start, start+num_per_page), file=sys.stderr)
url = "https://forum.minetest.net/viewforum.php?f=" + str(id) + "&start=" + str(start)
url = "https://forum.luanti.org/viewforum.php?f=" + str(id) + "&start=" + str(start)
r = urllib.request.urlopen(url).read().decode("utf-8")
soup = BeautifulSoup(r, "html.parser")

29
app/utils/version.py Normal file
View File

@@ -0,0 +1,29 @@
# ContentDB
# Copyright (C) rubenwardy
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
def is_minetest_v510(user_agent: str) -> bool:
parts = user_agent.split(" ")
version = parts[0].split("/")[1]
try:
digits = list(map(lambda x: int(x), version.split(".")))
except ValueError:
return False
if len(digits) < 2:
return False
return digits[0] == 5 and digits[1] >= 10

View File

@@ -37,6 +37,7 @@ TEMPLATES_AUTO_RELOAD = False
LOG_SQL = False
BLOCKED_DOMAINS = []
LINK_CHECKER_IGNORED_URLS = ["liberapay.com"]
ADMIN_CONTACT_URL = ""
MONITORING_URL = None

View File

@@ -3,4 +3,4 @@
This folder only contains technical documentation for those interested in the ContentDB source code.
Documentation for using ContentDB, whether through the interface or API, is available at
<https://content.minetest.net/help/>.
<https://content.luanti.org/help/>.

View File

@@ -6,14 +6,14 @@ or for implementing ContentDB compatible servers.
## Package List API call
The client makes a single [API](https://content.minetest.net/help/api/) request to `/api/packages/`.
The client makes a single [API](https://content.luanti.org/help/api/) request to `/api/packages/`.
The query arguments will include a list of supported types, the current
[engine version](https://content.minetest.net/api/minetest_versions/),
and any hidden [Content Flags](https://content.minetest.net/help/content_flags/).
[engine version](https://content.luanti.org/api/minetest_versions/),
and any hidden [Content Flags](https://content.luanti.org/help/content_flags/).
Example URL:
<https://content.minetest.net/api/packages/?type=mod&type=game&type=txp&protocol_version=39&engine_version=5.3.0&hide=nonfree&hide=desktop_default>
<https://content.luanti.org/api/packages/?type=mod&type=game&type=txp&protocol_version=39&engine_version=5.3.0&hide=nonfree&hide=desktop_default>
Example response:
@@ -24,7 +24,7 @@ Example response:
"name": "nodecore",
"release": 1234,
"short_description": "A short description",
"thumbnail": "https://content.minetest.net/thumbnails/1/abcdef.jpg",
"thumbnail": "https://content.luanti.org/thumbnails/1/abcdef.jpg",
"title": "NodeCore",
"type": "game"
}
@@ -50,7 +50,7 @@ The client can simply download the URL mentioned in `thumbnail`.
The client downloads packages by constructing a URL for the release and downloading it:
```
https://content.minetest.net/packages/<author>/<name>/releases/<release>/download/
https://content.luanti.org/packages/<author>/<name>/releases/<release>/download/
```
This supports redirects.
@@ -104,5 +104,5 @@ response for Mobs Monster.
The client will open the package in a browser by constructing the following URL
```
https://content.minetest.net/packages/<author>/<name>/
https://content.luanti.org/packages/<author>/<name>/
```

View File

@@ -0,0 +1,28 @@
"""empty message
Revision ID: d52f6901b707
Revises: daa040b727b2
Create Date: 2024-10-22 21:18:23.929298
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'd52f6901b707'
down_revision = 'daa040b727b2'
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table('package_daily_stats', schema=None) as batch_op:
batch_op.add_column(sa.Column('views_minetest', sa.Integer(), nullable=False, server_default="0"))
batch_op.add_column(sa.Column('v510', sa.Integer(), nullable=False, server_default="0"))
def downgrade():
with op.batch_alter_table('package_daily_stats', schema=None) as batch_op:
batch_op.drop_column('views_minetest')
batch_op.drop_column('v510')

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More