mirror of
https://github.com/YunoHost/apps.git
synced 2024-09-03 20:06:07 +02:00
appstore: black app.py and utils.py
This commit is contained in:
parent
fd6f0eb24c
commit
803f379c81
2 changed files with 198 additions and 76 deletions
216
store/app.py
216
store/app.py
|
@ -10,18 +10,33 @@ import urllib
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
from flask import Flask, send_from_directory, render_template, session, redirect, request
|
from flask import (
|
||||||
|
Flask,
|
||||||
|
send_from_directory,
|
||||||
|
render_template,
|
||||||
|
session,
|
||||||
|
redirect,
|
||||||
|
request,
|
||||||
|
)
|
||||||
from flask_babel import Babel
|
from flask_babel import Babel
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from github import Github, InputGitAuthor
|
from github import Github, InputGitAuthor
|
||||||
from .utils import get_locale, get_catalog, get_wishlist, get_stars, get_app_md_and_screenshots
|
from .utils import (
|
||||||
|
get_locale,
|
||||||
|
get_catalog,
|
||||||
|
get_wishlist,
|
||||||
|
get_stars,
|
||||||
|
get_app_md_and_screenshots,
|
||||||
|
)
|
||||||
|
|
||||||
app = Flask(__name__, static_url_path='/assets', static_folder="assets")
|
app = Flask(__name__, static_url_path="/assets", static_folder="assets")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config = toml.loads(open("config.toml").read())
|
config = toml.loads(open("config.toml").read())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("You should create a config.toml with the appropriate key/values, cf config.toml.example")
|
print(
|
||||||
|
"You should create a config.toml with the appropriate key/values, cf config.toml.example"
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
mandatory_config_keys = [
|
mandatory_config_keys = [
|
||||||
|
@ -43,14 +58,15 @@ for key in mandatory_config_keys:
|
||||||
if config.get("DEBUG"):
|
if config.get("DEBUG"):
|
||||||
app.debug = True
|
app.debug = True
|
||||||
app.config["DEBUG"] = True
|
app.config["DEBUG"] = True
|
||||||
app.config['TEMPLATES_AUTO_RELOAD'] = True
|
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
||||||
|
|
||||||
# This is the secret key used for session signing
|
# This is the secret key used for session signing
|
||||||
app.secret_key = config["COOKIE_SECRET"]
|
app.secret_key = config["COOKIE_SECRET"]
|
||||||
|
|
||||||
babel = Babel(app, locale_selector=get_locale)
|
babel = Babel(app, locale_selector=get_locale)
|
||||||
|
|
||||||
@app.template_filter('localize')
|
|
||||||
|
@app.template_filter("localize")
|
||||||
def localize(d):
|
def localize(d):
|
||||||
if not isinstance(d, dict):
|
if not isinstance(d, dict):
|
||||||
return d
|
return d
|
||||||
|
@ -61,19 +77,26 @@ def localize(d):
|
||||||
else:
|
else:
|
||||||
return d["en"]
|
return d["en"]
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
@app.route('/favicon.ico')
|
|
||||||
|
@app.route("/favicon.ico")
|
||||||
def favicon():
|
def favicon():
|
||||||
return send_from_directory('assets', 'favicon.png')
|
return send_from_directory("assets", "favicon.png")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
return render_template("index.html", locale=get_locale(), user=session.get('user', {}), catalog=get_catalog())
|
return render_template(
|
||||||
|
"index.html",
|
||||||
|
locale=get_locale(),
|
||||||
|
user=session.get("user", {}),
|
||||||
|
catalog=get_catalog(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/catalog')
|
@app.route("/catalog")
|
||||||
def browse_catalog():
|
def browse_catalog():
|
||||||
return render_template(
|
return render_template(
|
||||||
"catalog.html",
|
"catalog.html",
|
||||||
|
@ -82,14 +105,14 @@ def browse_catalog():
|
||||||
init_search=request.args.get("search"),
|
init_search=request.args.get("search"),
|
||||||
init_category=request.args.get("category"),
|
init_category=request.args.get("category"),
|
||||||
init_starsonly=request.args.get("starsonly"),
|
init_starsonly=request.args.get("starsonly"),
|
||||||
user=session.get('user', {}),
|
user=session.get("user", {}),
|
||||||
catalog=get_catalog(),
|
catalog=get_catalog(),
|
||||||
timestamp_now=int(time.time()),
|
timestamp_now=int(time.time()),
|
||||||
stars=get_stars(),
|
stars=get_stars(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/app/<app_id>')
|
@app.route("/app/<app_id>")
|
||||||
def app_info(app_id):
|
def app_info(app_id):
|
||||||
infos = get_catalog()["apps"].get(app_id)
|
infos = get_catalog()["apps"].get(app_id)
|
||||||
app_folder = os.path.join(config["APPS_CACHE"], app_id)
|
app_folder = os.path.join(config["APPS_CACHE"], app_id)
|
||||||
|
@ -98,19 +121,29 @@ def app_info(app_id):
|
||||||
|
|
||||||
get_app_md_and_screenshots(app_folder, infos)
|
get_app_md_and_screenshots(app_folder, infos)
|
||||||
|
|
||||||
return render_template("app.html", locale=get_locale(), user=session.get('user', {}), app_id=app_id, infos=infos, catalog=get_catalog(), stars=get_stars())
|
return render_template(
|
||||||
|
"app.html",
|
||||||
|
locale=get_locale(),
|
||||||
|
user=session.get("user", {}),
|
||||||
|
app_id=app_id,
|
||||||
|
infos=infos,
|
||||||
|
catalog=get_catalog(),
|
||||||
|
stars=get_stars(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/app/<app_id>/<action>')
|
@app.route("/app/<app_id>/<action>")
|
||||||
def star_app(app_id, action):
|
def star_app(app_id, action):
|
||||||
assert action in ["star", "unstar"]
|
assert action in ["star", "unstar"]
|
||||||
if app_id not in get_catalog()["apps"] and app_id not in get_wishlist():
|
if app_id not in get_catalog()["apps"] and app_id not in get_wishlist():
|
||||||
return _("App %(app_id) not found", app_id=app_id), 404
|
return _("App %(app_id) not found", app_id=app_id), 404
|
||||||
if not session.get('user', {}):
|
if not session.get("user", {}):
|
||||||
return _("You must be logged in to be able to star an app"), 401
|
return _("You must be logged in to be able to star an app"), 401
|
||||||
|
|
||||||
app_star_folder = os.path.join(".stars", app_id)
|
app_star_folder = os.path.join(".stars", app_id)
|
||||||
app_star_for_this_user = os.path.join(".stars", app_id, session.get('user', {})["id"])
|
app_star_for_this_user = os.path.join(
|
||||||
|
".stars", app_id, session.get("user", {})["id"]
|
||||||
|
)
|
||||||
|
|
||||||
if not os.path.exists(app_star_folder):
|
if not os.path.exists(app_star_folder):
|
||||||
os.mkdir(app_star_folder)
|
os.mkdir(app_star_folder)
|
||||||
|
@ -128,7 +161,8 @@ def star_app(app_id, action):
|
||||||
else:
|
else:
|
||||||
return redirect("/wishlist")
|
return redirect("/wishlist")
|
||||||
|
|
||||||
@app.route('/wishlist')
|
|
||||||
|
@app.route("/wishlist")
|
||||||
def browse_wishlist():
|
def browse_wishlist():
|
||||||
return render_template(
|
return render_template(
|
||||||
"wishlist.html",
|
"wishlist.html",
|
||||||
|
@ -136,52 +170,89 @@ def browse_wishlist():
|
||||||
init_search=request.args.get("search"),
|
init_search=request.args.get("search"),
|
||||||
init_starsonly=request.args.get("starsonly"),
|
init_starsonly=request.args.get("starsonly"),
|
||||||
locale=get_locale(),
|
locale=get_locale(),
|
||||||
user=session.get('user', {}),
|
user=session.get("user", {}),
|
||||||
wishlist=get_wishlist(),
|
wishlist=get_wishlist(),
|
||||||
stars=get_stars()
|
stars=get_stars(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/wishlist/add', methods=['GET', 'POST'])
|
@app.route("/wishlist/add", methods=["GET", "POST"])
|
||||||
def add_to_wishlist():
|
def add_to_wishlist():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
|
user = session.get("user", {})
|
||||||
user = session.get('user', {})
|
|
||||||
if not user:
|
if not user:
|
||||||
errormsg = _("You must be logged in to submit an app to the wishlist")
|
errormsg = _("You must be logged in to submit an app to the wishlist")
|
||||||
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=None, errormsg=errormsg)
|
return render_template(
|
||||||
|
"wishlist_add.html",
|
||||||
|
locale=get_locale(),
|
||||||
|
user=session.get("user", {}),
|
||||||
|
successmsg=None,
|
||||||
|
errormsg=errormsg,
|
||||||
|
)
|
||||||
|
|
||||||
name = request.form['name'].strip().replace("\n", "")
|
name = request.form["name"].strip().replace("\n", "")
|
||||||
description = request.form['description'].strip().replace("\n", "")
|
description = request.form["description"].strip().replace("\n", "")
|
||||||
upstream = request.form['upstream'].strip().replace("\n", "")
|
upstream = request.form["upstream"].strip().replace("\n", "")
|
||||||
website = request.form['website'].strip().replace("\n", "")
|
website = request.form["website"].strip().replace("\n", "")
|
||||||
|
|
||||||
checks = [
|
checks = [
|
||||||
(len(name) >= 3, _("App name should be at least 3 characters")),
|
(len(name) >= 3, _("App name should be at least 3 characters")),
|
||||||
(len(name) <= 30, _("App name should be less than 30 characters")),
|
(len(name) <= 30, _("App name should be less than 30 characters")),
|
||||||
(len(description) >= 5, _("App description should be at least 5 characters")),
|
(
|
||||||
(len(description) <= 100, _("App description should be less than 100 characters")),
|
len(description) >= 5,
|
||||||
(len(upstream) >= 10, _("Upstream code repo URL should be at least 10 characters")),
|
_("App description should be at least 5 characters"),
|
||||||
(len(upstream) <= 150, _("Upstream code repo URL should be less than 150 characters")),
|
),
|
||||||
|
(
|
||||||
|
len(description) <= 100,
|
||||||
|
_("App description should be less than 100 characters"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
len(upstream) >= 10,
|
||||||
|
_("Upstream code repo URL should be at least 10 characters"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
len(upstream) <= 150,
|
||||||
|
_("Upstream code repo URL should be less than 150 characters"),
|
||||||
|
),
|
||||||
(len(website) <= 150, _("Website URL should be less than 150 characters")),
|
(len(website) <= 150, _("Website URL should be less than 150 characters")),
|
||||||
(re.match(r"^[\w\.\-\(\)\ ]+$", name), _("App name contains special characters")),
|
(
|
||||||
|
re.match(r"^[\w\.\-\(\)\ ]+$", name),
|
||||||
|
_("App name contains special characters"),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
for check, errormsg in checks:
|
for check, errormsg in checks:
|
||||||
if not check:
|
if not check:
|
||||||
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=None, errormsg=errormsg)
|
return render_template(
|
||||||
|
"wishlist_add.html",
|
||||||
|
locale=get_locale(),
|
||||||
|
user=session.get("user", {}),
|
||||||
|
successmsg=None,
|
||||||
|
errormsg=errormsg,
|
||||||
|
)
|
||||||
|
|
||||||
slug = slugify(name)
|
slug = slugify(name)
|
||||||
github = Github(config["GITHUB_TOKEN"])
|
github = Github(config["GITHUB_TOKEN"])
|
||||||
author = InputGitAuthor(config["GITHUB_LOGIN"], config["GITHUB_EMAIL"])
|
author = InputGitAuthor(config["GITHUB_LOGIN"], config["GITHUB_EMAIL"])
|
||||||
repo = github.get_repo("Yunohost/apps")
|
repo = github.get_repo("Yunohost/apps")
|
||||||
current_wishlist_rawtoml = repo.get_contents("wishlist.toml", ref="app-store") # FIXME : ref=repo.default_branch)
|
current_wishlist_rawtoml = repo.get_contents(
|
||||||
|
"wishlist.toml", ref="app-store"
|
||||||
|
) # FIXME : ref=repo.default_branch)
|
||||||
current_wishlist_sha = current_wishlist_rawtoml.sha
|
current_wishlist_sha = current_wishlist_rawtoml.sha
|
||||||
current_wishlist_rawtoml = current_wishlist_rawtoml.decoded_content.decode()
|
current_wishlist_rawtoml = current_wishlist_rawtoml.decoded_content.decode()
|
||||||
new_wishlist = toml.loads(current_wishlist_rawtoml)
|
new_wishlist = toml.loads(current_wishlist_rawtoml)
|
||||||
|
|
||||||
if slug in new_wishlist:
|
if slug in new_wishlist:
|
||||||
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=None, errormsg=_("An entry with the name %(slug) already exists in the wishlist", slug=slug))
|
return render_template(
|
||||||
|
"wishlist_add.html",
|
||||||
|
locale=get_locale(),
|
||||||
|
user=session.get("user", {}),
|
||||||
|
successmsg=None,
|
||||||
|
errormsg=_(
|
||||||
|
"An entry with the name %(slug) already exists in the wishlist",
|
||||||
|
slug=slug,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
new_wishlist[slug] = {
|
new_wishlist[slug] = {
|
||||||
"name": name,
|
"name": name,
|
||||||
|
@ -195,13 +266,23 @@ def add_to_wishlist():
|
||||||
new_branch = f"add-to-wishlist-{slug}"
|
new_branch = f"add-to-wishlist-{slug}"
|
||||||
try:
|
try:
|
||||||
# Get the commit base for the new branch, and create it
|
# Get the commit base for the new branch, and create it
|
||||||
commit_sha = repo.get_branch("app-store").commit.sha # FIXME app-store -> repo.default_branch
|
commit_sha = repo.get_branch(
|
||||||
|
"app-store"
|
||||||
|
).commit.sha # FIXME app-store -> repo.default_branch
|
||||||
repo.create_git_ref(ref=f"refs/heads/{new_branch}", sha=commit_sha)
|
repo.create_git_ref(ref=f"refs/heads/{new_branch}", sha=commit_sha)
|
||||||
except exception as e:
|
except exception as e:
|
||||||
print("... Failed to create branch ?")
|
print("... Failed to create branch ?")
|
||||||
print(e)
|
print(e)
|
||||||
errormsg = _("Failed to create the pull request to add the app to the wishlist ... please report the issue to the yunohost team")
|
errormsg = _(
|
||||||
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=None, errormsg=errormsg)
|
"Failed to create the pull request to add the app to the wishlist ... please report the issue to the yunohost team"
|
||||||
|
)
|
||||||
|
return render_template(
|
||||||
|
"wishlist_add.html",
|
||||||
|
locale=get_locale(),
|
||||||
|
user=session.get("user", {}),
|
||||||
|
successmsg=None,
|
||||||
|
errormsg=errormsg,
|
||||||
|
)
|
||||||
|
|
||||||
message = f"Add {name} to wishlist"
|
message = f"Add {name} to wishlist"
|
||||||
repo.update_file(
|
repo.update_file(
|
||||||
|
@ -228,28 +309,50 @@ Proposed by **{session['user']['username']}**
|
||||||
|
|
||||||
# Open the PR
|
# Open the PR
|
||||||
pr = repo.create_pull(
|
pr = repo.create_pull(
|
||||||
title=message, body=body, head=new_branch, base="app-store" # FIXME app-store -> repo.default_branch
|
title=message,
|
||||||
|
body=body,
|
||||||
|
head=new_branch,
|
||||||
|
base="app-store", # FIXME app-store -> repo.default_branch
|
||||||
)
|
)
|
||||||
|
|
||||||
url = f"https://github.com/YunoHost/apps/pull/{pr.number}"
|
url = f"https://github.com/YunoHost/apps/pull/{pr.number}"
|
||||||
|
|
||||||
successmsg = _("Your proposed app has succesfully been submitted. It must now be validated by the YunoHost team. You can track progress here: %(url)s", url=url)
|
successmsg = _(
|
||||||
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=successmsg)
|
"Your proposed app has succesfully been submitted. It must now be validated by the YunoHost team. You can track progress here: %(url)s",
|
||||||
|
url=url,
|
||||||
|
)
|
||||||
|
return render_template(
|
||||||
|
"wishlist_add.html",
|
||||||
|
locale=get_locale(),
|
||||||
|
user=session.get("user", {}),
|
||||||
|
successmsg=successmsg,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return render_template("wishlist_add.html", locale=get_locale(), user=session.get('user', {}), successmsg=None, errormsg=None)
|
return render_template(
|
||||||
|
"wishlist_add.html",
|
||||||
|
locale=get_locale(),
|
||||||
|
user=session.get("user", {}),
|
||||||
|
successmsg=None,
|
||||||
|
errormsg=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Session / SSO using Discourse #
|
# Session / SSO using Discourse #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
@app.route('/login_using_discourse')
|
|
||||||
|
@app.route("/login_using_discourse")
|
||||||
def login_using_discourse():
|
def login_using_discourse():
|
||||||
"""
|
"""
|
||||||
Send auth request to Discourse:
|
Send auth request to Discourse:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
nonce, url, uri_to_redirect_to_after_login = create_nonce_and_build_url_to_login_on_discourse_sso()
|
(
|
||||||
|
nonce,
|
||||||
|
url,
|
||||||
|
uri_to_redirect_to_after_login,
|
||||||
|
) = create_nonce_and_build_url_to_login_on_discourse_sso()
|
||||||
|
|
||||||
session.clear()
|
session.clear()
|
||||||
session["nonce"] = nonce
|
session["nonce"] = nonce
|
||||||
|
@ -259,17 +362,17 @@ def login_using_discourse():
|
||||||
return redirect(url)
|
return redirect(url)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/sso_login_callback')
|
@app.route("/sso_login_callback")
|
||||||
def sso_login_callback():
|
def sso_login_callback():
|
||||||
response = base64.b64decode(request.args['sso'].encode()).decode()
|
response = base64.b64decode(request.args["sso"].encode()).decode()
|
||||||
user_data = urllib.parse.parse_qs(response)
|
user_data = urllib.parse.parse_qs(response)
|
||||||
if user_data['nonce'][0] != session.get("nonce"):
|
if user_data["nonce"][0] != session.get("nonce"):
|
||||||
return "Invalid nonce", 401
|
return "Invalid nonce", 401
|
||||||
|
|
||||||
uri_to_redirect_to_after_login = session.get("uri_to_redirect_to_after_login")
|
uri_to_redirect_to_after_login = session.get("uri_to_redirect_to_after_login")
|
||||||
|
|
||||||
session.clear()
|
session.clear()
|
||||||
session['user'] = {
|
session["user"] = {
|
||||||
"id": user_data["external_id"][0],
|
"id": user_data["external_id"][0],
|
||||||
"username": user_data["username"][0],
|
"username": user_data["username"][0],
|
||||||
"avatar_url": user_data["avatar_url"][0],
|
"avatar_url": user_data["avatar_url"][0],
|
||||||
|
@ -281,7 +384,7 @@ def sso_login_callback():
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/logout')
|
@app.route("/logout")
|
||||||
def logout():
|
def logout():
|
||||||
session.clear()
|
session.clear()
|
||||||
|
|
||||||
|
@ -308,7 +411,7 @@ def create_nonce_and_build_url_to_login_on_discourse_sso():
|
||||||
Redirect the user to DISCOURSE_ROOT_URL/session/sso_provider?sso=URL_ENCODED_PAYLOAD&sig=HEX_SIGNATURE
|
Redirect the user to DISCOURSE_ROOT_URL/session/sso_provider?sso=URL_ENCODED_PAYLOAD&sig=HEX_SIGNATURE
|
||||||
"""
|
"""
|
||||||
|
|
||||||
nonce = ''.join([str(random.randint(0, 9)) for i in range(99)])
|
nonce = "".join([str(random.randint(0, 9)) for i in range(99)])
|
||||||
|
|
||||||
# Only use the current referer URI if it's on the same domain as the current route
|
# Only use the current referer URI if it's on the same domain as the current route
|
||||||
# to avoid XSS or whatever...
|
# to avoid XSS or whatever...
|
||||||
|
@ -326,10 +429,17 @@ def create_nonce_and_build_url_to_login_on_discourse_sso():
|
||||||
if domain == request.environ.get("HTTP_HOST"):
|
if domain == request.environ.get("HTTP_HOST"):
|
||||||
uri_to_redirect_to_after_login = uri
|
uri_to_redirect_to_after_login = uri
|
||||||
|
|
||||||
url_data = {"nonce": nonce, "return_sso_url": config["CALLBACK_URL_AFTER_LOGIN_ON_DISCOURSE"]}
|
url_data = {
|
||||||
|
"nonce": nonce,
|
||||||
|
"return_sso_url": config["CALLBACK_URL_AFTER_LOGIN_ON_DISCOURSE"],
|
||||||
|
}
|
||||||
url_encoded = urllib.parse.urlencode(url_data)
|
url_encoded = urllib.parse.urlencode(url_data)
|
||||||
payload = base64.b64encode(url_encoded.encode()).decode()
|
payload = base64.b64encode(url_encoded.encode()).decode()
|
||||||
sig = hmac.new(config["DISCOURSE_SSO_SECRET"].encode(), msg=payload.encode(), digestmod=hashlib.sha256).hexdigest()
|
sig = hmac.new(
|
||||||
|
config["DISCOURSE_SSO_SECRET"].encode(),
|
||||||
|
msg=payload.encode(),
|
||||||
|
digestmod=hashlib.sha256,
|
||||||
|
).hexdigest()
|
||||||
data = {"sig": sig, "sso": payload}
|
data = {"sig": sig, "sso": payload}
|
||||||
url = f"{config['DISCOURSE_SSO_ENDPOINT']}?{urllib.parse.urlencode(data)}"
|
url = f"{config['DISCOURSE_SSO_ENDPOINT']}?{urllib.parse.urlencode(data)}"
|
||||||
|
|
||||||
|
|
|
@ -9,22 +9,23 @@ from flask import request
|
||||||
|
|
||||||
|
|
||||||
AVAILABLE_LANGUAGES = ["en"] + os.listdir("translations")
|
AVAILABLE_LANGUAGES = ["en"] + os.listdir("translations")
|
||||||
|
|
||||||
|
|
||||||
def get_locale():
|
def get_locale():
|
||||||
# try to guess the language from the user accept
|
# try to guess the language from the user accept
|
||||||
# The best match wins.
|
# The best match wins.
|
||||||
return request.accept_languages.best_match(AVAILABLE_LANGUAGES)
|
return request.accept_languages.best_match(AVAILABLE_LANGUAGES)
|
||||||
|
|
||||||
def get_catalog():
|
|
||||||
|
|
||||||
|
def get_catalog():
|
||||||
path = "../builds/default/v3/apps.json"
|
path = "../builds/default/v3/apps.json"
|
||||||
mtime = os.path.getmtime(path)
|
mtime = os.path.getmtime(path)
|
||||||
if get_catalog.mtime_catalog != mtime:
|
if get_catalog.mtime_catalog != mtime:
|
||||||
|
|
||||||
get_catalog.mtime_catalog = mtime
|
get_catalog.mtime_catalog = mtime
|
||||||
|
|
||||||
catalog = json.load(open(path))
|
catalog = json.load(open(path))
|
||||||
catalog['categories'] = {c['id']:c for c in catalog['categories']}
|
catalog["categories"] = {c["id"]: c for c in catalog["categories"]}
|
||||||
catalog['antifeatures'] = {c['id']:c for c in catalog['antifeatures']}
|
catalog["antifeatures"] = {c["id"]: c for c in catalog["antifeatures"]}
|
||||||
|
|
||||||
category_color = {
|
category_color = {
|
||||||
"synchronization": "sky",
|
"synchronization": "sky",
|
||||||
|
@ -43,35 +44,38 @@ def get_catalog():
|
||||||
"wat": "teal",
|
"wat": "teal",
|
||||||
}
|
}
|
||||||
|
|
||||||
for id_, category in catalog['categories'].items():
|
for id_, category in catalog["categories"].items():
|
||||||
category["color"] = category_color[id_]
|
category["color"] = category_color[id_]
|
||||||
|
|
||||||
get_catalog.cache_catalog = catalog
|
get_catalog.cache_catalog = catalog
|
||||||
|
|
||||||
return get_catalog.cache_catalog
|
return get_catalog.cache_catalog
|
||||||
|
|
||||||
|
|
||||||
get_catalog.mtime_catalog = None
|
get_catalog.mtime_catalog = None
|
||||||
get_catalog()
|
get_catalog()
|
||||||
|
|
||||||
|
|
||||||
def get_wishlist():
|
def get_wishlist():
|
||||||
|
|
||||||
path = "../wishlist.toml"
|
path = "../wishlist.toml"
|
||||||
mtime = os.path.getmtime(path)
|
mtime = os.path.getmtime(path)
|
||||||
if get_wishlist.mtime_wishlist != mtime:
|
if get_wishlist.mtime_wishlist != mtime:
|
||||||
|
|
||||||
get_wishlist.mtime_wishlist = mtime
|
get_wishlist.mtime_wishlist = mtime
|
||||||
get_wishlist.cache_wishlist = toml.load(open(path))
|
get_wishlist.cache_wishlist = toml.load(open(path))
|
||||||
|
|
||||||
return get_wishlist.cache_wishlist
|
return get_wishlist.cache_wishlist
|
||||||
|
|
||||||
|
|
||||||
get_wishlist.mtime_wishlist = None
|
get_wishlist.mtime_wishlist = None
|
||||||
get_wishlist()
|
get_wishlist()
|
||||||
|
|
||||||
|
|
||||||
def get_stars():
|
def get_stars():
|
||||||
|
checksum = (
|
||||||
checksum = subprocess.check_output("find . -type f -printf '%T@,' | md5sum", shell=True).decode().split()[0]
|
subprocess.check_output("find . -type f -printf '%T@,' | md5sum", shell=True)
|
||||||
|
.decode()
|
||||||
|
.split()[0]
|
||||||
|
)
|
||||||
if get_stars.cache_checksum != checksum:
|
if get_stars.cache_checksum != checksum:
|
||||||
stars = {}
|
stars = {}
|
||||||
for folder, _, files in os.walk(".stars/"):
|
for folder, _, files in os.walk(".stars/"):
|
||||||
|
@ -84,6 +88,7 @@ def get_stars():
|
||||||
|
|
||||||
return get_stars.cache_stars
|
return get_stars.cache_stars
|
||||||
|
|
||||||
|
|
||||||
get_stars.cache_checksum = None
|
get_stars.cache_checksum = None
|
||||||
get_stars()
|
get_stars()
|
||||||
|
|
||||||
|
@ -98,9 +103,7 @@ def human_to_binary(size: str) -> int:
|
||||||
size = size[:-1]
|
size = size[:-1]
|
||||||
|
|
||||||
if suffix not in symbols:
|
if suffix not in symbols:
|
||||||
raise Exception(
|
raise Exception(f"Invalid size suffix '{suffix}', expected one of {symbols}")
|
||||||
f"Invalid size suffix '{suffix}', expected one of {symbols}"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
size_ = float(size)
|
size_ = float(size)
|
||||||
|
@ -111,10 +114,11 @@ def human_to_binary(size: str) -> int:
|
||||||
|
|
||||||
|
|
||||||
def get_app_md_and_screenshots(app_folder, infos):
|
def get_app_md_and_screenshots(app_folder, infos):
|
||||||
|
|
||||||
locale = get_locale()
|
locale = get_locale()
|
||||||
|
|
||||||
if locale != "en" and os.path.exists(os.path.join(app_folder, "doc", f"DESCRIPTION_{locale}.md")):
|
if locale != "en" and os.path.exists(
|
||||||
|
os.path.join(app_folder, "doc", f"DESCRIPTION_{locale}.md")
|
||||||
|
):
|
||||||
description_path = os.path.join(app_folder, "doc", f"DESCRIPTION_{locale}.md")
|
description_path = os.path.join(app_folder, "doc", f"DESCRIPTION_{locale}.md")
|
||||||
elif os.path.exists(os.path.join(app_folder, "doc", "DESCRIPTION.md")):
|
elif os.path.exists(os.path.join(app_folder, "doc", "DESCRIPTION.md")):
|
||||||
description_path = os.path.join(app_folder, "doc", "DESCRIPTION.md")
|
description_path = os.path.join(app_folder, "doc", "DESCRIPTION.md")
|
||||||
|
@ -122,11 +126,15 @@ def get_app_md_and_screenshots(app_folder, infos):
|
||||||
description_path = None
|
description_path = None
|
||||||
if description_path:
|
if description_path:
|
||||||
with open(description_path) as f:
|
with open(description_path) as f:
|
||||||
infos["full_description_html"] = emojize(pycmarkgfm.gfm_to_html(f.read()), language="alias")
|
infos["full_description_html"] = emojize(
|
||||||
|
pycmarkgfm.gfm_to_html(f.read()), language="alias"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
infos["full_description_html"] = infos['manifest']['description'][locale]
|
infos["full_description_html"] = infos["manifest"]["description"][locale]
|
||||||
|
|
||||||
if locale != "en" and os.path.exists(os.path.join(app_folder, "doc", f"PRE_INSTALL_{locale}.md")):
|
if locale != "en" and os.path.exists(
|
||||||
|
os.path.join(app_folder, "doc", f"PRE_INSTALL_{locale}.md")
|
||||||
|
):
|
||||||
pre_install_path = os.path.join(app_folder, "doc", f"PRE_INSTALL_{locale}.md")
|
pre_install_path = os.path.join(app_folder, "doc", f"PRE_INSTALL_{locale}.md")
|
||||||
elif os.path.exists(os.path.join(app_folder, "doc", "PRE_INSTALL.md")):
|
elif os.path.exists(os.path.join(app_folder, "doc", "PRE_INSTALL.md")):
|
||||||
pre_install_path = os.path.join(app_folder, "doc", "PRE_INSTALL.md")
|
pre_install_path = os.path.join(app_folder, "doc", "PRE_INSTALL.md")
|
||||||
|
@ -134,7 +142,9 @@ def get_app_md_and_screenshots(app_folder, infos):
|
||||||
pre_install_path = None
|
pre_install_path = None
|
||||||
if pre_install_path:
|
if pre_install_path:
|
||||||
with open(pre_install_path) as f:
|
with open(pre_install_path) as f:
|
||||||
infos["pre_install_html"] = emojize(pycmarkgfm.gfm_to_html(f.read()), language="alias")
|
infos["pre_install_html"] = emojize(
|
||||||
|
pycmarkgfm.gfm_to_html(f.read()), language="alias"
|
||||||
|
)
|
||||||
|
|
||||||
infos["screenshot"] = None
|
infos["screenshot"] = None
|
||||||
|
|
||||||
|
@ -153,4 +163,6 @@ def get_app_md_and_screenshots(app_folder, infos):
|
||||||
break
|
break
|
||||||
|
|
||||||
ram_build_requirement = infos["manifest"]["integration"]["ram"]["build"]
|
ram_build_requirement = infos["manifest"]["integration"]["ram"]["build"]
|
||||||
infos["manifest"]["integration"]["ram"]["build_binary"] = human_to_binary(ram_build_requirement)
|
infos["manifest"]["integration"]["ram"]["build_binary"] = human_to_binary(
|
||||||
|
ram_build_requirement
|
||||||
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue