Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5981ac5
WebHost: render setup guides and game info pages to html on server
Berserker66 Oct 20, 2024
fd1615f
WebHost: move render_markdown to top of file next to the other "libra…
Berserker66 Oct 20, 2024
1ee0302
Merge branch 'refs/heads/Archipelago_Main' into webhost_server_render…
Berserker66 Apr 6, 2025
70ce214
fix merge
Berserker66 Apr 6, 2025
50ae00a
remove old json index use werkzeug secure filename instead of Utils.g…
Berserker66 Apr 6, 2025
d172d0c
fix code blocks
Berserker66 Apr 6, 2025
b527587
fix theme defaulting to grass for games with spaces and fix underscor…
Berserker66 Apr 6, 2025
4831b4c
add smarty pants
Berserker66 Apr 6, 2025
9afcd08
fix test setup
Berserker66 Apr 6, 2025
f6d9e14
remove outdated test
Berserker66 Apr 6, 2025
702d165
Merge branch 'refs/heads/Archipelago_Main' into webhost_server_render…
Berserker66 Apr 17, 2025
cc05f66
re-remove gameinfo
Berserker66 Apr 17, 2025
501a4a3
nuke tutorialLanding.js
Berserker66 Apr 17, 2025
cbadc0b
Update WebHost.py
Berserker66 Apr 17, 2025
15d5a7f
fix sorting
Berserker66 Apr 17, 2025
7af8005
Update WebHostLib/misc.py
Berserker66 Apr 17, 2025
d846dc3
Merge branch 'main' into webhost_server_render_remaining_markdown
Berserker66 Apr 17, 2025
04512f9
Merge branch 'main' into webhost_server_render_remaining_markdown
Berserker66 May 3, 2025
2c66cad
Fix anchors
Berserker66 May 3, 2025
3573bc6
fix supported games -> guides section jump
Berserker66 May 3, 2025
f89cffe
use url_for for tutorial
Berserker66 May 3, 2025
bc40be1
Merge branch 'main' into webhost_server_render_remaining_markdown
Berserker66 Jul 28, 2025
0b1bf84
Merge branch 'main' into webhost_server_render_remaining_markdown
Berserker66 Aug 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1926,7 +1926,7 @@ class Tutorial(NamedTuple):
description: str
language: str
file_name: str
link: str
link: str # unused
authors: List[str]


Expand Down
48 changes: 8 additions & 40 deletions WebHost.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,15 @@ def get_app() -> "Flask":
return app


def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]:
import json
def copy_tutorials_files_to_static() -> None:
import shutil
import zipfile
from werkzeug.utils import secure_filename

zfile: zipfile.ZipInfo

from worlds.AutoWorld import AutoWorldRegister
worlds = {}
data = []
for game, world in AutoWorldRegister.world_types.items():
if hasattr(world.web, 'tutorials') and (not world.hidden or game == 'Archipelago'):
worlds[game] = world
Expand All @@ -72,7 +71,7 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]
shutil.rmtree(base_target_path, ignore_errors=True)
for game, world in worlds.items():
# copy files from world's docs folder to the generated folder
target_path = os.path.join(base_target_path, get_file_safe_name(game))
target_path = os.path.join(base_target_path, secure_filename(game))
os.makedirs(target_path, exist_ok=True)

if world.zip_path:
Expand All @@ -85,45 +84,14 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]
for zfile in zf.infolist():
if not zfile.is_dir() and "/docs/" in zfile.filename:
zfile.filename = os.path.basename(zfile.filename)
zf.extract(zfile, target_path)
with open(os.path.join(target_path, secure_filename(zfile.filename)), "wb") as f:
f.write(zf.read(zfile))
else:
source_path = Utils.local_path(os.path.dirname(world.__file__), "docs")
files = os.listdir(source_path)
for file in files:
shutil.copyfile(Utils.local_path(source_path, file), Utils.local_path(target_path, file))

# build a json tutorial dict per game
game_data = {'gameTitle': game, 'tutorials': []}
for tutorial in world.web.tutorials:
# build dict for the json file
current_tutorial = {
'name': tutorial.tutorial_name,
'description': tutorial.description,
'files': [{
'language': tutorial.language,
'filename': game + '/' + tutorial.file_name,
'link': f'{game}/{tutorial.link}',
'authors': tutorial.authors
}]
}

# check if the name of the current guide exists already
for guide in game_data['tutorials']:
if guide and tutorial.tutorial_name == guide['name']:
guide['files'].append(current_tutorial['files'][0])
break
else:
game_data['tutorials'].append(current_tutorial)

data.append(game_data)
with open(Utils.local_path("WebHostLib", "static", "generated", "tutorials.json"), 'w', encoding='utf-8-sig') as json_target:
generic_data = {}
for games in data:
if 'Archipelago' in games['gameTitle']:
generic_data = data.pop(data.index(games))
sorted_data = [generic_data] + Utils.title_sorted(data, key=lambda entry: entry["gameTitle"])
json.dump(sorted_data, json_target, indent=2, ensure_ascii=False)
return sorted_data
shutil.copyfile(Utils.local_path(source_path, file),
Utils.local_path(target_path, secure_filename(file)))


if __name__ == "__main__":
Expand All @@ -142,7 +110,7 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]
logging.warning("Could not update LttP sprites.")
app = get_app()
create_options_files()
create_ordered_tutorials_file()
copy_tutorials_files_to_static()
if app.config["SELFLAUNCH"]:
autohost(app.config)
if app.config["SELFGEN"]:
Expand Down
129 changes: 83 additions & 46 deletions WebHostLib/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,49 @@
from pony.orm import count, commit, db_session
from werkzeug.utils import secure_filename

