From fe25e8d49dd052a4b3a30204447290b8eaa0dab0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Nov 2023 19:45:38 +0100 Subject: [PATCH 01/17] appstore: require to be level 1 to login ... --- store/app.py | 7 +- store/templates/wishlist_add.html | 2 + store/translations/fr/LC_MESSAGES/messages.mo | Bin 9362 -> 10150 bytes store/translations/fr/LC_MESSAGES/messages.po | 121 +++++++++++------- 4 files changed, 83 insertions(+), 47 deletions(-) diff --git a/store/app.py b/store/app.py index ac1fb8c..6a3c6d2 100644 --- a/store/app.py +++ b/store/app.py @@ -147,7 +147,7 @@ def star_app(app_id, action): 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 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") + "

" + _("Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.

'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( @@ -190,7 +190,7 @@ def add_to_wishlist(): if request.method == "POST": user = session.get("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") + "

" + _("Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.

'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( "wishlist_add.html", locale=get_locale(), @@ -414,6 +414,9 @@ def sso_login_callback(): uri_to_redirect_to_after_login = session.get("uri_to_redirect_to_after_login") + if "trust_level_100" not in user_data['groups'][0].split(','): + return _("Unfortunately, login was denied.") + "

" + _("Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.

'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 + session.clear() session["user"] = { "id": user_data["external_id"][0], diff --git a/store/templates/wishlist_add.html b/store/templates/wishlist_add.html index df02d59..6d1003f 100644 --- a/store/templates/wishlist_add.html +++ b/store/templates/wishlist_add.html @@ -27,6 +27,8 @@

{{ _("You must first login to be allowed to submit an app to the wishlist") }} +

