Compare commits
125 Commits
game_suppo
...
view-stats
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26d44ba357 | ||
|
|
c21a56585f | ||
|
|
cd53696831 | ||
|
|
8777d2bfd3 | ||
|
|
00cf79224d | ||
|
|
5b0d42173f | ||
|
|
6891ee8b19 | ||
|
|
5f2b2ffdf1 | ||
|
|
f0e67c93d6 | ||
|
|
e5fd908b54 | ||
|
|
3af7a19563 | ||
|
|
e1f0792dce | ||
|
|
2e91656245 | ||
|
|
8378095343 | ||
|
|
b6f67d4b0e | ||
|
|
ffe808c915 | ||
|
|
6169f4c0e4 | ||
|
|
8c5e542268 | ||
|
|
893b902314 | ||
|
|
2435e8e3d0 | ||
|
|
628d44460d | ||
|
|
fc1b7e500d | ||
|
|
7cfbbbe7e6 | ||
|
|
bea743b536 | ||
|
|
6ccc575cb4 | ||
|
|
9e4be57754 | ||
|
|
82b47628ae | ||
|
|
3bd62f4184 | ||
|
|
9a98c2f6c2 | ||
|
|
73c1706e6a | ||
|
|
649ee8bcd6 | ||
|
|
37e8f2dc28 | ||
|
|
4d9628a156 | ||
|
|
0ff9a3838e | ||
|
|
24eacb191d | ||
|
|
23e9ad6ef5 | ||
|
|
c7f26f706d | ||
|
|
699eabef80 | ||
|
|
73376194e0 | ||
|
|
aafa56df95 | ||
|
|
978c5d9704 | ||
|
|
c332e8f940 | ||
|
|
f116259f6a | ||
|
|
3f9902b001 | ||
|
|
a627276ab4 | ||
|
|
f72a66816a | ||
|
|
508f7d7e2b | ||
|
|
b86d372bd2 | ||
|
|
c75fd51626 | ||
|
|
6ad12288c3 | ||
|
|
af2543a99e | ||
|
|
2c61032d15 | ||
|
|
a54104aa82 | ||
|
|
dd2e73b40f | ||
|
|
a5ac4f38cf | ||
|
|
2ff11dec0a | ||
|
|
8e1547ca3b | ||
|
|
757e182d1b | ||
|
|
5562ca6039 | ||
|
|
74cf577245 | ||
|
|
79387309d8 | ||
|
|
e4b81feb5c | ||
|
|
58ac57e098 | ||
|
|
abc2941756 | ||
|
|
1432384b63 | ||
|
|
52df207088 | ||
|
|
7f834dbf8c | ||
|
|
9131b29b48 | ||
|
|
f621cd13d2 | ||
|
|
69904dbe81 | ||
|
|
d56430c0f0 | ||
|
|
f69bc8fc1e | ||
|
|
5a173ee18b | ||
|
|
6429b2e26d | ||
|
|
93f36adfea | ||
|
|
25547c9f38 | ||
|
|
6425149d20 | ||
|
|
4738e11ed0 | ||
|
|
ae67f6ce79 | ||
|
|
c23f004d35 | ||
|
|
8effec2cbb | ||
|
|
5afc429c25 | ||
|
|
d5552ad517 | ||
|
|
65a14ffdf1 | ||
|
|
837d0b5bc1 | ||
|
|
5b1417f432 | ||
|
|
53a004c41c | ||
|
|
ac34939c99 | ||
|
|
9aa8886309 | ||
|
|
1166cca357 | ||
|
|
395d3dd16b | ||
|
|
009dfd07de | ||
|
|
ff07ff5b7f | ||
|
|
2b32cfe6fa | ||
|
|
b31e9e71b6 | ||
|
|
94bf1973a0 | ||
|
|
357dfe76e8 | ||
|
|
5da955b3a5 | ||
|
|
7584a867eb | ||
|
|
9c77212f4a | ||
|
|
2b62224a5b | ||
|
|
bb561104f8 | ||
|
|
bdd9ab6a29 | ||
|
|
d450d6bae3 | ||
|
|
02cc464098 | ||
|
|
563345eddd | ||
|
|
e5e3230a16 | ||
|
|
ed4d4c67d9 | ||
|
|
42df276e73 | ||
|
|
bd17080f2a | ||
|
|
2fb7ddaaee | ||
|
|
ef868f776c | ||
|
|
06979345c7 | ||
|
|
e5de270e65 | ||
|
|
031c3c4684 | ||
|
|
a9d31590e8 | ||
|
|
4c4a55872a | ||
|
|
9387db5f8d | ||
|
|
6a5c7d44bf | ||
|
|
f1ace7fce8 | ||
|
|
20c946d127 | ||
|
|
bb2a1f3638 | ||
|
|
e603f29b47 | ||
|
|
9c2ecd1e22 | ||
|
|
80c3416ca7 |
2
.github/SECURITY.md
vendored
2
.github/SECURITY.md
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -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/* .
|
||||
|
||||
14
Dockerfile
14
Dockerfile
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
|
||||
@@ -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'):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -4,7 +4,7 @@ toc: False
|
||||
|
||||
## Rules
|
||||
|
||||
* [Rules](/rules/)
|
||||
* [Terms of Service](/terms/)
|
||||
* [Package Inclusion Policy and Guidance](/policy_and_guidance/)
|
||||
|
||||
## General Help
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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") }}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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"
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
133
app/flatpages/terms.md
Normal 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.
|
||||
@@ -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, [])
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% if version %}
|
||||
Edit {{ version.name }}
|
||||
{% else %}
|
||||
New Minetest Version
|
||||
New Luanti Version
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -252,14 +252,14 @@
|
||||
|
||||
<footer class="my-5 pt-5">
|
||||
<p class="pt-3 mb-1">
|
||||
ContentDB © 2018-23 to <a href="{{ url_for('flatpage', path='about') }}">rubenwardy</a>
|
||||
ContentDB © 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">
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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") }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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?")) }}
|
||||
|
||||
|
||||
@@ -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.")) }}
|
||||
|
||||
@@ -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.") }}
|
||||
|
||||
@@ -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.") }}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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) %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 > Profile > Edit signature</a>") }}
|
||||
{{ _("Go to <a href='https://forum.luanti.org/ucp.php?i=profile&mode=signature'>User Control Panel > Profile > Edit signature</a>") }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
@@ -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") }}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
30
app/tests/unit/utils/test_version.py
Normal file
30
app/tests/unit/utils/test_version.py
Normal 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")
|
||||
@@ -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():
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
29
app/utils/version.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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/>.
|
||||
|
||||
@@ -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>/
|
||||
```
|
||||
|
||||
28
migrations/versions/d52f6901b707_.py
Normal file
28
migrations/versions/d52f6901b707_.py
Normal 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
5803
translations/be/LC_MESSAGES/messages.po
Normal file
5803
translations/be/LC_MESSAGES/messages.po
Normal file
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
Reference in New Issue
Block a user