from worlds.AutoWorld import AutoWorldRegister
from worlds.AutoWorld import AutoWorldRegister, World
from . import app, cache
from .models import Seed, Room, Command, UUID, uuid4
from Utils import title_sorted


def get_world_theme(game_name: str):
def get_world_theme(game_name: str) -> str:
if game_name in AutoWorldRegister.world_types:
return AutoWorldRegister.world_types[game_name].web.theme
return 'grass'


def get_visible_worlds() -> dict[str, type(World)]:
worlds = {}
for game, world in AutoWorldRegister.world_types.items():
if not world.hidden:
worlds[game] = world
return worlds


def render_markdown(path: str) -> str:
import markdown

with open(path, encoding="utf-8-sig") as f:
document = f.read()
return markdown.markdown(
document,
extensions=[
"mdx_breakless_lists",
"markdown.extensions.fenced_code",
"markdown.extensions.smarty",
"toc",
],
extension_configs={
"toc": {
"anchorlink": True,
# documentation says setting this to an empty string speeds up parsing if no marker present
"marker": "",
},
}
)


@app.errorhandler(404)
@app.errorhandler(jinja2.exceptions.TemplateNotFound)
def page_not_found(err):
Expand All @@ -31,83 +63,88 @@ def start_playing():
return render_template(f"startPlaying.html")


# Game Info Pages
@app.route('/games/<string:game>/info/<string:lang>')
@cache.cached()
def game_info(game, lang):
try:
world = AutoWorldRegister.world_types[game]
if lang not in world.web.game_info_languages:
raise KeyError("Sorry, this game's info page is not available in that language yet.")
except KeyError:
return abort(404)
return render_template('gameInfo.html', game=game, lang=lang, theme=get_world_theme(game))
"""Game Info Pages"""
theme = get_world_theme(game)
secure_game_name = secure_filename(game)
lang = secure_filename(lang)
document = render_markdown(os.path.join(
app.static_folder, "generated", "docs",
secure_game_name, f"{lang}_{secure_game_name}.md"
))
return render_template(
"markdown_document.html",
title=f"{game} Guide",
html_from_markdown=document,
theme=theme,
)


# List of supported games
@app.route('/games')
@cache.cached()
def games():
worlds = {}
for game, world in AutoWorldRegister.world_types.items():
if not world.hidden:
worlds[game] = world
return render_template("supportedGames.html", worlds=worlds)
"""List of supported games"""
return render_template("supportedGames.html", worlds=get_visible_worlds())


@app.route('/tutorial/<string:game>/<string:file>/<string:lang>')
@app.route('/tutorial/<string:game>/<string:file>')
@cache.cached()
def tutorial(game, file, lang):
try:
world = AutoWorldRegister.world_types[game]
if lang not in [tut.link.split("/")[1] for tut in world.web.tutorials]:
raise KeyError("Sorry, the tutorial is not available in that language yet.")
except KeyError:
return abort(404)
return render_template("tutorial.html", game=game, file=file, lang=lang, theme=get_world_theme(game))
def tutorial(game: str, file: str):
theme = get_world_theme(game)
secure_game_name = secure_filename(game)
file = secure_filename(file)
document = render_markdown(os.path.join(
app.static_folder, "generated", "docs",
secure_game_name, file+".md"
))
return render_template(
"markdown_document.html",
title=f"{game} Guide",
html_from_markdown=document,
theme=theme,
)


@app.route('/tutorial/')
@cache.cached()
def tutorial_landing():
return render_template("tutorialLanding.html")
tutorials = {}
worlds = AutoWorldRegister.world_types
for world_name, world_type in worlds.items():
current_world = tutorials[world_name] = {}
for tutorial in world_type.web.tutorials:
current_tutorial = current_world.setdefault(tutorial.tutorial_name, {
"description": tutorial.description, "files": {}})
current_tutorial["files"][secure_filename(tutorial.file_name).rsplit(".", 1)[0]] = {
"authors": tutorial.authors,
"language": tutorial.language
}
tutorials = {world_name: tutorials for world_name, tutorials in title_sorted(
tutorials.items(), key=lambda element: "\x00" if element[0] == "Archipelago" else worlds[element[0]].game)}
return render_template("tutorialLanding.html", worlds=worlds, tutorials=tutorials)


@app.route('/faq/<string:lang>/')
@cache.cached()
def faq(lang: str):
import markdown
with open(os.path.join(app.static_folder, "assets", "faq", secure_filename(lang)+".md")) as f:
document = f.read()
document = render_markdown(os.path.join(app.static_folder, "assets", "faq", secure_filename(lang)+".md"))
return render_template(
"markdown_document.html",
title="Frequently Asked Questions",
html_from_markdown=markdown.markdown(
document,
extensions=["toc", "mdx_breakless_lists"],
extension_configs={
"toc": {"anchorlink": True}
}
),
html_from_markdown=document,
)


@app.route('/glossary/<string:lang>/')
@cache.cached()
def glossary(lang: str):
import markdown
with open(os.path.join(app.static_folder, "assets", "glossary", secure_filename(lang)+".md")) as f:
document = f.read()
document = render_markdown(os.path.join(app.static_folder, "assets", "glossary", secure_filename(lang)+".md"))
return render_template(
"markdown_document.html",
title="Glossary",
html_from_markdown=markdown.markdown(
document,
extensions=["toc", "mdx_breakless_lists"],
extension_configs={
"toc": {"anchorlink": True}
}
),
html_from_markdown=document,
)


Expand Down
45 changes: 0 additions & 45 deletions WebHostLib/static/assets/gameInfo.js

This file was deleted.

52 changes: 0 additions & 52 deletions WebHostLib/static/assets/tutorial.js

This file was deleted.

Loading
Loading