+ {{ _("Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.

'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.") }}

{% endif %} diff --git a/store/translations/fr/LC_MESSAGES/messages.mo b/store/translations/fr/LC_MESSAGES/messages.mo index 1cac11a2404edca993ba2b1e105b6248b8b867c2..936963d036a7766461e940339d07e17ab247278b 100644 GIT binary patch delta 2231 zcmajfTWnNC7{Kx2vaMXivQX}qDFRzz*>*vcmQqqQq#%VR)+=H%-JP~4**&M6%T^HC zfSB-rCAc9mkQmfNs25%~F{qfR`{Ii+8WF|BkYJ*T22J$AsEPi+ZMnYKZ1*=aXJ_V{ z@0;D1w!E>U_OEE%O=LD`$s;DY7WP__X16A#4eJ2w6h(KpcV#(MTwK8+Rz4x?=N3Ccnju@3)0VpFk6qYF<(=}*Vs za1P1=CQVjq8P3P1aY}Wdth1Xa=HM{S$I~e5R&X!-tFLIJaR>Rl4-X)7)j3>&6_k@- zMLB5=d6ordp=6*5C7{j7OLg%!4|gJmR)w+g{V1P1g43{s#dS1Zqj3bk!)J+nfTzv$ ze_*%Au%2+YV;(#4Jho$uWADTVku#`MNQ~-j)c79WkC#v~9FLDa--aprPsGVT*;R)b zkjrumB?BMh1NbMN#5OJlP4x}RiGM~(<#_5fOu&nf*ws7rN^QgsP%`y5%J>u}$$E2< zA+-yaV=#;S%Y+jQbl@dijSI;4y|@F{;vtl~@jgoGKS#N&KcU8dQ06VFmQEF)hK^+l0}r0yaP;}w*e`E!+8g?mvJJcAPGM<{_vftNr%l3le9<-}WY z0zQdyhn^P6kb^9(LeeI8>P6hlc<~&KMh5=ns>%l%2~SSkfs(o*BuVN8loZAmu^Dbb z3H%^Rpu;Hh&!fz{fQ#`8GF45WBN=HxdA<^PR8%cv4<1LUaW6^+B9xOohnw&yp2aWm zLn1$*M=#+<((owb`*8>_U^jN%J^G{e3ht->J4%LpeucQd>L50$EMEf}9Sl@ZHj42j zmRsD3>+unkK#pM>zK&gZdCU|y>1+D?P%`i^1?L*691@FqK2dIs{W!6>-3zS_2TYjM z*@%{>3&wZ6DA1-i3T%+nLst7X2z@6L+N{ob{f?_WmpN9O!9g8_o?o4p2>mDsb-C%3t8a;(Tc10D_Iit9Y? zc72{7<&)aDS)G?ngF%~d`kai(<@U5{E0e1;B?v<(YdOxKli>`$HQ8Gpt!dVSUJwT9 z(dAglz4Zg>X4yXqZE$^ZbwaAFCvUA;>=H{DxhAx^J;|}844FV@t?Srqs&uMuNoi`$ zq;je5-Ej-sj2`f9--g7p#>mgz6C~Ql8f{Icz0@5)JuRJHm2T1Lmew^bE1T2h*olDaqwK{BUbjEXC`;0?H+{#GC27yCC^JF!&u5m-1O;p|t zN!IP<2_l~~>1s}Is?xS=pDf;}B-N)>tQS#nzjB7%3!YOgS7pRE{Z6PO_jqJqyFCZd*(8{AZ^oI|*#^V$PSmFHNdUoNApW&&>3 zjfpo^iab>(XQ|q?IjM5Wq0;39m23HS3-_lnp1#pEHz5%RB#MqTh6YM>EZf-jK0$OqKGli~h1{7U~9Y6735MV$B>b-rVP$THl7iP(S| z&x;+{g>KfDKQz{(o9)t+W=uyfYQ|?!GaN<@_y9HYx2PNYiCX#@T!As13&~0f>UcKl zbHx~oQ))?z5YmJNxF5Bm zXT#&KF`ND;q*#(bxpL4RNBuSPS_aDT1Rlr7XvXb)&7Lu834It~qo+|bOX0jqF%Q{` z96}xMLXFppR(y#mIEnhag~2kcvr_+D8vP99;X};F8RS`6$Ld=0O4Q4_7wy=NI`0(f z5j{eVUEX6VPN2@S@S_bgkv{~f#@UKtfPRyohMv^~X5kOyEe>YuPK!`Cauky=fD}h= zqGo&_=izhIyECRzp>gCbQ_JV>l97v%M&FG(-iVs0-%CSFb_FShjG&gTfO;3A4|T^w zs2jYC8sH1+JQG)aHCj#hbW{TJn9JD0_ZBBD&D!DT9-#kxh F{a^I$o#Oxi diff --git a/store/translations/fr/LC_MESSAGES/messages.po b/store/translations/fr/LC_MESSAGES/messages.po index 718e777..40338f9 100644 --- a/store/translations/fr/LC_MESSAGES/messages.po +++ b/store/translations/fr/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-09-19 17:04+0200\n" +"POT-Creation-Date: 2023-11-19 19:39+0100\n" "PO-Revision-Date: 2023-09-05 19:50+0200\n" "Last-Translator: FULL NAME \n" "Language: fr\n" @@ -18,59 +18,73 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.12.1\n" -#: app.py:140 +#: app.py:148 msgid "App %(app_id) not found" msgstr "L'app %(app_id) n'a pas été trouvée" -#: app.py:142 +#: app.py:150 msgid "You must be logged in to be able to star an app" msgstr "Vous devez être connecté·e pour mettre une app en favoris" -#: app.py:185 +#: app.py:150 app.py:193 app.py:418 templates/wishlist_add.html:31 +msgid "" +"Note that, due to various abuses, we restricted login on the app store to" +" 'trust level 1' users.

'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." +msgstr "" +"Notez que, suite à divers abus, la connexion nécessite maintenant d'être" +" 'trust level 1' sur le forum.

Le 'trust level 1' est obtenu après" +" avoir intéragit un minimum avec le forum, et plus précisémment: ouvrir au" +" moins 5 fils de discussion, lire au moins 30 messages, et passer au moins" +" 10 minutes à lire des messages." + +#: app.py:193 msgid "You must be logged in to submit an app to the wishlist" msgstr "Vous devez être connecté·e pour proposer une app pour la liste de souhaits" -#: app.py:200 +#: app.py:206 msgid "Invalid CSRF token, please refresh the form and try again" msgstr "Jeton CSRF invalide, prière de rafraîchir la page et retenter" -#: app.py:216 +#: app.py:222 msgid "App name should be at least 3 characters" msgstr "Le nom d'app devrait contenir au moins 3 caractères" -#: app.py:217 +#: app.py:223 msgid "App name should be less than 30 characters" msgstr "Le nom d'app devrait contenir moins de 30 caractères" -#: app.py:220 +#: app.py:226 msgid "App description should be at least 5 characters" msgstr "La description de l'app devrait contenir au moins 5 caractères" -#: app.py:224 +#: app.py:230 msgid "App description should be less than 100 characters" msgstr "La description de l'app devrait contenir moins de 100 caractères" -#: app.py:228 +#: app.py:234 msgid "Upstream code repo URL should be at least 10 characters" msgstr "L'URL du dépôt de code devrait contenir au moins 10 caractères" -#: app.py:232 +#: app.py:238 msgid "Upstream code repo URL should be less than 150 characters" msgstr "L'URL du dépôt de code devrait contenir moins de 150 caractères" -#: app.py:234 +#: app.py:240 msgid "Website URL should be less than 150 characters" msgstr "L'URL du site web devrait contenir moins de 150 caractères" -#: app.py:237 +#: app.py:243 msgid "App name contains special characters" msgstr "Le nom de l'app contiens des caractères spéciaux" -#: app.py:270 +#: app.py:276 msgid "An entry with the name %(slug) already exists in the wishlist" msgstr "Une entrée nommée $(slug) existe déjà dans la liste de souhaits" -#: app.py:295 +#: app.py:299 msgid "" "Failed to create the pull request to add the app to the wishlist ... " "please report the issue to the yunohost team" @@ -78,15 +92,19 @@ msgstr "" "Échec de la création de la demande d'intégration de l'app dans la liste " "de souhaits ... merci de rapport le problème à l'équipe YunoHost" -#: app.py:340 -#, python-format +#: app.py:348 msgid "" "Your proposed app has succesfully been submitted. It must now be " -"validated by the YunoHost team. You can track progress here: %(url)s" +"validated by the YunoHost team. You can track progress here: %(url)s" msgstr "" "Un demande d'intégration à la liste de souhaits a bien été créée pour " "cette app. Elle doit maintenant être validée par l'équipe YunoHost. Vous " -"pouvez suivre cette demande ici: %(url)s" +"pouvez suivre cette demande ici: %(url)s" + +#: app.py:418 +msgid "Unfortunately, login was denied." +msgstr "Malheureusement, la connexion a été refusée." #: templates/app.html:10 templates/catalog.html:23 #, python-format @@ -116,7 +134,9 @@ msgstr "" msgid "" "This app has been good quality according to our automatic tests over at " "least one year." -msgstr "Cette app est de bonne qualité d'après nos tests automatisés depuis au moins un an." +msgstr "" +"Cette app est de bonne qualité d'après nos tests automatisés depuis au " +"moins un an." #: templates/app.html:81 msgid "Try the demo" @@ -204,34 +224,45 @@ msgstr "Dépôt de code du paquet YunoHost" msgid "YunoHost app store" msgstr "Store d'apps de YunoHost" -#: templates/base.html:56 templates/base.html:149 templates/index.html:3 +#: templates/base.html:18 templates/base.html:113 templates/index.html:3 msgid "Home" msgstr "Accueil" -#: templates/base.html:65 templates/base.html:158 +#: templates/base.html:27 templates/base.html:122 msgid "Catalog" msgstr "Catalogue" -#: templates/base.html:71 templates/base.html:167 +#: templates/base.html:33 templates/base.html:131 msgid "Wishlist" msgstr "Liste de souhaits" -#: templates/base.html:84 templates/base.html:177 +#: templates/base.html:46 templates/base.html:141 msgid "YunoHost documentation" msgstr "Documentation YunoHost" -#: templates/base.html:92 templates/base.html:187 +#: templates/base.html:54 templates/base.html:151 msgid "Login using YunoHost's forum" msgstr "Se connecter via le forum YunoHost" -#: templates/base.html:122 templates/base.html:213 +#: templates/base.html:86 templates/base.html:179 msgid "Logout" msgstr "Se déconnecter" -#: templates/base.html:135 +#: templates/base.html:99 msgid "Toggle menu" msgstr "Activer le menu" +#: templates/base.html:197 +msgid "" +"Made with " +"using Flask and TailwindCSS - Source" +msgstr "" + #: templates/catalog.html:75 templates/catalog.html:80 msgid "Application Catalog" msgstr "Catalogue d'applications" @@ -253,17 +284,17 @@ msgid "Sort by" msgstr "Trier par" #: templates/catalog.html:123 templates/wishlist.html:45 -msgid "Alphabetical" -msgstr "Alphabétique" +#: templates/wishlist.html:78 +msgid "Popularity" +msgstr "Popularité" #: templates/catalog.html:124 msgid "Newest" msgstr "Nouveauté" #: templates/catalog.html:125 templates/wishlist.html:46 -#: templates/wishlist.html:78 -msgid "Popularity" -msgstr "Popularité" +msgid "Alphabetical" +msgstr "Alphabétique" #: templates/catalog.html:128 templates/wishlist.html:49 msgid "Requires to be logged-in" @@ -274,7 +305,7 @@ msgstr "Nécessite d'être connecté·e" msgid "Show only apps you starred" msgstr "Montrer uniquement mes favoris" -#: templates/catalog.html:155 templates/wishlist.html:152 +#: templates/catalog.html:155 templates/wishlist.html:154 msgid "No results found." msgstr "Aucun résultat trouvé." @@ -326,7 +357,7 @@ msgstr "" msgid "Suggest an app" msgstr "Suggérer une app" -#: templates/wishlist.html:71 templates/wishlist_add.html:57 +#: templates/wishlist.html:71 templates/wishlist_add.html:59 msgid "Name" msgstr "Nom" @@ -338,11 +369,11 @@ msgstr "Description" msgid "Official website" msgstr "Site officiel" -#: templates/wishlist.html:114 templates/wishlist.html:115 +#: templates/wishlist.html:115 templates/wishlist.html:116 msgid "Code repository" msgstr "Dépôt de code officiel" -#: templates/wishlist.html:127 templates/wishlist.html:128 +#: templates/wishlist.html:129 templates/wishlist.html:130 msgid "Star this app" msgstr "Étoiler cette app" @@ -354,11 +385,11 @@ msgstr "Suggérer une application à ajouter dans le catalogue de YunoHost" msgid "You must first login to be allowed to submit an app to the wishlist" msgstr "Vous devez être connecté·e pour proposer une app pour la liste de souhaits" -#: templates/wishlist_add.html:37 +#: templates/wishlist_add.html:39 msgid "Please check the license of the app your are proposing" msgstr "Merci de vérifier la licence de l'app que vous proposez" -#: templates/wishlist_add.html:40 +#: templates/wishlist_add.html:42 msgid "" "The YunoHost project will only package free/open-source software (with " "possible case-by-case exceptions for apps which are not-totally-free)" @@ -367,15 +398,15 @@ msgstr "" "(avec quelques possibles exceptions au cas-par-cas pour des apps qui ne " "sont pas entièrement libres)" -#: templates/wishlist_add.html:60 +#: templates/wishlist_add.html:62 msgid "App's description" msgstr "Description de l'app" -#: templates/wishlist_add.html:62 +#: templates/wishlist_add.html:64 msgid "Please be concise and focus on what the app does." msgstr "Prière de rester concis et de se concentrer sur ce que l'app fait." -#: templates/wishlist_add.html:62 +#: templates/wishlist_add.html:64 msgid "" "No need to repeat '[App] is ...'. No need to state that it is free/open-" "source or self-hosted (otherwise it wouldn't be packaged for YunoHost). " @@ -387,15 +418,15 @@ msgstr "" "Évitez les formulations marketing type 'le meilleur', ou les propriétés " "vagues telles que 'facile', 'simple', 'léger'." -#: templates/wishlist_add.html:64 +#: templates/wishlist_add.html:66 msgid "Project code repository" msgstr "Dépôt de code officiel" -#: templates/wishlist_add.html:67 +#: templates/wishlist_add.html:69 msgid "Project website" msgstr "Site officiel" -#: templates/wishlist_add.html:69 +#: templates/wishlist_add.html:71 msgid "" "Please *do not* just copy-paste the code repository URL. If the project " "has no proper website, then leave the field empty." @@ -403,7 +434,7 @@ msgstr "" "Prière de *ne pas* juste copier-coller l'URL du dépôt de code. Si le " "projet n'a pas de vrai site web, laissez le champ vide." -#: templates/wishlist_add.html:76 +#: templates/wishlist_add.html:78 msgid "Submit" msgstr "Envoyer" From e2c3b7fb1125cc64cc8c91f00541b4bfa4f9a630 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 19 Nov 2023 19:47:35 +0100 Subject: [PATCH 02/17] appstore: annd of course i didnt remove the test thingy --- store/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/app.py b/store/app.py index 6a3c6d2..6eeb5d4 100644 --- a/store/app.py +++ b/store/app.py @@ -414,7 +414,7 @@ def sso_login_callback(): uri_to_redirect_to_after_login = session.get("uri_to_redirect_to_after_login") - if "trust_level_100" not in user_data['groups'][0].split(','): + if "trust_level_1" not in user_data['groups'][0].split(','): return _("Unfortunately, login was denied.") + "

" + _("Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.

'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 session.clear() From fbbe1728f21644610f6a49ffb5f305f606ab9e81 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Dec 2023 19:00:53 +0100 Subject: [PATCH 03/17] Improve linter to also check notworking apps and graveyard entries --- tools/catalog_linter.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tools/catalog_linter.py b/tools/catalog_linter.py index 3c5453e..3657180 100755 --- a/tools/catalog_linter.py +++ b/tools/catalog_linter.py @@ -37,6 +37,12 @@ def get_wishlist() -> Dict[str, Dict[str, str]]: return toml.load(wishlist_path) +@cache +def get_graveyard() -> Dict[str, Dict[str, str]]: + wishlist_path = APPS_ROOT / "graveyard.toml" + return toml.load(wishlist_path) + + def validate_schema() -> Generator[str, None, None]: with open(APPS_ROOT / "schemas" / "apps.toml.schema.json", encoding="utf-8") as file: apps_catalog_schema = json.load(file) @@ -50,9 +56,6 @@ def check_app(app: str, infos: Dict[str, Any]) -> Generator[Tuple[str, bool], No yield "state is missing", True return - if infos["state"] != "working": - return - # validate that the app is not (anymore?) in the wishlist # we use fuzzy matching because the id in catalog may not be the same exact id as in the wishlist # some entries are ignore-hard-coded, because e.g. radarr an readarr are really different apps... @@ -66,6 +69,14 @@ def check_app(app: str, infos: Dict[str, Any]) -> Generator[Tuple[str, bool], No if wishlist_matches: yield f"app seems to be listed in wishlist: {wishlist_matches}", True + graveyard_matches = [ + grave + for grave in get_graveyard() + if SequenceMatcher(None, app, grave).ratio() > 0.9 + ] + if graveyard_matches: + yield f"app seems to be listed in graveyard: {graveyard_matches}", True + repo_name = infos.get("url", "").split("/")[-1] if repo_name != f"{app}_ynh": yield f"repo name should be {app}_ynh, not in {repo_name}", True From 10c41869358bb1182eb94a974e568f93eb2cd504 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Dec 2023 19:03:23 +0100 Subject: [PATCH 04/17] Fix false positive mailman<->mailman3 --- tools/catalog_linter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/catalog_linter.py b/tools/catalog_linter.py index 3657180..4c383a9 100755 --- a/tools/catalog_linter.py +++ b/tools/catalog_linter.py @@ -69,10 +69,12 @@ def check_app(app: str, infos: Dict[str, Any]) -> Generator[Tuple[str, bool], No if wishlist_matches: yield f"app seems to be listed in wishlist: {wishlist_matches}", True + ignored_graveyard_entries = ["mailman"] graveyard_matches = [ grave for grave in get_graveyard() - if SequenceMatcher(None, app, grave).ratio() > 0.9 + if grave not in ignored_graveyard_entries + and SequenceMatcher(None, app, grave).ratio() > 0.9 ] if graveyard_matches: yield f"app seems to be listed in graveyard: {graveyard_matches}", True From b1d6bdaeb15537dc21028c7a636d163f876fd177 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Dec 2023 16:44:32 +0100 Subject: [PATCH 05/17] Add description for graveyard.toml in README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 0b0f855..7d69dfd 100644 --- a/README.md +++ b/README.md @@ -67,3 +67,8 @@ App packagers should *not* manually set their apps' level. The levels of all the Applications with no recent activity and no active sign from maintainer may be flagged in `apps.toml` with the `package-not-maintained` antifeature tag to signify that the app is inactive and may slowly become outdated with respect to the upstream, or with respect to good packaging practices. It does **not** mean that the app is not working anymore. Feel free to contact the app group if you feel like taking over the maintenance of a currently unmaintained app! + +### `graveyard.toml` + +This file is for apps that are long-term not-working and unlikely to be ever revived + From 3519296bded71598391bc0db2d2489a5eac287d9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 4 Jan 2024 00:49:57 +0100 Subject: [PATCH 06/17] appstore/wishlist_add: add stupid check that user aint using boring marketing terms --- store/app.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/store/app.py b/store/app.py index 6eeb5d4..73a4b8f 100644 --- a/store/app.py +++ b/store/app.py @@ -218,6 +218,8 @@ def add_to_wishlist(): upstream = request.form["upstream"].strip().replace("\n", "") website = request.form["website"].strip().replace("\n", "") + boring_keywords_to_check_for_people_not_reading_the_instructions = ["free", "open source", "open-source", "self-hosted", "simple", "lightweight", "light-weight", "best", "most", "fast", "flexible", "puissante", "powerful", "secure"] + checks = [ (len(name) >= 3, _("App name should be at least 3 characters")), (len(name) <= 30, _("App name should be less than 30 characters")), @@ -242,6 +244,14 @@ def add_to_wishlist(): re.match(r"^[\w\.\-\(\)\ ]+$", name), _("App name contains special characters"), ), + ( + all(keyword not in description.lower() 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'.") + ), + ( + description.lower().split()[0] != name an description.lower().split()[1] not in ["is", "est"], + _("No need to repeat '{app} is'. Focus on what the app does.") + ) ] for check, errormsg in checks: From 8bb05de2ba7afbfc2218fb70b6ae3a46457431dc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 4 Jan 2024 01:55:31 +0100 Subject: [PATCH 07/17] store/wishlist_add: ask for link to LICENSE file --- store/app.py | 10 ++++++++++ store/templates/wishlist_add.html | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/store/app.py b/store/app.py index 73a4b8f..7a2b6f3 100644 --- a/store/app.py +++ b/store/app.py @@ -217,6 +217,7 @@ def add_to_wishlist(): description = request.form["description"].strip().replace("\n", "") upstream = request.form["upstream"].strip().replace("\n", "") website = request.form["website"].strip().replace("\n", "") + license = request.form["license"].strip().replace("\n", "") boring_keywords_to_check_for_people_not_reading_the_instructions = ["free", "open source", "open-source", "self-hosted", "simple", "lightweight", "light-weight", "best", "most", "fast", "flexible", "puissante", "powerful", "secure"] @@ -239,6 +240,14 @@ def add_to_wishlist(): len(upstream) <= 150, _("Upstream code repo URL should be less than 150 characters"), ), + ( + len(license) >= 10, + _("License URL should be at least 10 characters"), + ), + ( + len(license) <= 250, + _("License URL should be less than 250 characters"), + ), (len(website) <= 150, _("Website URL should be less than 150 characters")), ( re.match(r"^[\w\.\-\(\)\ ]+$", name), @@ -338,6 +347,7 @@ Proposed by **{session['user']['username']}** Website: {website} Upstream repo: {upstream} +License: {license} Description: {description} - [ ] Confirm app is self-hostable and generally makes sense to possibly integrate in YunoHost diff --git a/store/templates/wishlist_add.html b/store/templates/wishlist_add.html index 6d1003f..7fb2a56 100644 --- a/store/templates/wishlist_add.html +++ b/store/templates/wishlist_add.html @@ -66,6 +66,10 @@ + + + {{ _("The YunoHost project will only package free/open-source software (with possible case-by-case exceptions for apps which are not-totally-free)") }} + {{ _("Please *do not* just copy-paste the code repository URL. If the project has no proper website, then leave the field empty.") }} From e9e2eb17049f0ee68672b32411ccac2cd3ba3a62 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 4 Jan 2024 01:57:18 +0100 Subject: [PATCH 08/17] store/wishlist_add: ratelimit wishlist proposal to once every 15 days per user --- store/app.py | 12 ++++++++++-- store/templates/wishlist_add.html | 18 ++++++++++-------- store/utils.py | 23 ++++++++++++++++++++++- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/store/app.py b/store/app.py index 7a2b6f3..f0863b9 100644 --- a/store/app.py +++ b/store/app.py @@ -31,6 +31,8 @@ from utils import ( get_wishlist, get_stars, get_app_md_and_screenshots, + save_wishlist_submit_for_ratelimit, + check_wishlist_submit_ratelimit, ) app = Flask(__name__, static_url_path="/assets", static_folder="assets") @@ -199,7 +201,6 @@ def add_to_wishlist(): successmsg=None, errormsg=errormsg, ) - csrf_token = request.form["csrf_token"] if csrf_token != session.get("csrf_token"): @@ -222,6 +223,10 @@ def add_to_wishlist(): boring_keywords_to_check_for_people_not_reading_the_instructions = ["free", "open source", "open-source", "self-hosted", "simple", "lightweight", "light-weight", "best", "most", "fast", "flexible", "puissante", "powerful", "secure"] checks = [ + ( + check_wishlist_submit_ratelimit(session['user']['username']) is True, + _("Proposing wishlist additions is limited to once every 15 days per user.") + ), (len(name) >= 3, _("App name should be at least 3 characters")), (len(name) <= 30, _("App name should be less than 30 characters")), ( @@ -258,7 +263,7 @@ def add_to_wishlist(): _("Please focus on what the app does, without using marketing, fuzzy terms, or repeating that the app is 'free' and 'self-hostable'.") ), ( - description.lower().split()[0] != name an description.lower().split()[1] not in ["is", "est"], + description.lower().split()[0] != name and (len(description.split()) == 1 or description.lower().split()[1] not in ["is", "est"]), _("No need to repeat '{app} is'. Focus on what the app does.") ) ] @@ -369,6 +374,9 @@ Description: {description} "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, ) + + save_wishlist_submit_for_ratelimit(session['user']['username']) + return render_template( "wishlist_add.html", locale=get_locale(), diff --git a/store/templates/wishlist_add.html b/store/templates/wishlist_add.html index 7fb2a56..29e6d54 100644 --- a/store/templates/wishlist_add.html +++ b/store/templates/wishlist_add.html @@ -28,20 +28,22 @@ {{ _("You must first login to be allowed to submit an app to the wishlist") }}

+

+

{{ _("Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.

'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.") }}

- {% endif %} - -