mirror of
https://github.com/YunoHost/apps.git
synced 2024-09-03 20:06:07 +02:00
Use pathlib, fix ruff issues
This commit is contained in:
parent
b44b882474
commit
9d9748a218
5 changed files with 110 additions and 107 deletions
|
@ -0,0 +1 @@
|
|||
#!/usr/bin/env python3
|
104
store/app.py
104
store/app.py
|
@ -1,45 +1,39 @@
|
|||
import time
|
||||
import re
|
||||
import toml
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import string
|
||||
import random
|
||||
import urllib
|
||||
import json
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
from slugify import slugify
|
||||
from flask import (
|
||||
Flask,
|
||||
send_from_directory,
|
||||
render_template,
|
||||
session,
|
||||
redirect,
|
||||
request,
|
||||
)
|
||||
from flask_babel import Babel
|
||||
import time
|
||||
import urllib
|
||||
from pathlib import Path
|
||||
|
||||
import toml
|
||||
from flask import Flask, redirect, render_template, request, send_from_directory, session
|
||||
from flask.typing import ResponseReturnValue
|
||||
from flask_babel import Babel # type: ignore
|
||||
from flask_babel import gettext as _
|
||||
from github import Github, InputGitAuthor
|
||||
from slugify import slugify
|
||||
|
||||
sys.path = [os.path.dirname(__file__)] + sys.path
|
||||
|
||||
from utils import (
|
||||
get_locale,
|
||||
get_catalog,
|
||||
get_wishlist,
|
||||
get_stars,
|
||||
get_app_md_and_screenshots,
|
||||
save_wishlist_submit_for_ratelimit,
|
||||
from .utils import (
|
||||
check_wishlist_submit_ratelimit,
|
||||
get_app_md_and_screenshots,
|
||||
get_catalog,
|
||||
get_locale,
|
||||
get_stars,
|
||||
get_wishlist,
|
||||
save_wishlist_submit_for_ratelimit,
|
||||
)
|
||||
|
||||
app = Flask(__name__, static_url_path="/assets", static_folder="assets")
|
||||
|
||||
try:
|
||||
config = toml.loads(open("config.toml").read())
|
||||
except Exception as e:
|
||||
config = toml.loads(Path("config.toml").open().read())
|
||||
except RuntimeError:
|
||||
print(
|
||||
"You should create a config.toml with the appropriate key/values, cf config.toml.example"
|
||||
)
|
||||
|
@ -126,8 +120,8 @@ def popularity_json():
|
|||
@app.route("/app/<app_id>")
|
||||
def app_info(app_id):
|
||||
infos = get_catalog()["apps"].get(app_id)
|
||||
app_folder = os.path.join(config["APPS_CACHE"], app_id)
|
||||
if not infos or not os.path.exists(app_folder):
|
||||
app_folder = Path(config["APPS_CACHE"]) / app_id
|
||||
if not infos or not app_folder.exists():
|
||||
return f"App {app_id} not found", 404
|
||||
|
||||
get_app_md_and_screenshots(app_folder, infos)
|
||||
|
@ -144,7 +138,7 @@ def app_info(app_id):
|
|||
|
||||
|
||||
@app.route("/app/<app_id>/<action>")
|
||||
def star_app(app_id, action):
|
||||
def star_app(app_id: str, action) -> ResponseReturnValue:
|
||||
assert action in ["star", "unstar"]
|
||||
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
|
||||
|
@ -153,26 +147,23 @@ def star_app(app_id, action):
|
|||
_("You must be logged in to be able to star an app")
|
||||
+ "<br/><br/>"
|
||||
+ _(
|
||||
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.<br/><br/>'Trust level 1' is obtained after interacting a minimum with the forum, and more specifically: entering at least 5 topics, reading at least 30 posts, and spending at least 10 minutes reading posts."
|
||||
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users."
|
||||
"<br/><br/>"
|
||||
"'Trust level 1' is obtained after interacting a minimum with the forum, and more specifically: "
|
||||
"entering at least 5 topics, reading at least 30 posts, and spending at least 10 minutes reading posts."
|
||||
),
|
||||
401,
|
||||
)
|
||||
|
||||
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_folder = Path(".stars") / app_id
|
||||
app_star_for_this_user = app_star_folder / (session.get("user", {})["id"])
|
||||
|
||||
if not os.path.exists(app_star_folder):
|
||||
os.mkdir(app_star_folder)
|
||||
app_star_folder.mkdir(exist_ok=True)
|
||||
|
||||
if action == "star":
|
||||
open(app_star_for_this_user, "w").write("")
|
||||
app_star_for_this_user.open("w").write("")
|
||||
elif action == "unstar":
|
||||
try:
|
||||
os.remove(app_star_for_this_user)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
app_star_folder.unlink(missing_ok=True)
|
||||
|
||||
if app_id in get_catalog()["apps"]:
|
||||
return redirect(f"/app/{app_id}")
|
||||
|
@ -203,7 +194,10 @@ def add_to_wishlist():
|
|||
_("You must be logged in to submit an app to the wishlist")
|
||||
+ "<br/><br/>"
|
||||
+ _(
|
||||
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.<br/><br/>'Trust level 1' is obtained after interacting a minimum with the forum, and more specifically: entering at least 5 topics, reading at least 30 posts, and spending at least 10 minutes reading posts."
|
||||
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users."
|
||||
"<br/><br/>'Trust level 1' is obtained after interacting a minimum with the forum, and more "
|
||||
"specifically: entering at least 5 topics, reading at least 30 posts, and spending at least "
|
||||
"10 minutes reading posts."
|
||||
)
|
||||
)
|
||||
return render_template(
|
||||
|
@ -258,7 +252,8 @@ def add_to_wishlist():
|
|||
check_wishlist_submit_ratelimit(session["user"]["username"]) is True
|
||||
and session["user"]["bypass_ratelimit"] is False,
|
||||
_(
|
||||
"Proposing wishlist additions is limited to once every 15 days per user. Please try again in a few days."
|
||||
"Proposing wishlist additions is limited to once every 15 days per user. "
|
||||
"Please try again in a few days."
|
||||
),
|
||||
),
|
||||
(len(name) >= 3, _("App name should be at least 3 characters")),
|
||||
|
@ -298,7 +293,8 @@ def add_to_wishlist():
|
|||
for keyword in boring_keywords_to_check_for_people_not_reading_the_instructions
|
||||
),
|
||||
_(
|
||||
"Please focus on what the app does, without using marketing, fuzzy terms, or repeating that the app is 'free' and 'self-hostable'."
|
||||
"Please focus on what the app does, without using marketing, fuzzy terms, "
|
||||
"or repeating that the app is 'free' and 'self-hostable'."
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -342,7 +338,8 @@ def add_to_wishlist():
|
|||
csrf_token=csrf_token,
|
||||
successmsg=None,
|
||||
errormsg=_(
|
||||
"An entry with the name %(slug)s already exists in the wishlist, instead, you can <a href='%(url)s'>add a star to the app to show your interest</a>.",
|
||||
"An entry with the name %(slug)s already exists in the wishlist, instead, "
|
||||
"you can <a href='%(url)s'>add a star to the app to show your interest</a>.",
|
||||
slug=slug,
|
||||
url=url,
|
||||
),
|
||||
|
@ -362,12 +359,13 @@ def add_to_wishlist():
|
|||
# Get the commit base for the new branch, and create it
|
||||
commit_sha = repo.get_branch(repo.default_branch).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(e)
|
||||
url = "https://github.com/YunoHost/apps/pulls?q=is%3Apr+is%3Aopen+wishlist"
|
||||
errormsg = _(
|
||||
"Failed to create the pull request to add the app to the wishlist… Maybe there's already <a href='%(url)s'>a waiting PR for this app</a>? Else, please report the issue to the YunoHost team.",
|
||||
"Failed to create the pull request to add the app to the wishlist… Maybe there's already "
|
||||
"<a href='%(url)s'>a waiting PR for this app</a>? Else, please report the issue to the YunoHost team.",
|
||||
url=url,
|
||||
)
|
||||
return render_template(
|
||||
|
@ -419,7 +417,8 @@ Description: {description}
|
|||
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: <a href='%(url)s'>%(url)s</a>",
|
||||
"Your proposed app has succesfully been submitted. It must now be validated by the YunoHost team. "
|
||||
"You can track progress here: <a href='%(url)s'>%(url)s</a>",
|
||||
url=url,
|
||||
)
|
||||
|
||||
|
@ -495,7 +494,10 @@ def sso_login_callback():
|
|||
_("Unfortunately, login was denied.")
|
||||
+ "<br/><br/>"
|
||||
+ _(
|
||||
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.<br/><br/>'Trust level 1' is obtained after interacting a minimum with the forum, and more specifically: entering at least 5 topics, reading at least 30 posts, and spending at least 10 minutes reading posts."
|
||||
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users."
|
||||
"<br/><br/>'Trust level 1' is obtained after interacting a minimum with the forum, and more "
|
||||
"specifically: entering at least 5 topics, reading at least 30 posts, and spending at least "
|
||||
"10 minutes reading posts."
|
||||
),
|
||||
403,
|
||||
)
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import os
|
||||
#!/usr/bin/env python3
|
||||
|
||||
install_dir = os.path.dirname(__file__)
|
||||
from pathlib import Path
|
||||
|
||||
install_dir = Path(__file__).resolve().parent
|
||||
command = f"{install_dir}/venv/bin/gunicorn"
|
||||
pythonpath = install_dir
|
||||
pythonpath = str(install_dir)
|
||||
workers = 4
|
||||
user = "appstore"
|
||||
bind = f"unix:{install_dir}/sock"
|
||||
pid = "/run/gunicorn/appstore-pid"
|
||||
errorlog = "/var/log/appstore/error.log"
|
||||
accesslog = "/var/log/appstore/access.log"
|
||||
access_log_format = '%({X-Real-IP}i)s %({X-Forwarded-For}i)s %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
||||
access_log_format = \
|
||||
'%({X-Real-IP}i)s %({X-Forwarded-For}i)s %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
||||
loglevel = "warning"
|
||||
capture_output = True
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
Flask==2.3.2
|
||||
python-slugify
|
||||
PyGithub
|
||||
toml
|
||||
pycmarkgfm
|
||||
gunicorn
|
||||
emoji
|
||||
Babel
|
||||
Flask-Babel
|
|
@ -1,15 +1,22 @@
|
|||
import time
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import base64
|
||||
import os
|
||||
import json
|
||||
import toml
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from hashlib import md5
|
||||
from pathlib import Path
|
||||
|
||||
import pycmarkgfm
|
||||
import tomlkit
|
||||
from emoji import emojize
|
||||
from flask import request
|
||||
from hashlib import md5
|
||||
|
||||
AVAILABLE_LANGUAGES = ["en"] + os.listdir("translations")
|
||||
TRANSLATIONS_DIR = Path(__file__).parent / "translations"
|
||||
|
||||
|
||||
AVAILABLE_LANGUAGES = ["en"] + [str(d) for d in TRANSLATIONS_DIR.glob("*/")]
|
||||
|
||||
|
||||
def get_locale():
|
||||
|
@ -19,12 +26,13 @@ def get_locale():
|
|||
|
||||
|
||||
def get_catalog():
|
||||
path = "../builds/default/v3/apps.json"
|
||||
mtime = os.path.getmtime(path)
|
||||
path = Path("../builds/default/v3/apps.json").resolve()
|
||||
|
||||
mtime = path.stat().st_mtime
|
||||
if get_catalog.mtime_catalog != mtime:
|
||||
get_catalog.mtime_catalog = mtime
|
||||
|
||||
catalog = json.load(open(path))
|
||||
catalog = json.load(path.open())
|
||||
catalog["categories"] = {c["id"]: c for c in catalog["categories"]}
|
||||
catalog["antifeatures"] = {c["id"]: c for c in catalog["antifeatures"]}
|
||||
|
||||
|
@ -58,11 +66,11 @@ get_catalog()
|
|||
|
||||
|
||||
def get_wishlist():
|
||||
path = "../wishlist.toml"
|
||||
mtime = os.path.getmtime(path)
|
||||
path = Path("../wishlist.toml").resolve()
|
||||
mtime = path.stat().st_mtime
|
||||
if get_wishlist.mtime_wishlist != mtime:
|
||||
get_wishlist.mtime_wishlist = mtime
|
||||
get_wishlist.cache_wishlist = toml.load(open(path))
|
||||
get_wishlist.cache_wishlist = tomlkit.load(path.open())
|
||||
|
||||
return get_wishlist.cache_wishlist
|
||||
|
||||
|
@ -96,26 +104,22 @@ get_stars()
|
|||
|
||||
def check_wishlist_submit_ratelimit(user):
|
||||
|
||||
dir_ = os.path.join(".wishlist_ratelimit")
|
||||
if not os.path.exists(dir_):
|
||||
os.mkdir(dir_)
|
||||
dir_ = Path(".wishlist_ratelimit").resolve()
|
||||
dir_.mkdir(exist_ok=True)
|
||||
f = dir_ / md5(user.encode()).hexdigest()
|
||||
|
||||
f = os.path.join(dir_, md5(user.encode()).hexdigest())
|
||||
|
||||
return not os.path.exists(f) or (time.time() - os.path.getmtime(f)) > (
|
||||
return not f.exists() or (time.time() - f.stat().st_mtime) > (
|
||||
15 * 24 * 3600
|
||||
) # 15 days
|
||||
|
||||
|
||||
def save_wishlist_submit_for_ratelimit(user):
|
||||
|
||||
dir_ = os.path.join(".wishlist_ratelimit")
|
||||
if not os.path.exists(dir_):
|
||||
os.mkdir(dir_)
|
||||
dir_ = Path(".wishlist_ratelimit").resolve()
|
||||
dir_.mkdir(exist_ok=True)
|
||||
|
||||
f = os.path.join(dir_, md5(user.encode()).hexdigest())
|
||||
|
||||
open(f, "w").write("")
|
||||
f = dir_ / md5(user.encode()).hexdigest()
|
||||
f.touch()
|
||||
|
||||
|
||||
def human_to_binary(size: str) -> int:
|
||||
|
@ -133,7 +137,7 @@ def human_to_binary(size: str) -> int:
|
|||
try:
|
||||
size_ = float(size)
|
||||
except Exception:
|
||||
raise Exception(f"Failed to convert size {size} to float")
|
||||
raise Exception(f"Failed to convert size {size} to float") # noqa: B904
|
||||
|
||||
return int(size_ * factor[suffix])
|
||||
|
||||
|
@ -141,46 +145,48 @@ def human_to_binary(size: str) -> int:
|
|||
def get_app_md_and_screenshots(app_folder, infos):
|
||||
locale = get_locale()
|
||||
|
||||
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")
|
||||
elif os.path.exists(os.path.join(app_folder, "doc", "DESCRIPTION.md")):
|
||||
description_path = os.path.join(app_folder, "doc", "DESCRIPTION.md")
|
||||
description_path_localized = app_folder / "doc" / f"DESCRIPTION_{locale}.md"
|
||||
description_path_generic = app_folder / "doc" / "DESCRIPTION.md"
|
||||
|
||||
if locale != "en" and description_path_localized.exists():
|
||||
description_path = description_path_localized
|
||||
elif description_path_generic.exists():
|
||||
description_path = description_path_generic
|
||||
else:
|
||||
description_path = None
|
||||
if description_path:
|
||||
with open(description_path) as f:
|
||||
with description_path.open() as f:
|
||||
infos["full_description_html"] = emojize(
|
||||
pycmarkgfm.gfm_to_html(f.read()), language="alias"
|
||||
)
|
||||
else:
|
||||
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")
|
||||
):
|
||||
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")):
|
||||
pre_install_path = os.path.join(app_folder, "doc", "PRE_INSTALL.md")
|
||||
preinstall_path_localized = app_folder / "doc" / f"PRE_INSTALL_{locale}.md"
|
||||
preinstall_path_generic = app_folder / "doc" / "PRE_INSTALL.md"
|
||||
|
||||
if locale != "en" and preinstall_path_localized.exists():
|
||||
pre_install_path = preinstall_path_localized
|
||||
elif preinstall_path_generic.exists():
|
||||
pre_install_path = preinstall_path_generic
|
||||
else:
|
||||
pre_install_path = None
|
||||
if pre_install_path:
|
||||
with open(pre_install_path) as f:
|
||||
with pre_install_path.open() as f:
|
||||
infos["pre_install_html"] = emojize(
|
||||
pycmarkgfm.gfm_to_html(f.read()), language="alias"
|
||||
)
|
||||
|
||||
infos["screenshot"] = None
|
||||
|
||||
screenshots_folder = os.path.join(app_folder, "doc", "screenshots")
|
||||
screenshots_folder = app_folder / "doc" / "screenshots"
|
||||
|
||||
if os.path.exists(screenshots_folder):
|
||||
with os.scandir(screenshots_folder) as it:
|
||||
if screenshots_folder.exists():
|
||||
with screenshots_folder.iterdir() as it:
|
||||
for entry in it:
|
||||
ext = os.path.splitext(entry.name)[1].replace(".", "").lower()
|
||||
ext = entry.suffix.lower()
|
||||
if entry.is_file() and ext in ("png", "jpg", "jpeg", "webp", "gif"):
|
||||
with open(entry.path, "rb") as img_file:
|
||||
with entry.open("rb") as img_file:
|
||||
data = base64.b64encode(img_file.read()).decode("utf-8")
|
||||
infos["screenshot"] = (
|
||||
f"data:image/{ext};charset=utf-8;base64,{data}"
|
||||
|
|
Loading…
Add table
Reference in a new issue