From f6241d676dcfddb22b7da2c52f36a033ae0ee83d Mon Sep 17 00:00:00 2001 From: Eryk Michalak Date: Wed, 25 Sep 2024 16:03:55 +0000 Subject: [PATCH 01/28] Translated using Weblate (Polish) Currently translated at 100.0% (1600 of 1600 strings) Translation: pl-fe/pl-fe Translate-URL: https://hosted.weblate.org/projects/pl-fe/pl-fe/pl/ --- packages/pl-fe/src/locales/pl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/pl-fe/src/locales/pl.json b/packages/pl-fe/src/locales/pl.json index 27df9e342..50206f68d 100644 --- a/packages/pl-fe/src/locales/pl.json +++ b/packages/pl-fe/src/locales/pl.json @@ -1126,6 +1126,7 @@ "notification.pleroma:participation_request": "{name} cce wziąć udział w Twoim wydarzeniu", "notification.poll": "Głosowanie w którym brałeś(-aś) udział zakończyła się", "notification.reblog": "{name} podbił(a) Twój wpis", + "notification.reply": "{name} odpowiedział(a) na Twój wpis", "notification.severed_relationships": "Utracono połączenia z {name}", "notification.status": "{name} właśnie opublikował(a) wpis", "notification.update": "{name} zedytował(a) wpis, który podbiłeś(-aś)", @@ -1464,7 +1465,7 @@ "status.group": "Napisano w {group}", "status.group_mod_delete": "Usuń wpis z grupy", "status.hide_translation": "Ukryj tłumaczenie", - "status.interactions.dislikes": "", + "status.interactions.dislikes": "{count, plural, one {Negatywna reakcja} other {Negatywnych reakcji}}", "status.interactions.favourites": "{count, plural, one {Polubienie} few {Polubienia} other {Polubień}}", "status.interactions.quotes": "{count, plural, one {Cytat} few {Cytaty} other {Cytatów}}", "status.interactions.reblogs": "{count, plural, one {Podanie dalej} few {Podania dalej} other {Podań dalej}}", From f1107c3e192490d495d0c50deee41b45f8bc63d8 Mon Sep 17 00:00:00 2001 From: Aris Date: Tue, 8 Oct 2024 05:19:42 +0000 Subject: [PATCH 02/28] Translated using Weblate (French) Currently translated at 79.0% (1264 of 1599 strings) Translation: pl-fe/pl-fe Translate-URL: https://hosted.weblate.org/projects/pl-fe/pl-fe/fr/ --- packages/pl-fe/src/locales/fr.json | 565 +++++++++++++++-------------- 1 file changed, 283 insertions(+), 282 deletions(-) diff --git a/packages/pl-fe/src/locales/fr.json b/packages/pl-fe/src/locales/fr.json index 924758fb7..b59e560d8 100644 --- a/packages/pl-fe/src/locales/fr.json +++ b/packages/pl-fe/src/locales/fr.json @@ -1,17 +1,17 @@ { - "about.also_available": "Disponible en :", + "about.also_available": "Disponible en :", "accordion.collapse": "Réduire", "accordion.expand": "Développer", "account.add_or_remove_from_list": "Ajouter ou retirer des listes", "account.avatar.alt": "", "account.badges.bot": "Robot", - "account.birthday": "Naissance {date}", - "account.birthday_today": "Jour d'anniversaire !", + "account.birthday": "Né⋅e le {date}", + "account.birthday_today": "Jour d’anniversaire !", "account.bite": "", "account.bite.fail": "", "account.bite.success": "", "account.block": "Bloquer @{name}", - "account.block_domain": "Tout masquer venant de {domain}", + "account.block_domain": "Tout masquer en provenance de {domain}", "account.blocked": "Bloqué", "account.chat": "Chat with @{name}", "account.copy": "Copier le lien vers le profil", @@ -20,9 +20,9 @@ "account.domain_blocked": "Domaine masqué", "account.edit_profile": "Modifier le profil", "account.endorse": "Recommander sur le profil", - "account.endorse.success": "Vous présentez @{acct} sur votre profil", + "account.endorse.success": "Vous présentez désormais @{acct} sur votre profil", "account.familiar_followers": "Suivi par {accounts}", - "account.familiar_followers.empty": "Vous suivez à présent {name}.", + "account.familiar_followers.empty": "Personne que vous connaissez ne suit {name}.", "account.familiar_followers.more": "{count, plural, one {# other} other {# others}} que vous suivez", "account.follow": "Suivre", "account.followers": "Abonné⋅e⋅s", @@ -35,42 +35,42 @@ "account.last_status": "Dernière activité", "account.link_verified_on": "La propriété de ce lien a été vérifiée le {date}", "account.locked_info": "Ce compte est verrouillé. Son propriétaire approuve manuellement qui peut le ou la suivre.", - "account.login": "Log in", - "account.media": "Média", + "account.login": "Connexion", + "account.media": "Media", "account.member_since": "Inscription le {date}", "account.mention": "Mentionner", - "account.mute": "Masquer @{name}", - "account.muted": "Mise en silencieux", + "account.mute": "Mettre en sourdine @{name}", + "account.muted": "Mise en sourdine", "account.never_active": "Jamais", "account.posts": "Pouets", "account.posts_with_replies": "Pouets et réponses", - "account.profile": "Profil", + "account.profile": "Profile", "account.profile_external": "Voir le profil sur {domain}", "account.register": "Sign up", "account.remote_follow": "Suivre à distance", - "account.remove_from_followers": "Supprimer cet abonnement", + "account.remove_from_followers": "Supprimer cet⋅te abonné⋅e", "account.report": "Signaler @{name}", "account.requested": "En attente d’approbation. Cliquez pour annuler la requête", - "account.requested_small": "En attente d'approbation", - "account.rss_feed": "S'abonner au flux RSS", - "account.scrobbling": "", - "account.scrobbling.title": "", + "account.requested_small": "En attente d’approbation", + "account.rss_feed": "S’abonner au flux RSS", + "account.scrobbling": "En écoute : {song}", + "account.scrobbling.title": "{title} par {artist}", "account.search": "Recherche depuis @{name}", "account.search_self": "Chercher dans vos publications", "account.share": "Partager le profil de @{name}", "account.show_reblogs": "Afficher les partages de @{name}", "account.statuses": "", - "account.subscribe": "S'abonner aux notifications de @{name}", - "account.subscribe.failure": "Une erreur s'est produite en essayant de s'abonner à ce compte.", + "account.subscribe": "S’abonner aux notifications de @{name}", + "account.subscribe.failure": "Une erreur s’est produite en essayant de s’abonner à ce compte.", "account.subscribe.success": "Vous avez souscrit à ce compte.", "account.unblock": "Débloquer @{name}", "account.unblock_domain": "Ne plus masquer {domain}", "account.unendorse": "Ne plus recommander sur le profil", "account.unendorse.success": "Vous ne présentez plus @{acct}", "account.unfollow": "Ne plus suivre", - "account.unmute": "Ne plus masquer @{name}", + "account.unmute": "Ne plus mettre en sourdine @{name}", "account.unsubscribe": "Se désinscrire des notifications de @{name}", - "account.unsubscribe.failure": "Une erreur s'est produite en tentant de se désinscrire de ce compte.", + "account.unsubscribe.failure": "Une erreur s’est produite en tentant de se désinscrire de ce compte.", "account.unsubscribe.success": "Vous avez supprimé votre souscription à ce profil.", "account.verified": "Compte vérifié", "account_gallery.none": "Pas de média à montrer.", @@ -78,100 +78,100 @@ "account_moderation_modal.fields.account_role": "Rôle", "account_moderation_modal.fields.badges": "Badges personnalisés", "account_moderation_modal.fields.deactivate": "Désactiver le compte", - "account_moderation_modal.fields.delete": "Delete account", + "account_moderation_modal.fields.delete": "Supprimer le compte", "account_moderation_modal.fields.suggested": "Personnes suggérées", "account_moderation_modal.fields.verified": "Compte vérifié", "account_moderation_modal.info.id": "ID : {id}", "account_moderation_modal.roles.admin": "Admin", "account_moderation_modal.roles.moderator": "Modérateur⋅ice", - "account_moderation_modal.roles.user": "User", + "account_moderation_modal.roles.user": "Utilisateur⋅rice", "account_moderation_modal.title": "Modérer @{acct}", - "account_note.header": "", - "account_note.placeholder": "Pas de commentaire", + "account_note.header": "Note", + "account_note.placeholder": "Cliquer pour ajouter une note", "account_search.placeholder": "Rechercher un compte", "admin.announcements.action": "Créer une annonce", "admin.announcements.all_day": "Toute la journée", "admin.announcements.delete": "Effacer", - "admin.announcements.edit": "Editer", + "admin.announcements.edit": "Edit", "admin.announcements.ends_at": "Se termine à :", "admin.announcements.starts_at": "Commence à :", - "admin.awaiting_approval.empty_message": "Personne n'attend d'approbation. Quand un nouveau compte s'affiche, vous pouvez l'examiner ici.", - "admin.dashboard.registration_mode.approval_hint": "Tout le monde peut s'inscrire, mais leur compte ne s'active qu'après approbation.", + "admin.awaiting_approval.empty_message": "Personne n’attend d’approbation. Quand un nouveau compte s’affiche, vous pouvez l’examiner ici.", + "admin.dashboard.registration_mode.approval_hint": "Tout le monde peut s’inscrire, mais les comptes ne sont activés qu’après approbation.", "admin.dashboard.registration_mode.approval_label": "Approbation requise", - "admin.dashboard.registration_mode.closed_hint": "Personne ne peut s'inscrire. Vous pouvez toujours inviter des gens.", + "admin.dashboard.registration_mode.closed_hint": "Personne ne peut s’inscrire. Vous pouvez toujours inviter des gens.", "admin.dashboard.registration_mode.closed_label": "Fermé", - "admin.dashboard.registration_mode.open_hint": "N'importe qui peut rejoindre.", - "admin.dashboard.registration_mode.open_label": "Open", + "admin.dashboard.registration_mode.open_hint": "N’importe qui peut rejoindre.", + "admin.dashboard.registration_mode.open_label": "Ouvert", "admin.dashboard.registration_mode_label": "Inscriptions", - "admin.dashboard.settings_saved": "Paramètres sauvegardées !", + "admin.dashboard.settings_saved": "Paramètres sauvegardées !", "admin.dashcounters.domain_count_label": "pairs", "admin.dashcounters.mau_label": "comptes actifs mensuellement", "admin.dashcounters.retention_label": "rétention de comptes", - "admin.dashcounters.status_count_label": "posts", + "admin.dashcounters.status_count_label": "pouets", "admin.dashcounters.user_count_label": "comptes au total", "admin.dashwidgets.email_list_header": "Email list", - "admin.dashwidgets.software_header": "Software", - "admin.domains.action": "", - "admin.domains.delete": "", - "admin.domains.edit": "", - "admin.domains.name": "", - "admin.domains.private": "", - "admin.domains.public": "", + "admin.dashwidgets.software_header": "Logiciel", + "admin.domains.action": "Créer un domaine", + "admin.domains.delete": "Effacer", + "admin.domains.edit": "Modifier", + "admin.domains.name": "Domaine :", + "admin.domains.private": "Privé", + "admin.domains.public": "Public", "admin.domains.resolve.fail_label": "", - "admin.domains.resolve.last_checked": "", + "admin.domains.resolve.last_checked": "Dernièreconsultation : {date}", "admin.domains.resolve.pending_label": "", "admin.domains.resolve.success_label": "", "admin.edit_announcement.created": "Annonce créée", "admin.edit_announcement.deleted": "Annonce supprimée", - "admin.edit_announcement.fields.all_day_hint": "Si cette case est cochée, seules les dates de la plage horaire seront affichées.", - "admin.edit_announcement.fields.all_day_label": "Événement d'une journée", + "admin.edit_announcement.fields.all_day_hint": "Si cette case est cochée, seules les dates de la plage horaire seront affichées", + "admin.edit_announcement.fields.all_day_label": "Événement sur une journée entière", "admin.edit_announcement.fields.content_label": "Contenu", - "admin.edit_announcement.fields.content_placeholder": "Contenu de l'annonce", + "admin.edit_announcement.fields.content_placeholder": "Contenu de l’annonce", "admin.edit_announcement.fields.end_time_label": "Date de fin", - "admin.edit_announcement.fields.end_time_placeholder": "L'annonce se termine le :", + "admin.edit_announcement.fields.end_time_placeholder": "L’annonce se termine le :", "admin.edit_announcement.fields.start_time_label": "Date de début", - "admin.edit_announcement.fields.start_time_placeholder": "L'annonce commence le :", + "admin.edit_announcement.fields.start_time_placeholder": "L’annonce commence le :", "admin.edit_announcement.save": "Sauvegarder", - "admin.edit_announcement.updated": "Annonce éditée", - "admin.edit_domain.created": "", - "admin.edit_domain.deleted": "", - "admin.edit_domain.fields.domain_label": "", - "admin.edit_domain.fields.domain_placeholder": "", - "admin.edit_domain.fields.public_hint": "", - "admin.edit_domain.fields.public_label": "", - "admin.edit_domain.save": "", - "admin.edit_domain.updated": "", + "admin.edit_announcement.updated": "Annonce modifée", + "admin.edit_domain.created": "Domaine créé", + "admin.edit_domain.deleted": "Domaine supprimé", + "admin.edit_domain.fields.domain_label": "Domaine", + "admin.edit_domain.fields.domain_placeholder": "Nom de domaine", + "admin.edit_domain.fields.public_hint": "Si cette option est cochée, tout le monde peut enregistrer un compte avec ce domaine", + "admin.edit_domain.fields.public_label": "Public", + "admin.edit_domain.save": "Enregistrer", + "admin.edit_domain.updated": "Domaine modifié", "admin.edit_rule.created": "", "admin.edit_rule.deleted": "", "admin.edit_rule.fields.priority_label": "", "admin.edit_rule.fields.priority_placeholder": "", "admin.edit_rule.fields.text_label": "", "admin.edit_rule.fields.text_placeholder": "", - "admin.edit_rule.save": "", + "admin.edit_rule.save": "Enregistrer", "admin.edit_rule.updated": "", "admin.latest_accounts_panel.more": "Cliquez pour voir {count, plural, one {# account} other {# accounts}}", - "admin.latest_accounts_panel.title": "Derniers comptes", - "admin.moderation_log.empty_message": "Vous n'avez pas encore effectué d'acte de modération. Quand vous le ferez, un historique sera montrée ici.", - "admin.relays.add.fail": "", - "admin.relays.add.success": "", - "admin.relays.deleted": "", - "admin.relays.followed_back": "", - "admin.relays.new.follow": "", - "admin.relays.new.url_placeholder": "", - "admin.relays.unfollow": "", - "admin.relays.url": "", + "admin.latest_accounts_panel.title": "Comptes les plus récents", + "admin.moderation_log.empty_message": "Vous n’avez pas encore effectué d’action de modération. Quand vous le ferez, un historique disponible montrée ici.", + "admin.relays.add.fail": "Échec de suivi du relai de cette instance", + "admin.relays.add.success": "Relai de l’instance suivi avec succès", + "admin.relays.deleted": "Le relai n’est plus suivi", + "admin.relays.followed_back": "Relai suivi en retour", + "admin.relays.new.follow": "Suivre", + "admin.relays.new.url_placeholder": "Adresse URL du relais", + "admin.relays.unfollow": "Ne plus suivre", + "admin.relays.url": "Adresse URL de l’instance :", "admin.reports.actions.close": "Fermer", - "admin.reports.actions.view_status": "View post", - "admin.reports.empty_message": "Il n'y a pas de rapport ouvert. Si un profil est signalé, il se présentera ici.", + "admin.reports.actions.view_status": "Voir la publication", + "admin.reports.empty_message": "Il n’y a aucun rapport ouvert. Si un profil est signalé, il sera visible ici.", "admin.reports.report_closed_message": "Le signalement de @{name} a été fermé", - "admin.reports.report_title": "Report on {acct}", - "admin.rule.priority": "", + "admin.reports.report_title": "Signalement de {acct}", + "admin.rule.priority": "Priorité :", "admin.rules.action": "", - "admin.rules.delete": "", - "admin.rules.edit": "", + "admin.rules.delete": "Effacer", + "admin.rules.edit": "Edit", "admin.software.backend": "Backend", "admin.software.frontend": "Frontend", - "admin.statuses.actions.delete_status": "Delete post", + "admin.statuses.actions.delete_status": "Supprimer la publication", "admin.statuses.actions.mark_status_not_sensitive": "Marquer la publication comme non sensible", "admin.statuses.actions.mark_status_sensitive": "Marquer la publication comme sensible", "admin.statuses.status_deleted_message": "Publication de @{acct} supprimée", @@ -179,89 +179,89 @@ "admin.statuses.status_marked_message_sensitive": "Publication de @{acct} marquée comme sensible", "admin.theme.title": "Thème", "admin.user_index.empty": "Aucun profil trouvé.", - "admin.user_index.search_input_placeholder": "Qui recherchez-vous ?", + "admin.user_index.search_input_placeholder": "Qui recherchez-vous ?", "admin.users.actions.deactivate_user": "Désactiver @{name}", - "admin.users.actions.delete_user": "Delete @{name}", - "admin.users.actions.demote_to_moderator_message": "@{acct} a été rétrogradé⋅e au statut de modérateur⋅ice", - "admin.users.actions.demote_to_user_message": "@{acct} a été rétrogradé⋅e au statut d'utilisateur⋅ice", - "admin.users.actions.promote_to_admin_message": "@{acct} a été promu⋅e au statut d'administrateur⋅ice", + "admin.users.actions.delete_user": "Supprimer le compte @{name}", + "admin.users.actions.demote_to_moderator_message": "@{acct} a été démis⋅e du statut de modérateur⋅ice", + "admin.users.actions.demote_to_user_message": "@{acct} a été rétrogradé⋅e au statut d’utilisateur⋅ice", + "admin.users.actions.promote_to_admin_message": "@{acct} a été promu⋅e au statut d’administrateur⋅ice", "admin.users.actions.promote_to_moderator_message": "@{acct} a été promu⋅e au statut de modérateur⋅ice", "admin.users.badges_saved_message": "Badges personnalisés mis à jour.", "admin.users.remove_donor_message": "Le statut de donateur de @{acct} a été supprimé", "admin.users.set_donor_message": "Le statut de donateur a été octroyé à @{acct}", - "admin.users.user_deactivated_message": "@{acct} a été désactivé⋅e", - "admin.users.user_deleted_message": "@{acct} a été supprimé⋅e", + "admin.users.user_deactivated_message": "@{acct} : le compte a été désactivé", + "admin.users.user_deleted_message": "@{acct} : le compte a été supprimé", "admin.users.user_suggested_message": "@{acct} a été suggéré⋅e", - "admin.users.user_unsuggested_message": "@{acct} a été supprimé⋅e des suggestions", + "admin.users.user_unsuggested_message": "@{acct} : le compte a été supprimé des suggestions", "admin.users.user_unverified_message": "@{acct} a été supprimé⋅e des comptes vérifiés", "admin.users.user_verified_message": "@{acct} a été ajouté⋅e aux comptes vérifiés", - "admin_nav.awaiting_approval": "Awaiting Approval", - "admin_nav.dashboard": "Dashboard", - "admin_nav.reports": "Reports", - "alert.unexpected.body": "Nous vous prions de nous excuser pour l'interruption. Si le problème persiste, veuillez contacter notre équipe de support. Vous pouvez également essayer de {clearCookies} (ceci vous déconnectera).", - "alert.unexpected.browser": "Browser", + "admin_nav.awaiting_approval": "En attente d’approbation", + "admin_nav.dashboard": "Tableau de bord", + "admin_nav.reports": "Signalements", + "alert.unexpected.body": "Nous vous prions de nous excuser pour cette interruption. Si le problème persiste, veuillez contacter notre équipe de support. Vous pouvez également essayer de {clearCookies} (ceci vous déconnectera).", + "alert.unexpected.browser": "Navigateur", "alert.unexpected.clear_cookies": "supprimer les cookies et les données de navigation", - "alert.unexpected.links.help": "Centre d'aide", + "alert.unexpected.links.help": "Centre d’aide", "alert.unexpected.links.status": "Status", - "alert.unexpected.links.support": "Support", + "alert.unexpected.links.support": "Assistance", "alert.unexpected.message": "Une erreur inattendue s’est produite.", - "alert.unexpected.return_home": "Return Home", - "alert.unexpected.submit_feedback": "", - "alert.unexpected.thanks": "", + "alert.unexpected.return_home": "Retour en page d’accueil", + "alert.unexpected.submit_feedback": "Faire un retour", + "alert.unexpected.thanks": "Merci pour votre retour !", "aliases.account.add": "Créer un alias", - "aliases.account_label": "Ancien compte :", - "aliases.aliases_list_delete": "Délier l'alias", + "aliases.account_label": "Ancien compte :", + "aliases.aliases_list_delete": "Délier l’alias", "aliases.search": "Recherche votre ancien compte", "aliases.success.add": "Alias de compte créé avec succès", "aliases.success.remove": "Alias de compte supprimé avec succès", "announcements.title": "Annonces", - "app_create.name_label": "Nom de l'application", - "app_create.name_placeholder": "e.g. 'Soapbox'", + "app_create.name_label": "Nom de l’application", + "app_create.name_placeholder": "e.g. 'pl-fe'", "app_create.redirect_uri_label": "URIs de redirection", "app_create.restart": "Créer une autre application", "app_create.results.app_label": "Application", - "app_create.results.explanation_text": "Vous avez créé une nouvelle application et jeton ! Veuillez copier les identifiants quelque part ; vous ne les verrez plus après avoir navigué hors de cette page.", + "app_create.results.explanation_text": "Vous avez créé une nouvelle application et jeton ! Veuillez copier les identifiants quelque part ; vous ne les verrez plus après avoir quitté cette page.", "app_create.results.explanation_title": "Application créée avec succès", - "app_create.results.token_label": "OAuth token", + "app_create.results.token_label": "jeton OAuth (token)", "app_create.scopes_label": "Objectifs", "app_create.scopes_placeholder": "e.g. 'lire, écrire, suivre'", "app_create.submit": "Créer une application", - "app_create.website_label": "Website", - "auth.awaiting_approval": "Votre compte est en attente d'approbation", + "app_create.website_label": "Site web", + "auth.awaiting_approval": "Votre compte est en attente d’approbation", "auth.invalid_credentials": "Mauvais identifiant ou mot de passe", "auth.logged_out": "Session déconnectée.", "authorize.success": "Approuvé", "backups.actions.create": "Créer une sauvegarde", - "backups.download": "", - "backups.empty_message": "Pas de sauvegarde trouvée. {action}", - "backups.empty_message.action": "Créer maintenant ?", + "backups.download": "Télécharger", + "backups.empty_message": "Aucune sauvegarde trouvée. {action}", + "backups.empty_message.action": "Créer maintenant ?", "backups.pending": "En attente", "badge_input.placeholder": "Enter a badge…", "birthday_panel.title": "Anniversaires", - "birthdays_modal.empty": "Aucun de vos amis n'a son anniversaire aujourd'hui.", - "bookmark_folders.add.fail": "", - "bookmark_folders.add.success": "", - "bookmark_folders.all_bookmarks": "", - "bookmark_folders.edit.fail": "", - "bookmark_folders.edit.success": "", - "bookmark_folders.new.create_title": "", - "bookmark_folders.new.title_placeholder": "", - "bookmarks.delete_folder": "", - "bookmarks.delete_folder.fail": "", - "bookmarks.delete_folder.success": "", - "bookmarks.edit_folder": "", + "birthdays_modal.empty": "Aucun de vos amis n’a son anniversaire aujourd’hui.", + "bookmark_folders.add.fail": "Échec de création d’un dossier de signets", + "bookmark_folders.add.success": "Le dossier de signets créé avec succès", + "bookmark_folders.all_bookmarks": "Tous les signets", + "bookmark_folders.edit.fail": "Échec de la modification du dossier de signets", + "bookmark_folders.edit.success": "Le dossier de signets modifié avec succès", + "bookmark_folders.new.create_title": "Ajouter un dossier", + "bookmark_folders.new.title_placeholder": "Nouveau titre du dossier", + "bookmarks.delete_folder": "Supprimer le dossier", + "bookmarks.delete_folder.fail": "Échec de la suppression du dossier", + "bookmarks.delete_folder.success": "Dossier supprimé", + "bookmarks.edit_folder": "Modification du dossier", "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour passer ceci, la prochaine fois", - "boost_modal.title": "Republier ?", - "bundle_column_error.body": "", - "bundle_column_error.retry": "", - "bundle_column_error.title": "", + "boost_modal.title": "Republier ?", + "bundle_column_error.body": "Nous avons rencontré un problème lors du chargement de la page.", + "bundle_column_error.retry": "Essayer de nouveau", + "bundle_column_error.title": "Erreur de réseau", "card.back.label": "Retour", "chat.actions.send": "Envoyer", - "chat.failed_to_send": "L'envoi du message a échoué.", + "chat.failed_to_send": "L’envoi du message a échoué.", "chat.input.placeholder": "Écrire un message", "chat.new_message.title": "Nouveau message", "chat.page_settings.accepting_messages.label": "Permettre aux autres de commencer une discussion avec vous", - "chat.page_settings.play_sounds.label": "Émettre un son à la réception d'un message", + "chat.page_settings.play_sounds.label": "Émettre un son à la réception d’un message", "chat.page_settings.preferences": "Préférences", "chat.page_settings.privacy": "Vie privée", "chat.page_settings.submit": "Sauvegarder", @@ -276,9 +276,9 @@ "chat_list_item.blocked_you": "Cette personne vous bloque", "chat_list_item.blocking": "Vous avez bloqué cette personne", "chat_message_list.blocked": "Vous bloquez cette personne", - "chat_message_list.blocked_by": "", + "chat_message_list.blocked_by": "Vous êtes bloqué par", "chat_message_list.network_failure.action": "Essayer à nouveau", - "chat_message_list.network_failure.subtitle": "Une erreur de réseau s'est produite.", + "chat_message_list.network_failure.subtitle": "Une erreur de réseau s’est produite.", "chat_message_list.network_failure.title": "Oups !", "chat_message_list_intro.actions.accept": "Accepter", "chat_message_list_intro.actions.leave_chat": "Quitter la conversation", @@ -288,10 +288,10 @@ "chat_message_list_intro.leave_chat.confirm": "Quitter la conversation", "chat_message_list_intro.leave_chat.heading": "Quitter la conversation", "chat_message_list_intro.leave_chat.message": "Êtes-vous sûr⋅e de quitter cette conversation ? Les messages seront supprimés pour vous et cette discussion sera supprimée de votre boîte de réception.", - "chat_pane.blankslate.action": "", + "chat_pane.blankslate.action": "Envoyer un message à quelqu’un", "chat_pane.blankslate.body": "", - "chat_pane.blankslate.title": "", - "chat_search.blankslate.body": "Chercher quelqu'un avec qui discuter.", + "chat_pane.blankslate.title": "Aucun message pour l’instant", + "chat_search.blankslate.body": "Chercher quelqu’un avec qui discuter.", "chat_search.blankslate.title": "Commencer une discussion", "chat_search.empty_results_blankslate.body": "Essayez de chercher un autre nom.", "chat_search.empty_results_blankslate.title": "Aucun résultat trouvé", @@ -326,33 +326,33 @@ "chats.actions.delete_for_me": "", "chats.actions.more": "Plus", "chats.actions.report": "Report user", - "chats.dividers.today": "Aujourd'hui", - "chats.main.blankslate.new_chat": "Envoyer à message à quelqu'un", - "chats.main.blankslate.subtitle": "Chercher quelqu'un à qui parler", + "chats.dividers.today": "Aujourd’hui", + "chats.main.blankslate.new_chat": "Envoyer à message à quelqu’un", + "chats.main.blankslate.subtitle": "Chercher quelqu’un à qui parler", "chats.main.blankslate.title": "Pas de message pour le moment", - "chats.main.blankslate_with_chats.subtitle": "Sélectionner depuis l'une de vos conversations ou créer un nouveau message.", + "chats.main.blankslate_with_chats.subtitle": "Sélectionner depuis l’une de vos conversations ou créer un nouveau message.", "chats.main.blankslate_with_chats.title": "Sélectionner une discussion", "chats.search_placeholder": "Start a chat with…", "column.admin.announcements": "Annonces", - "column.admin.awaiting_approval": "En attente d'approbation", + "column.admin.awaiting_approval": "En attente d’approbation", "column.admin.create_announcement": "Créer une annonce", - "column.admin.create_domain": "", + "column.admin.create_domain": "Créer un domaine", "column.admin.create_rule": "", "column.admin.dashboard": "Dashboard", - "column.admin.domains": "", - "column.admin.edit_announcement": "Éditer une annonce", - "column.admin.edit_domain": "", + "column.admin.domains": "Domaines", + "column.admin.edit_announcement": "Modifier une annonce", + "column.admin.edit_domain": "Modifier le domaine", "column.admin.edit_rule": "", "column.admin.moderation_log": "Historique de modération", - "column.admin.relays": "", + "column.admin.relays": "Relais de l’instance", "column.admin.reports": "Reports", "column.admin.reports.menu.moderation_log": "Historique de modération", "column.admin.rules": "", "column.admin.users": "Utilisateurs", "column.aliases": "Account aliases", - "column.aliases.create_error": "Erreur à la création de l'alias", + "column.aliases.create_error": "Erreur à la création de l’alias", "column.aliases.delete": "Delete", - "column.aliases.delete_error": "Erreur à la suppression de l'alias", + "column.aliases.delete_error": "Erreur à la suppression de l’alias", "column.aliases.subheading_add_new": "Ajouter un nouvel alias", "column.aliases.subheading_aliases": "Alias actuels", "column.app_create": "Créer une application", @@ -369,12 +369,12 @@ "column.developers.service_worker": "Service Worker", "column.direct": "Messages privés", "column.directory": "Parcourir les profils", - "column.dislikes": "Je n'aime pas", + "column.dislikes": "Je n’aime pas", "column.domain_blocks": "Domaines cachés", - "column.draft_statuses": "", - "column.edit_profile": "Éditer le profil", - "column.event_map": "Emplacement de l'évènement", - "column.event_participants": "Participants à l'évènement", + "column.draft_statuses": "Brouillons", + "column.edit_profile": "Modifier le profil", + "column.event_map": "Emplacement de l’évènement", + "column.event_participants": "Participants à l’évènement", "column.events": "Évènements", "column.export_data": "Export data", "column.familiar_followers": "Des personnes que vous connaissez suivant aussi {name}", @@ -390,7 +390,7 @@ "column.filters.delete_error": "Erreur en supprimant le filtre", "column.filters.drop_header": "Supprimer au lieu de cacher", "column.filters.drop_hint": "Les publication filtrées disparaîtront de façon irréversible et ce même si le filtre est supprimé ultérieurement", - "column.filters.edit": "Éditer", + "column.filters.edit": "Modifier", "column.filters.expiration.1800": "30 minutes", "column.filters.expiration.21600": "6 heures", "column.filters.expiration.3600": "1 heure", @@ -401,7 +401,7 @@ "column.filters.expires": "Expire après", "column.filters.hide_header": "Cacher entièrement", "column.filters.hide_hint": "Cacher entièrement le contenu filtré au lieu de montrer un avertissement", - "column.filters.home_timeline": "Fil d'accueil", + "column.filters.home_timeline": "Fil d’accueil", "column.filters.keyword": "Mot-clé ou phrase", "column.filters.keywords": "Mot-clés ou phrases", "column.filters.notifications": "Notifications", @@ -438,64 +438,64 @@ "column.quotes": "Citations", "column.reactions": "Réactions", "column.reblogs": "Partages", - "column.registration": "", + "column.registration": "Sign up", "column.scheduled_statuses": "Publications programmées", "column.search": "Rechercher", "column.settings_store": "Magasin de paramètres", "column.test": "Fil de test", - "column_forbidden.body": "Vous n'avez pas la permission d'accéder à cette page.", + "column_forbidden.body": "Vous n’avez pas la permission d’accéder à cette page.", "column_forbidden.title": "Accès interdit", "common.cancel": "Annuler", - "compare_history_modal.header": "Éditer l'historique", + "compare_history_modal.header": "Modifier l’historique", "compose.character_counter.title": "{chars} sur {maxChars} {maxChars, plural, one {caractère} other {caractères}} {maxChars, plural, one {utilisé} other {utilisés}}", "compose.edit_success": "Votre publication a été modifiée", - "compose.invalid_schedule": "Vous devez programmer votre publication avec un délai de 5 minutes au minimum", - "compose.language_dropdown.add_language": "", - "compose.language_dropdown.delete_language": "", + "compose.invalid_schedule": "Vous devez programmer votre publication avec un délai de 5 minutes au minimum.", + "compose.language_dropdown.add_language": "Ajouter une langue", + "compose.language_dropdown.delete_language": "Supprimer une langue", "compose.language_dropdown.more_languages": "", - "compose.language_dropdown.prompt": "", - "compose.language_dropdown.search": "", - "compose.language_dropdown.suggestion": "", - "compose.reply_group_indicator.message": "", - "compose.scheduled_success": "", + "compose.language_dropdown.prompt": "Sélectionner une langue", + "compose.language_dropdown.search": "Chercher une langue…", + "compose.language_dropdown.suggestion": "{language} (détecté)", + "compose.reply_group_indicator.message": "Publier dans le groupe {groupLink}", + "compose.scheduled_success": "Votre publication a été programmée", "compose.submit_success": "Votre publication a été envoyée !", "compose_event.create": "Créer", "compose_event.edit_success": "Votre évènement a été édité", "compose_event.fields.approval_required": "Approuver les requêtes de participation manuellement", - "compose_event.fields.banner_hint": "", - "compose_event.fields.banner_label": "Bannière d'évènement", - "compose_event.fields.description_label": "Description de l'évènement", + "compose_event.fields.banner_hint": "PNG, GIF ou JPG. De préférence au format paysage.", + "compose_event.fields.banner_label": "Bannière d’évènement", + "compose_event.fields.description_label": "Description de l’évènement", "compose_event.fields.description_placeholder": "Description", - "compose_event.fields.end_time_label": "Date de fin de l'évènement", - "compose_event.fields.end_time_placeholder": "L'évènement se finit le…", - "compose_event.fields.has_end_time": "L'évènement a une date de fin", - "compose_event.fields.location_label": "Lieu de l'évènement", - "compose_event.fields.name_label": "Nom de l'évènement", + "compose_event.fields.end_time_label": "Date de fin de l’évènement", + "compose_event.fields.end_time_placeholder": "L’évènement se finit le…", + "compose_event.fields.has_end_time": "L’évènement a une date de fin", + "compose_event.fields.location_label": "Lieu de l’évènement", + "compose_event.fields.name_label": "Nom de l’évènement", "compose_event.fields.name_placeholder": "Nom", - "compose_event.fields.start_time_label": "Date de début de l'évènement", - "compose_event.fields.start_time_placeholder": "L'évènement commence le…", + "compose_event.fields.start_time_label": "Date de début de l’évènement", + "compose_event.fields.start_time_placeholder": "L’évènement commence le…", "compose_event.participation_requests.authorize": "Autoriser", "compose_event.participation_requests.authorize_success": "Utilisateur⋅ice accepté⋅e", "compose_event.participation_requests.reject": "Rejeter", "compose_event.participation_requests.reject_success": "Utilisateur⋅ice rejeté⋅e", "compose_event.reset_location": "Réinitaliser le lieu", "compose_event.submit_success": "Votre évènement a été créé", - "compose_event.tabs.edit": "Éditer les détails", + "compose_event.tabs.edit": "Modifier les détails", "compose_event.tabs.pending": "Gérer les requêtes", "compose_event.update": "Mettre à jour", - "compose_event.upload_banner": "Téléverser une bannière d'évènement", - "compose_form.content_type.change": "", + "compose_event.upload_banner": "Téléverser une bannière d’évènement", + "compose_form.content_type.change": "Changer de type de contenu", "compose_form.direct_message_warning": "Ce pouet sera uniquement envoyé aux personnes mentionnées. Cependant, l’administration de votre instance et des instances réceptrices pourront inspecter ce message.", "compose_form.event_placeholder": "Publier cet évènement", "compose_form.hashtag_warning": "Ce pouet ne sera pas listé dans les recherches par hashtag car sa visibilité est réglée sur \"non listé\". Seuls les pouets avec une visibilité \"publique\" peuvent être recherchés par hashtag.", - "compose_form.lexical.create_horizontal_line": "", - "compose_form.lexical.format_bold": "", - "compose_form.lexical.format_italic": "", - "compose_form.lexical.format_strikethrough": "", - "compose_form.lexical.format_underline": "", - "compose_form.lexical.insert_code_block": "", - "compose_form.lexical.insert_link": "", - "compose_form.lexical.upload_media": "", + "compose_form.lexical.create_horizontal_line": "Créer une ligne horizontale", + "compose_form.lexical.format_bold": "Gras", + "compose_form.lexical.format_italic": "Italique", + "compose_form.lexical.format_strikethrough": "Barré", + "compose_form.lexical.format_underline": "Souligné", + "compose_form.lexical.insert_code_block": "Insérer un bloc de code", + "compose_form.lexical.insert_link": "Insérer un lien", + "compose_form.lexical.upload_media": "Téléverser un média", "compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.", "compose_form.lock_disclaimer.lock": "verrouillé", "compose_form.markdown.marked": "Publication avec Markdown activé", @@ -511,15 +511,15 @@ "compose_form.poll.remove_option": "Supprimer ce choix", "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices", "compose_form.poll.switch_to_single": "Change poll to allow for a single choice", - "compose_form.poll_placeholder": "Ajouter un sujet de sondage...", + "compose_form.poll_placeholder": "Ajouter un sujet de sondage…", "compose_form.publish": "Pouet", "compose_form.publish_loud": "{publish} !", "compose_form.save_changes": "Sauvegarder les changements", "compose_form.schedule": "Planifier", "compose_form.scheduled_statuses.click_here": "Cliquez ici", "compose_form.scheduled_statuses.message": "Vous avez planifié des publications. {click_here} pour les voir.", - "compose_form.sensitive.marked": "", - "compose_form.sensitive.unmarked": "", + "compose_form.sensitive.marked": "Le média est marqué comme sensible", + "compose_form.sensitive.unmarked": "Le média n’est pas marqué comme sensible", "compose_form.spoiler.marked": "Le texte est caché derrière un avertissement", "compose_form.spoiler.unmarked": "Le texte n’est pas caché", "compose_form.spoiler_placeholder": "Écrivez ici votre avertissement", @@ -531,21 +531,21 @@ "confirmations.admin.deactivate_user.heading": "Désactiver @{acct}", "confirmations.admin.deactivate_user.message": "Vous allez désactiver @{acct}. Cette action est réversible.", "confirmations.admin.delete_announcement.confirm": "Supprimer", - "confirmations.admin.delete_announcement.heading": "Supprimer l'annonce", - "confirmations.admin.delete_announcement.message": "Souhaitez-vous vraiment supprimer l'annonce ?", - "confirmations.admin.delete_domain.confirm": "", - "confirmations.admin.delete_domain.heading": "", - "confirmations.admin.delete_domain.message": "", + "confirmations.admin.delete_announcement.heading": "Supprimer l’annonce", + "confirmations.admin.delete_announcement.message": "Souhaitez-vous vraiment supprimer l’annonce ?", + "confirmations.admin.delete_domain.confirm": "Effacer", + "confirmations.admin.delete_domain.heading": "Supprimer le domaine", + "confirmations.admin.delete_domain.message": "Êtes vous certain de vouloir supprimer ce domaine ?", "confirmations.admin.delete_local_user.checkbox": "Je comprends que je vais supprimer un utilisateur local.", - "confirmations.admin.delete_rule.confirm": "", + "confirmations.admin.delete_rule.confirm": "Supprimer", "confirmations.admin.delete_rule.heading": "", "confirmations.admin.delete_rule.message": "", "confirmations.admin.delete_status.confirm": "Delete post", "confirmations.admin.delete_status.heading": "Delete post", - "confirmations.admin.delete_status.message": "Vous allez supprimer une publication de @{acct}. Cette action est irréversible", + "confirmations.admin.delete_status.message": "Vous allez supprimer une publication de @{acct}. Cette action est irréversible.", "confirmations.admin.delete_user.confirm": "Delete @{name}", "confirmations.admin.delete_user.heading": "Delete @{acct}", - "confirmations.admin.delete_user.message": "Vous allez supprimer le compte @{acct}. C'EST UNE ACTION DESTRUCTRICE IRRÉVERSIBLE.", + "confirmations.admin.delete_user.message": "Vous allez supprimer le compte @{acct}. C’EST UNE ACTION DESTRUCTRICE IRRÉVERSIBLE.", "confirmations.admin.mark_status_not_sensitive.confirm": "Marquer la publication comme non sensible", "confirmations.admin.mark_status_not_sensitive.heading": "Marquer la publication comme non sensible.", "confirmations.admin.mark_status_not_sensitive.message": "Vous allez marquer une publication de @{acct} non sensible.", @@ -554,7 +554,7 @@ "confirmations.admin.mark_status_sensitive.message": "Vous allez marquer une publication de @{acct} sensible.", "confirmations.admin.reject_user.confirm": "Rejeter @{name}", "confirmations.admin.reject_user.heading": "Rejeter @{acct}", - "confirmations.admin.reject_user.message": "Vous allez rejeter la demande d'inscription de @{acct}. Cette action est irréversible.", + "confirmations.admin.reject_user.message": "Vous allez rejeter la demande d’inscription de @{acct}. Cette action est irréversible.", "confirmations.block.block_and_report": "Bloquer et signaler", "confirmations.block.confirm": "Bloquer", "confirmations.block.heading": "Block @{name}", @@ -565,40 +565,40 @@ "confirmations.cancel.confirm": "Abandonner", "confirmations.cancel.heading": "Abandonner la publication", "confirmations.cancel.message": "Êtes-vous sûr⋅e de vouloir abandonner la création de cette publication ?", - "confirmations.cancel_editing.confirm": "Annuler l'édition", - "confirmations.cancel_editing.heading": "Annuler l'édition de la publication", - "confirmations.cancel_editing.message": "Annuler l'édition de la publication ? Tous les changements seront perdus.", - "confirmations.cancel_editing.save_draft": "", - "confirmations.cancel_event_editing.heading": "Annuler l'édition de l'évènement", - "confirmations.cancel_event_editing.message": "Êtes-vous sûr⋅e de vouloir annuler l'édition de cet évènement ? Tous les changements seront perdus.", + "confirmations.cancel_editing.confirm": "Annuler l’édition", + "confirmations.cancel_editing.heading": "Annuler l’édition de la publication", + "confirmations.cancel_editing.message": "Annuler l’édition de la publication ? Tous les changements seront perdus.", + "confirmations.cancel_editing.save_draft": "Enregistrer le brouillon", + "confirmations.cancel_event_editing.heading": "Annuler l’édition de l’évènement", + "confirmations.cancel_event_editing.message": "Êtes-vous sûr⋅e de vouloir annuler l’édition de cet évènement ? Tous les changements seront perdus.", "confirmations.delete.confirm": "Supprimer", "confirmations.delete.heading": "Delete post", "confirmations.delete.message": "Confirmez-vous la suppression de ce pouet ?", - "confirmations.delete_bookmark_folder.confirm": "", - "confirmations.delete_bookmark_folder.heading": "", - "confirmations.delete_bookmark_folder.message": "", + "confirmations.delete_bookmark_folder.confirm": "Supprimer le dossier", + "confirmations.delete_bookmark_folder.heading": "Supprimer le dossier « {name} » ?", + "confirmations.delete_bookmark_folder.message": "Êtes vous certain de vouloir supprimer le dossier ? Les signets seront par contre conservés.", "confirmations.delete_event.confirm": "Supprimer", - "confirmations.delete_event.heading": "Supprimer l'évènement", + "confirmations.delete_event.heading": "Supprimer l’évènement", "confirmations.delete_event.message": "Êtes-vous sûr⋅e de vouloir supprimer cet évènement ?", "confirmations.delete_from_group.message": "Souhaitez-vous vraiment supprimer la publication de @{name} ?", "confirmations.delete_group.confirm": "Supprimer", "confirmations.delete_group.heading": "Supprimer le groupe", - "confirmations.delete_group.message": "Souhaitez-vous vraiment supprimer ce groupe ? C'est une action irréversible.", + "confirmations.delete_group.message": "Souhaitez-vous vraiment supprimer ce groupe ? C’est une action irréversible.", "confirmations.delete_list.confirm": "Supprimer", "confirmations.delete_list.heading": "Delete list", "confirmations.delete_list.message": "Êtes-vous sûr·e de vouloir supprimer définitivement cette liste ?", "confirmations.domain_block.confirm": "Masquer le domaine entier", "confirmations.domain_block.heading": "Block {domain}", "confirmations.domain_block.message": "Êtes-vous vraiment, vraiment sûr⋅e de vouloir bloquer {domain} en entier ? Dans la plupart des cas, quelques blocages ou masquages ciblés sont suffisants et préférables. Vous ne verrez plus de contenu provenant de ce domaine, ni dans fils publics, ni dans vos notifications. Vos abonné·e·s utilisant ce domaine seront retiré·e·s.", - "confirmations.draft_status_delete.confirm": "", - "confirmations.draft_status_delete.heading": "", - "confirmations.draft_status_delete.message": "", + "confirmations.draft_status_delete.confirm": "Abandonner", + "confirmations.draft_status_delete.heading": "Supprimer le brouillon", + "confirmations.draft_status_delete.message": "Êtes vous certain de vouloir effacer ce brouillon ?", "confirmations.kick_from_group.confirm": "Éjecter", "confirmations.kick_from_group.heading": "", "confirmations.kick_from_group.message": "Souhaitez-vous vraiment éjecter @{name} du groupe ?", - "confirmations.leave_event.confirm": "Quitter l'évènement", - "confirmations.leave_event.heading": "", - "confirmations.leave_event.message": "Si vous voulez rejoindre à nouveau l'évènement, la requête sera à nouveau validée manuellement. Êtes-vous sûr⋅e de continuer ?", + "confirmations.leave_event.confirm": "Quitter l’évènement", + "confirmations.leave_event.heading": "Quitter l’évènement", + "confirmations.leave_event.message": "Si vous voulez rejoindre à nouveau l’évènement, la requête sera à nouveau validée manuellement. Êtes-vous sûr⋅e de continuer ?", "confirmations.leave_group.confirm": "Quitter", "confirmations.leave_group.heading": "Quitter le groupe", "confirmations.leave_group.message": "Vous allez quitter le groupe. Souhaitez-vous continuer ?", @@ -629,7 +629,7 @@ "confirmations.unfollow.confirm": "Ne plus suivre", "copy.success": "Copié dans le presse-papier !", "crypto.lightning": "", - "crypto_donate.explanation_box.message": "{siteTitle} accepte les dons en cryptomonnaies. Vous pouvez envoyer un don à l'une des adresses ci-dessous. Merci de votre soutien !", + "crypto_donate.explanation_box.message": "{siteTitle} accepte les dons en cryptomonnaies. Vous pouvez envoyer un don à l’une des adresses ci-dessous. Merci de votre soutien !", "crypto_donate.explanation_box.title": "Envoyer des dons en cryptomonnaie", "crypto_donate_panel.actions.view": "Cliquer pour voir {count, plural, one {# le portefeuille} other {# les portefeuilles}}", "crypto_donate_panel.heading": "Donner en cryptomonnaie", @@ -645,7 +645,7 @@ "developers.challenge.answer_label": "Réponse", "developers.challenge.answer_placeholder": "Votre réponse", "developers.challenge.fail": "Mauvaise réponse", - "developers.challenge.message": "Quel est le résultat de l'appel de {function} ?", + "developers.challenge.message": "Quel est le résultat de l’appel de {function} ?", "developers.challenge.submit": "Devenir un⋅e développeur⋅euse", "developers.challenge.success": "Vous êtes maintenant développeur⋅euse", "developers.leave": "Vous avez quitté les développeur⋅euse⋅s", @@ -658,18 +658,18 @@ "developers.navigation.show_toast": "Activer les notifications urgentes", "developers.navigation.test_timeline_label": "Fil de test", "developers.settings_store.advanced": "Paramètres avancés", - "developers.settings_store.hint": "Il est possible d'éditer vos paramètres de compte utilisateur ici. FAÎTES ATTENTION ! Editer cette section peut rendre votre compte inutilisable et vous ne pourrez le récupérer qu'à travers l'API.", + "developers.settings_store.hint": "Il est possible de modifier vos paramètres de compte utilisateur ici. FAÎTES ATTENTION ! Modifier cette section peut rendre votre compte inutilisable et vous ne pourrez le récupérer qu’à travers l’API.", "direct.search_placeholder": "Send a message to…", - "directory.display_filter": "", + "directory.display_filter": "Afficher les filtres", "directory.federated": "Depuis le Fédivers connu", "directory.fediverse_filter": "", "directory.local": "Depuis {domain} seulement", "directory.new_arrivals": "Nouveaux profils", "directory.recently_active": "Récemment actif", - "draft_status.cancel": "", - "draft_status.edit": "", - "edit_bookmark_folder_modal.confirm": "", - "edit_bookmark_folder_modal.header_title": "", + "draft_status.cancel": "Effacer", + "draft_status.edit": "Edit", + "edit_bookmark_folder_modal.confirm": "Sauvegarder", + "edit_bookmark_folder_modal.header_title": "Modification du dossier", "edit_email.header": "Changer le courriel", "edit_email.placeholder": "me@example.com", "edit_federation.followers_only": "Cacher les publications excepté aux abonnés", @@ -681,12 +681,12 @@ "edit_federation.unlisted": "Forcer les publications en non listées", "edit_password.header": "Changer de mot de passe", "edit_profile.error": "La mise à jour du profil a échoué", - "edit_profile.fields.accepts_email_list_label": "S'inscrire à la lettre d'information", + "edit_profile.fields.accepts_email_list_label": "S’inscrire à la lettre d’information", "edit_profile.fields.bio_label": "Biographie", - "edit_profile.fields.bio_placeholder": "Dites-nous en plus plus à propos de vous.", + "edit_profile.fields.bio_placeholder": "Dites-nous en plus à propos de vous.", "edit_profile.fields.birthday_label": "Anniversaire", "edit_profile.fields.birthday_placeholder": "Votre anniversaire", - "edit_profile.fields.bot_label": "C'est un compte de robot", + "edit_profile.fields.bot_label": "C’est un compte de robot", "edit_profile.fields.discoverable_label": "Autoriser la découvert du profil", "edit_profile.fields.display_name_label": "Nom affiché", "edit_profile.fields.display_name_placeholder": "Name", @@ -696,19 +696,19 @@ "edit_profile.fields.locked_label": "Lock account", "edit_profile.fields.meta_fields.content_placeholder": "Content", "edit_profile.fields.meta_fields.label_placeholder": "Label", - "edit_profile.fields.meta_fields.label_placeholder.first": "", + "edit_profile.fields.meta_fields.label_placeholder.first": "Label (e.g. pronons)", "edit_profile.fields.meta_fields_label": "Champs du profil", - "edit_profile.fields.rss_label": "", + "edit_profile.fields.rss_label": "Activer le flux RSS pour les publications publiques", "edit_profile.fields.stranger_notifications_label": "Bloquer les notifications des personnes inconnues", "edit_profile.fields.website_label": "Website", "edit_profile.fields.website_placeholder": "Afficher un lien", - "edit_profile.header": "Éditer le profil", - "edit_profile.hints.accepts_email_list": "Activer les nouvelles et mises à jour marketing", + "edit_profile.header": "Modifier le profil", + "edit_profile.hints.accepts_email_list": "Activer les nouvelles et mises à jour marketing.", "edit_profile.hints.bot": "Ce compte fait principalement des actions automatiques et peut ne pas être surveillé", "edit_profile.hints.discoverable": "Afficher le compte dans le répertoire des profil et autoriser son indexation par des services externes", - "edit_profile.hints.hide_network": "Votre profil n'indiquera pas qui vous suivez et qui vous suit", + "edit_profile.hints.hide_network": "Votre profil n’indiquera pas qui vous suivez et qui vous suit", "edit_profile.hints.locked": "Nécessite que vous approuvier manuellement vos abonnés", - "edit_profile.hints.meta_fields": "Vous pouvez avoir jusqu'à {count, plural, one {# custom field} other {# custom fields}} affichés sur votre profil.", + "edit_profile.hints.meta_fields": "Vous pouvez avoir jusqu’à {count, plural, one {# custom field} other {# custom fields}} affichés sur votre profil.", "edit_profile.hints.stranger_notifications": "Uniquement afficher les notifications de personne que vous suivez", "edit_profile.save": "Sauvegarder", "edit_profile.success": "Votre profil a été sauvegardé avec succès !", @@ -725,7 +725,7 @@ "emoji_button.objects": "Objets", "emoji_button.oh_no": "Oh non !", "emoji_button.people": "Personnes", - "emoji_button.pick": "Choisir un emoji", + "emoji_button.pick": "Choisir un emoji…", "emoji_button.recent": "Fréquemment utilisés", "emoji_button.search": "Recherche…", "emoji_button.search_results": "Résultats de la recherche", @@ -738,63 +738,63 @@ "emoji_button.skins_choose": "Choisir le ton de skin par défaut", "emoji_button.symbols": "Symboles", "emoji_button.travel": "Lieux & Voyages", - "empty_column.account_blocked": "You are blocked by @{accountUsername}.", - "empty_column.account_favourited_statuses": "This user doesn't have any liked posts yet.", + "empty_column.account_blocked": "Vous êtes bloqué par @{accountUsername}.", + "empty_column.account_favourited_statuses": "Cet utilisateur⋅rice n’a pas encore « aimé » de publication.", "empty_column.account_timeline": "Aucun pouet ici !", "empty_column.account_unavailable": "Profil non disponible", - "empty_column.admin.announcements": "Il n'y a aucune annonce pour le moment.", - "empty_column.admin.domains": "", - "empty_column.admin.relays": "", - "empty_column.admin.rules": "", - "empty_column.aliases": "You haven't created any account alias yet.", + "empty_column.admin.announcements": "Il n’y a aucune annonce pour le moment.", + "empty_column.admin.domains": "Il n’y a aucun domaine actuellement.", + "empty_column.admin.relays": "Il n’y a aucun relai actuellement suivi.", + "empty_column.admin.rules": "Aucune règle actuellement définie pour cette instance.", + "empty_column.aliases": "You haven’t created any account alias yet.", "empty_column.aliases.suggestions": "There are no account suggestions available for the provided term.", "empty_column.blocks": "Vous n’avez bloqué aucun·e utilisateur·rice pour le moment.", - "empty_column.bookmarks": "You don't have any bookmarks yet. When you add one, it will show up here.", - "empty_column.bookmarks.folder": "", - "empty_column.bubble": "", + "empty_column.bookmarks": "You don’t have any bookmarks yet. When you add one, it will show up here.", + "empty_column.bookmarks.folder": "Vous n’avez aucun signet enregistré dans ce dossier. Lorsque vous en enregistrerez, ils seront visible ici.", + "empty_column.bubble": "Il n’y a rien ici ! Commencez à publier quelque chose pour le remplir", "empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !", "empty_column.direct": "Vous n’avez pas encore de messages directs. Lorsque vous en enverrez ou recevrez un, il s’affichera ici.", - "empty_column.dislikes": "Personne n'a détesté cette publication pour l'instant. Quand quelqu'un le fera cela se verra ici.", + "empty_column.dislikes": "Personne n’a détesté cette publication pour l’instant. Quand quelqu’un le fera cela se verra ici.", "empty_column.domain_blocks": "Il n’y a aucun domaine caché pour le moment.", "empty_column.draft_statuses": "", - "empty_column.event_participant_requests": "Il n'y a pas de requête de participation en attente.", - "empty_column.event_participants": "Personne n'a rejoint cet évènement pour le moment. Quand quelqu'un le fera, cela apparaîtra ici.", + "empty_column.event_participant_requests": "Il n’y a pas de requête de participation en attente.", + "empty_column.event_participants": "Personne n’a rejoint cet évènement pour le moment. Quand quelqu’un le fera, cela apparaîtra ici.", "empty_column.favourited_statuses": "Vous n’avez aucun pouet favoris pour le moment. Lorsque vous en mettrez un en favori, il apparaîtra ici.", "empty_column.favourites": "Personne n’a encore mis ce pouet en favori. Lorsque quelqu’un le fera, il apparaîtra ici.", - "empty_column.filters": "You haven't created any muted words yet.", + "empty_column.filters": "You haven’t created any muted words yet.", "empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.", "empty_column.follow_requests": "Vous n’avez pas encore de demande de suivi. Lorsque vous en recevrez une, elle apparaîtra ici.", - "empty_column.followed_tags": "", - "empty_column.group": "", + "empty_column.followed_tags": "Vous ne suivez aucun hashtag actuellement.", + "empty_column.group": "Aucune publication dans cet groupe actuellement.", "empty_column.group_blocks": "", "empty_column.group_membership_requests": "", "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.", "empty_column.home": "Vous ne suivez personne. Visitez {public} ou utilisez la recherche pour trouver d’autres personnes à suivre.", "empty_column.home.local_tab": "the {site_title} tab", - "empty_column.home.subtitle": "{siteTitle} gets more interesting once you follow other users.", - "empty_column.home.title": "You're not following anyone yet", + "empty_column.home.subtitle": "{siteTitle} devient plus attrayant lorsque vous suivez d’autre personnes.", + "empty_column.home.title": "You’re not following anyone yet", "empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Dès que des personnes de cette liste publieront de nouveaux statuts, ils apparaîtront ici.", "empty_column.lists": "Vous n’avez pas encore de liste. Lorsque vous en créerez une, elle apparaîtra ici.", "empty_column.mutes": "Vous n’avez pas encore mis d’utilisateur·rice·s en silence.", "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.", - "empty_column.notifications_filtered": "You don't have any notifications of this type yet.", + "empty_column.notifications_filtered": "You don’t have any notifications of this type yet.", "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres instances pour le remplir", - "empty_column.quotes": "Cette publication n'a pas été citée pour le moment.", - "empty_column.remote": "Il n'y a rien ici ! Suivez manuellement les utilisateurs de {instance} pour remplir cet espace.", - "empty_column.scheduled_statuses": "You don't have any scheduled statuses yet. When you add one, it will show up here.", + "empty_column.quotes": "Cette publication n’a pas été citée pour le moment.", + "empty_column.remote": "Il n’y a rien ici ! Suivez manuellement les utilisateurs de {instance} pour remplir cet espace.", + "empty_column.scheduled_statuses": "You don’t have any scheduled statuses yet. When you add one, it will show up here.", "empty_column.search.accounts": "There are no people results for \"{term}\"", "empty_column.search.hashtags": "There are no hashtags results for \"{term}\"", "empty_column.search.statuses": "There are no posts results for \"{term}\"", "empty_column.test": "The test timeline is empty.", - "event.banner": "Bannière de l'évènement", - "event.copy": "Copier le lien de l'évènement", + "event.banner": "Bannière de l’évènement", + "event.copy": "Copier le lien de l’évènement", "event.date": "Date", "event.description": "Description", - "event.discussion": "", - "event.discussion.empty": "Personne n'a commenté cet évènement pour l'instant. Quand quelqu'un le fera, cela apparaîtra ici.", + "event.discussion": "Discussion", + "event.discussion.empty": "Personne n’a commenté cet évènement pour l’instant. Quand quelqu’un le fera, cela apparaîtra ici.", "event.export_ics": "Exporter vers votre calendrier", "event.external": "Voir les évènements sur {domain}", - "event.information": "", + "event.information": "Information", "event.join_state.accept": "Participer", "event.join_state.empty": "Participer", "event.join_state.pending": "En attente", @@ -811,9 +811,9 @@ "event_map.navigate": "Naviguer", "events.create_event": "Créer un évènement", "events.joined_events": "Évènements rejoints", - "events.joined_events.empty": "Vous n'avez rejoint aucun évènement pour le moment.", + "events.joined_events.empty": "Vous n’avez rejoint aucun évènement pour le moment.", "events.recent_events": "Récents évènements", - "events.recent_events.empty": "Il n'y a pas d'évènement public pour le moment.", + "events.recent_events.empty": "Il n’y a pas d’évènement public pour le moment.", "export_data.actions.export": "Export", "export_data.actions.export_blocks": "Export blocks", "export_data.actions.export_follows": "Export follows", @@ -836,33 +836,34 @@ "federation_restrictions.explanation_box.message": "Normally servers on the Fediverse can communicate freely. {siteTitle} has imposed restrictions on the following servers.", "federation_restrictions.explanation_box.title": "Instance-specific policies", "federation_restrictions.not_disclosed_message": "{siteTitle} does not disclose federation restrictions through the API.", - "fediverse_tab.explanation_box.dismiss": "Don't show again", - "fediverse_tab.explanation_box.explanation": "{site_title} is part of the Fediverse, a social network made up of thousands of independent social media sites (aka \"servers\"). The posts you see here are from 3rd-party servers. You have the freedom to engage with them, or to block any server you don't like. Pay attention to the full username after the second @ symbol to know which server a post is from. To see only {site_title} posts, visit {local}.", - "fediverse_tab.explanation_box.title": "Qu'est-ce que le Fédivers ?", + "fediverse_tab.explanation_box.dismiss": "Don’t show again", + "fediverse_tab.explanation_box.explanation": "{site_title} is part of the Fediverse, a social network made up of thousands of independent social media sites (aka \"servers\"). The posts you see here are from 3rd-party servers. You have the freedom to engage with them, or to block any server you don’t like. Pay attention to the full username after the second @ symbol to know which server a post is from. To see only {site_title} posts, visit {local}.", + "fediverse_tab.explanation_box.title": "Qu’est-ce que le Fédivers ?", "feed_suggestions.heading": "Suggested Profiles", "feed_suggestions.view_all": "View all", "filters.added": "Filter added.", "filters.context_header": "Filter contexts", "filters.context_hint": "One or multiple contexts where the filter should apply", - "filters.create_filter": "", + "filters.create_filter": "Créer un filtre", "filters.filters_list_context_label": "Contexte du filtre :", "filters.filters_list_drop": "Drop", "filters.filters_list_expired": "", "filters.filters_list_hide": "Hide", - "filters.filters_list_hide_completely": "", + "filters.filters_list_hide_completely": "Hide content", "filters.filters_list_phrases_label": "", "filters.filters_list_warn": "", "filters.removed": "Filter deleted.", - "follow_recommendations.heading": "", + "follow_recommendations.heading": "Profils suggérés", "follow_request.authorize": "Accepter", "follow_request.reject": "Rejeter", "gdpr.accept": "Accept", "gdpr.learn_more": "Learn more", "gdpr.message": "{siteTitle} uses session cookies, which are essential to the website's functioning.", "gdpr.title": "{siteTitle} uses cookies", - "generic.saved": "", - "getting_started.open_source_notice": "{code_name} est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {code_link} (v{code_version}) sur GitLab.", - "group.cancel_request": "", + "generic.logo": "Logo", + "generic.saved": "Enregistré", + "getting_started.open_source_notice": "{code_name} est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {code_link} (v{code_version}) sur GitHub.", + "group.cancel_request": "Annuler la demande", "group.delete.success": "", "group.demote.user.success": "", "group.group_mod_authorize.fail": "", @@ -982,9 +983,9 @@ "join_event.hint": "Vous pouvez dire à la personne organisatrice pourquoi vous voulez participer à cet évènement :", "join_event.join": "Demander à participer", "join_event.placeholder": "Envoyer un message à la personne organisatrice", - "join_event.request_success": "Demander à participer à l'évènement", + "join_event.request_success": "Demander à participer à l’évènement", "join_event.success": "Évènements rejoints", - "join_event.title": "Rejoindre l'évènement", + "join_event.title": "Rejoindre l’évènement", "keyboard_shortcuts.back": "pour revenir en arrière", "keyboard_shortcuts.blocked": "pour ouvrir une liste d’utilisateur·rice·s bloqué·e·s", "keyboard_shortcuts.boost": "pour partager", @@ -1025,7 +1026,7 @@ "lists.account.add": "Ajouter à la liste", "lists.account.remove": "Supprimer de la liste", "lists.delete": "Delete list", - "lists.edit": "Éditer la liste", + "lists.edit": "Modifier la liste", "lists.edit.submit": "Changer le titre", "lists.new.create": "Ajouter une liste", "lists.new.create_title": "Add list", @@ -1039,7 +1040,7 @@ "login.fields.instance_label": "Instance", "login.fields.instance_placeholder": "example.com", "login.fields.otp_code_hint": "Enter the two-factor code generated by your phone app or use one of your recovery codes", - "login.fields.otp_code_label": "Code d'authentification à deux facteurs :", + "login.fields.otp_code_label": "Code d’authentification à deux facteurs :", "login.fields.password_placeholder": "Password", "login.fields.username_label": "Email or username", "login.log_in": "Log in", @@ -1078,18 +1079,18 @@ "mfa.disable.success_message": "MFA disabled", "mfa.disabled": "Disabled", "mfa.enabled": "Enabled", - "mfa.mfa_disable_enter_password": "Entrer votre mot de passe actuel pour désactiver l'authentification à deux facteurs.", + "mfa.mfa_disable_enter_password": "Entrer votre mot de passe actuel pour désactiver l’authentification à deux facteurs.", "mfa.mfa_setup.code_hint": "Enter the code from your two-factor app.", "mfa.mfa_setup.code_placeholder": "Code", "mfa.mfa_setup.password_hint": "Enter your current password to confirm your identity.", "mfa.mfa_setup.password_placeholder": "Password", - "mfa.mfa_setup_scan_description": "En utilisant votre application d'authentification à deux facteurs, scannez le QR code et entrez le texte de la clé.", + "mfa.mfa_setup_scan_description": "En utilisant votre application d’authentification à deux facteurs, scannez le QR code et entrez le texte de la clé.", "mfa.mfa_setup_scan_title": "Scan", "mfa.mfa_setup_verify_title": "Verify", "mfa.otp_enabled_description": "You have enabled two-factor authentication via OTP.", "mfa.otp_enabled_title": "OTP Enabled", "mfa.setup_recoverycodes": "Recovery codes", - "mfa.setup_warning": "Write these codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.", + "mfa.setup_warning": "Write these codes down or save them somewhere secure - otherwise you won’t see them again. If you lose access to your 2FA app and recovery codes you’ll be locked out of your account.", "migration.fields.acct.label": "Handle of the new account", "migration.fields.acct.placeholder": "username@domain", "migration.fields.confirm_password.label": "Current password", @@ -1103,7 +1104,7 @@ "missing_description_modal.cancel": "Cancel", "missing_description_modal.continue": "Post", "missing_description_modal.description": "Continuer tout de même ?", - "missing_description_modal.text": "Vous n'avez pas entré de description pour toutes les pièces jointes. Continuer tout de même ?", + "missing_description_modal.text": "Vous n’avez pas entré de description pour toutes les pièces jointes. Continuer tout de même ?", "missing_indicator.label": "Non trouvé", "missing_indicator.sublabel": "Ressource introuvable", "moderation_overlay.contact": "Contact", @@ -1136,7 +1137,7 @@ "navigation_bar.compose": "Rédiger un nouveau toot", "navigation_bar.compose_direct": "Direct message", "navigation_bar.compose_edit": "Edit post", - "navigation_bar.compose_event": "Gérer l'évènement", + "navigation_bar.compose_event": "Gérer l’évènement", "navigation_bar.compose_group": "", "navigation_bar.compose_group_reply": "", "navigation_bar.compose_quote": "Quote post", @@ -1179,8 +1180,8 @@ "notification.pleroma:chat_mention": "{name} sent you a message", "notification.pleroma:emoji_reaction": "{name} reacted to your post", "notification.pleroma:event_reminder": "Un évènement auquel vous participez commence bientôt", - "notification.pleroma:participation_accepted": "Votre participation à l'évènement a été acceptée", - "notification.pleroma:participation_request": "{name} vous invite à rejoindre l'évènement", + "notification.pleroma:participation_accepted": "Votre participation à l’évènement a été acceptée", + "notification.pleroma:participation_request": "{name} vous invite à rejoindre l’évènement", "notification.poll": "Un sondage auquel vous avez participé vient de se terminer", "notification.reblog": "{name} a partagé votre statut", "notification.severed_relationships": "", @@ -1210,7 +1211,7 @@ "onboarding.display_name.title": "Choose a display name", "onboarding.done": "Done", "onboarding.error": "An unexpected error occurred. Please try again or skip this step.", - "onboarding.fediverse.its_you": "C'est vous-même ! Les autres personnes peuvent vous suivre depuis d'autres serveurs en utilisant votre adresse complète contenant un @.", + "onboarding.fediverse.its_you": "C’est vous-même ! Les autres personnes peuvent vous suivre depuis d’autres serveurs en utilisant votre adresse complète contenant un @.", "onboarding.fediverse.message": "The Fediverse is a social network made up of thousands of diverse and independently-run social media sites (aka \"servers\"). You can follow users — and like, repost, and reply to posts — from most other Fediverse servers, because they can communicate with {siteTitle}.", "onboarding.fediverse.next": "Next", "onboarding.fediverse.other_instances": "When browsing your timeline, pay attention to the full username after the second @ symbol to know which server a post is from.", @@ -1250,7 +1251,7 @@ "plfe_config.feed_injection_hint": "Inject the feed with additional content, such as suggested profiles.", "plfe_config.feed_injection_label": "Feed injection", "plfe_config.fields.crypto_addresses_label": "Cryptocurrency addresses", - "plfe_config.fields.edit_theme_label": "Éditer le thème", + "plfe_config.fields.edit_theme_label": "Modifier le thème", "plfe_config.fields.home_footer_fields_label": "Home footer items", "plfe_config.fields.logo_label": "Logo", "plfe_config.fields.promo_panel_fields_label": "Promo panel items", @@ -1269,16 +1270,16 @@ "plfe_config.hints.promo_panel_icons.link": "Soapbox Icons List", "plfe_config.home_footer.meta_fields.label_placeholder": "Label", "plfe_config.home_footer.meta_fields.url_placeholder": "URL", - "plfe_config.media_preview_hint": "Certains backends offrent une version optimisée de médias pour l'affichage dans les délais. Cependant, ces images de prévisualisation peuvent être trop petites sans configuration supplémentaire.", + "plfe_config.media_preview_hint": "Certains backends offrent une version optimisée de médias pour l’affichage dans les délais. Cependant, ces images de prévisualisation peuvent être trop petites sans configuration supplémentaire.", "plfe_config.media_preview_label": "Préférer les médias pour les vignettes", "plfe_config.promo_panel.meta_fields.icon_placeholder": "Icon", "plfe_config.promo_panel.meta_fields.label_placeholder": "Label", "plfe_config.promo_panel.meta_fields.url_placeholder": "URL", "plfe_config.raw_json_hint": "Edit the settings data directly. Changes made directly to the JSON file will override the form fields above. Click Save to apply your changes.", "plfe_config.raw_json_invalid": "est invalide", - "plfe_config.raw_json_label": "Avancé : Éditer les données brutes en JSON", - "plfe_config.redirect_root_no_login_hint": "Chemin vers lequel rediriger la page d'accueil hors connexion.", - "plfe_config.redirect_root_no_login_label": "Rediriger la page d'accueil", + "plfe_config.raw_json_label": "Avancé : Modifier les données brutes en JSON", + "plfe_config.redirect_root_no_login_hint": "Chemin vers lequel rediriger la page d’accueil hors connexion.", + "plfe_config.redirect_root_no_login_label": "Rediriger la page d’accueil", "plfe_config.save": "Save", "plfe_config.saved": "Configuration de Soapbox sauvegardée !", "plfe_config.sentry_dsn_hint": "", @@ -1286,7 +1287,7 @@ "plfe_config.tile_server_attribution_label": "Attribution des tuiles de carte", "plfe_config.tile_server_label": "Serveur de tuiles de carte", "plfe_config.verified_can_edit_name_label": "Allow verified users to edit their own display name.", - "poll.choose_multiple": "Choose as many as you'd like.", + "poll.choose_multiple": "Choose as many as you’d like.", "poll.closed": "Fermé", "poll.non_anonymous": "Public poll", "poll.non_anonymous.label": "Other instances may display the options you voted for", @@ -1306,7 +1307,7 @@ "preferences.fields.content_type_label": "Default post format", "preferences.fields.delete_modal_label": "Show confirmation dialog before deleting a post", "preferences.fields.demetricator_label": "Use Demetricator", - "preferences.fields.demo_hint": "Utiliser le logo de Soapbox par défaut et son schéma de couleur. Utile pour prendre des captures d'écran.", + "preferences.fields.demo_hint": "Utiliser le logo de Soapbox par défaut et son schéma de couleur. Utile pour prendre des captures d’écran.", "preferences.fields.demo_label": "Mode de démonstration", "preferences.fields.display_media.default": "Hide media marked as sensitive", "preferences.fields.display_media.hide_all": "Always hide media", @@ -1372,7 +1373,7 @@ "registration.fields.username_hint": "Only letters, numbers, and underscores are allowed.", "registration.fields.username_placeholder": "Username", "registration.newsletter": "Subscribe to newsletter.", - "registration.password_mismatch": "Passwords don't match.", + "registration.password_mismatch": "Passwords don’t match.", "registration.reason": "Pourquoi souhaitez-vous rejoindre ?", "registration.reason_hint": "This will help us review your application", "registration.sign_up": "Sign up", @@ -1407,7 +1408,7 @@ "remote_interaction.reblog_title": "Reblog a post remotely", "remote_interaction.reply": "Proceed to reply", "remote_interaction.reply_title": "Reply to a post remotely", - "remote_interaction.user_not_found_error": "Couldn't find given user", + "remote_interaction.user_not_found_error": "Couldn’t find given user", "remote_timeline.filter_message": "You are viewing the timeline of {instance}.", "reply_indicator.cancel": "Annuler", "reply_mentions.account.add": "Add to mentions", @@ -1654,7 +1655,7 @@ "unauthorized_modal.title": "Sign up for {site_title}", "upload_button.label": "Joindre un média (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_error.image_size_limit": "Image exceeds the current file size limit ({limit})", - "upload_error.limit": "Taille maximale d'envoi de fichier dépassée.", + "upload_error.limit": "Taille maximale d’envoi de fichier dépassée.", "upload_error.poll": "L’envoi de fichiers n’est pas autorisé avec les sondages.", "upload_error.video_duration_limit": "La vidéo dépasse les limites de durées actuelles ({limit, plural, one {# seconde} other {# secondes}})", "upload_error.video_size_limit": "Video exceeds the current file size limit ({limit})", From 873a6a344ab97612afaced701342ceb712c30852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 13 Oct 2024 00:19:21 +0200 Subject: [PATCH 03/28] Update changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/pl-fe/CHANGELOG.md b/packages/pl-fe/CHANGELOG.md index 4df46616d..eeaa47b75 100644 --- a/packages/pl-fe/CHANGELOG.md +++ b/packages/pl-fe/CHANGELOG.md @@ -26,7 +26,7 @@ Changes made since the project forked from Soapbox in April 2024. - You can write posts with multiple language versions, when supported by backend. - Language detection is done client-side for composed posts, utilizing `fasttext.wasm.js`. - Draft posts. They are stored locally only and work with any backend. - +- New visibility scopes are supported – local-only and list-only for Pleroma. Local-only is a separate switch on GoToSocial. **Features:** - The most recent scrobble is displayed on user profile/card. @@ -34,7 +34,7 @@ Changes made since the project forked from Soapbox in April 2024. - You can bite users, if supported by backend. - You can browse Bubble timeline, if supported by backend. - Mastodon displays trending articles on Search page. -- Postsa can be addressed to lists of users, on Pleroma. +- Posts can be addressed to lists of users, on Pleroma. ### Changed @@ -62,13 +62,14 @@ Changes made since the project forked from Soapbox in April 2024. - Updated Lists UI, to match the overall style. - RSS button is displayed in account header for local users, when unauthenticated. - Conversations page is always displayed, even when Chats are supported. +- Made it woke. - Emojis are zoomed on hover. **Internal:** - Migrated some local stores from Redux to Zustand. **Dependencies:** -- `@tanstack/react-virtual` is used for list virtualization, instead of `react-virtuoso`. +- `@tanstack/react-virtual` is used for list virtualization, instead of `react-virtuoso`. This improves compatibility with Ladybird browser. - Replaced `react-popper` and `react-overlays` with `@floating-ui/react`. - `uuid` package is replaced by the `randomUUID()` method. From 37034815ec573db6093a339e506519e2eb75ec3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 13 Oct 2024 00:21:37 +0200 Subject: [PATCH 04/28] Style improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/components/birthday-input.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pl-fe/src/components/birthday-input.tsx b/packages/pl-fe/src/components/birthday-input.tsx index 56e7cc550..0fcb8fdf6 100644 --- a/packages/pl-fe/src/components/birthday-input.tsx +++ b/packages/pl-fe/src/components/birthday-input.tsx @@ -113,7 +113,7 @@ const BirthdayInput: React.FC = ({ value, onChange, required })
Date: Sat, 12 Oct 2024 11:01:25 +0000 Subject: [PATCH 05/28] Translated using Weblate (Polish) Currently translated at 100.0% (1599 of 1599 strings) Translation: pl-fe/pl-fe Translate-URL: https://hosted.weblate.org/projects/pl-fe/pl-fe/pl/ --- packages/pl-fe/src/locales/pl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pl-fe/src/locales/pl.json b/packages/pl-fe/src/locales/pl.json index 50206f68d..24a43b61f 100644 --- a/packages/pl-fe/src/locales/pl.json +++ b/packages/pl-fe/src/locales/pl.json @@ -1123,7 +1123,7 @@ "notification.pleroma:emoji_reaction": "{name} zareagował(a) na Twój wpis", "notification.pleroma:event_reminder": "Wydarzenie w którym bierzesz udział wkrótce się zaczyna", "notification.pleroma:participation_accepted": "Twoje zgłoszenie udziału do wydarzenia zostało przyjęte", - "notification.pleroma:participation_request": "{name} cce wziąć udział w Twoim wydarzeniu", + "notification.pleroma:participation_request": "{name} chce wziąć udział w Twoim wydarzeniu", "notification.poll": "Głosowanie w którym brałeś(-aś) udział zakończyła się", "notification.reblog": "{name} podbił(a) Twój wpis", "notification.reply": "{name} odpowiedział(a) na Twój wpis", From baaa0e1102d5dcdaab05e6b832003a94535ee9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 13 Oct 2024 21:03:54 +0200 Subject: [PATCH 06/28] pl-fe: Refresh account header when changing pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/layouts/group-layout.tsx | 2 +- packages/pl-fe/src/layouts/profile-layout.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pl-fe/src/layouts/group-layout.tsx b/packages/pl-fe/src/layouts/group-layout.tsx index 4c20fcb43..474bafd48 100644 --- a/packages/pl-fe/src/layouts/group-layout.tsx +++ b/packages/pl-fe/src/layouts/group-layout.tsx @@ -94,7 +94,7 @@ const GroupLayout: React.FC = ({ params, children }) => { <> - + = ({ params, children }) => {
-
+
{account && showTabs && ( From 95f38374c1eb00979980d62f98c911d95a33280c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 13 Oct 2024 21:17:16 +0200 Subject: [PATCH 07/28] pl-fe: Add Valibot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/package.json | 1 + packages/pl-fe/yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/pl-fe/package.json b/packages/pl-fe/package.json index a268610a6..c5ea26e27 100644 --- a/packages/pl-fe/package.json +++ b/packages/pl-fe/package.json @@ -140,6 +140,7 @@ "type-fest": "^4.26.1", "typescript": "^5.6.2", "util": "^0.12.5", + "valibot": "^0.42.1", "vite": "^5.4.8", "vite-plugin-compile-time": "^0.2.1", "vite-plugin-html": "^3.2.2", diff --git a/packages/pl-fe/yarn.lock b/packages/pl-fe/yarn.lock index 3bf1ebf32..4d552f5f7 100644 --- a/packages/pl-fe/yarn.lock +++ b/packages/pl-fe/yarn.lock @@ -9748,6 +9748,11 @@ util@^0.12.5: is-typed-array "^1.1.3" which-typed-array "^1.1.2" +valibot@^0.42.1: + version "0.42.1" + resolved "https://registry.yarnpkg.com/valibot/-/valibot-0.42.1.tgz#a31183d8e9d7552f98e22ca0977172cab8815188" + integrity sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw== + value-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" From 7abeee3d1ca9c7f44ca58648a795a4a45fba6cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 13 Oct 2024 22:24:06 +0200 Subject: [PATCH 08/28] Migrate pl-fe schemas to Valibot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/schemas/pl-fe/settings.ts | 102 ++++++++++--------- packages/pl-fe/src/schemas/pleroma.ts | 34 ++++--- packages/pl-fe/src/schemas/utils.ts | 10 +- packages/pl-fe/src/stores/settings.ts | 11 +- packages/pl-fe/src/utils/config-db.ts | 9 +- 5 files changed, 92 insertions(+), 74 deletions(-) diff --git a/packages/pl-fe/src/schemas/pl-fe/settings.ts b/packages/pl-fe/src/schemas/pl-fe/settings.ts index 6d2ecd9e5..c0601614c 100644 --- a/packages/pl-fe/src/schemas/pl-fe/settings.ts +++ b/packages/pl-fe/src/schemas/pl-fe/settings.ts @@ -1,85 +1,89 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { locales } from 'pl-fe/messages'; import { coerceObject } from '../utils'; -const skinToneSchema = z.union([ - z.literal(1), z.literal(2), z.literal(3), z.literal(4), z.literal(5), z.literal(6), -]); +const skinToneSchema = v.picklist([1, 2, 3, 4, 5, 6]); -const settingsSchema = z.object({ - onboarded: z.boolean().catch(false), - skinTone: skinToneSchema.catch(1), - reduceMotion: z.boolean().catch(false), - underlineLinks: z.boolean().catch(false), - autoPlayGif: z.boolean().catch(true), - displayMedia: z.enum(['default', 'hide_all', 'show_all']).catch('default'), - displaySpoilers: z.boolean().catch(false), - unfollowModal: z.boolean().catch(true), - boostModal: z.boolean().catch(false), - deleteModal: z.boolean().catch(true), - missingDescriptionModal: z.boolean().catch(true), - defaultPrivacy: z.enum(['public', 'unlisted', 'private', 'direct']).catch('public'), - defaultContentType: z.enum(['text/plain', 'text/markdown']).catch('text/plain'), - themeMode: z.enum(['system', 'light', 'dark', 'black']).catch('system'), - locale: z.string().catch(navigator.language).pipe(z.enum(locales)).catch('en'), - showExplanationBox: z.boolean().catch(true), - explanationBox: z.boolean().catch(true), - autoloadTimelines: z.boolean().catch(true), - autoloadMore: z.boolean().catch(true), - preserveSpoilers: z.boolean().catch(false), - autoTranslate: z.boolean().catch(false), - knownLanguages: z.array(z.string()).catch([]), +const settingsSchema = v.object({ + onboarded: v.fallback(v.boolean(), false), + skinTone: v.fallback(skinToneSchema, 1), + reduceMotion: v.fallback(v.boolean(), false), + underlineLinks: v.fallback(v.boolean(), false), + autoPlayGif: v.fallback(v.boolean(), true), + displayMedia: v.fallback(v.picklist(['default', 'hide_all', 'show_all']), 'default'), + displaySpoilers: v.fallback(v.boolean(), false), + unfollowModal: v.fallback(v.boolean(), true), + boostModal: v.fallback(v.boolean(), false), + deleteModal: v.fallback(v.boolean(), true), + missingDescriptionModal: v.fallback(v.boolean(), true), + defaultPrivacy: v.fallback(v.picklist(['public', 'unlisted', 'private', 'direct']), 'public'), + defaultContentType: v.fallback(v.picklist(['text/plain', 'text/markdown']), 'text/plain'), + themeMode: v.fallback(v.picklist(['system', 'light', 'dark', 'black']), 'system'), + locale: v.fallback( + v.pipe( + v.fallback(v.string(), navigator.language), + v.picklist(locales), + ), + 'en', + ), + showExplanationBox: v.fallback(v.boolean(), true), + explanationBox: v.fallback(v.boolean(), true), + autoloadTimelines: v.fallback(v.boolean(), true), + autoloadMore: v.fallback(v.boolean(), true), + preserveSpoilers: v.fallback(v.boolean(), false), + autoTranslate: v.fallback(v.boolean(), false), + knownLanguages: v.fallback(v.array(v.string()), []), - systemFont: z.boolean().catch(false), - demetricator: z.boolean().catch(false), + systemFont: v.fallback(v.boolean(), false), + demetricator: v.fallback(v.boolean(), false), - isDeveloper: z.boolean().catch(false), + isDeveloper: v.fallback(v.boolean(), false), chats: coerceObject({ - mainWindow: z.enum(['minimized', 'open']).catch('minimized'), - sound: z.boolean().catch(true), + mainWindow: v.fallback(v.picklist(['minimized', 'open']), 'minimized'), + sound: v.fallback(v.boolean(), true), }), - timelines: z.record(coerceObject({ + timelines: v.fallback(v.record(v.string(), coerceObject({ shows: coerceObject({ - reblog: z.boolean().catch(true), - reply: z.boolean().catch(true), - direct: z.boolean().catch(false), + reblog: v.fallback(v.boolean(), true), + reply: v.fallback(v.boolean(), true), + direct: v.fallback(v.boolean(), false), }), other: coerceObject({ - onlyMedia: z.boolean().catch(false), + onlyMedia: v.fallback(v.boolean(), false), }), - })).catch({}), + })), {}), account_timeline: coerceObject({ shows: coerceObject({ - pinned: z.boolean().catch(true), + pinned: v.fallback(v.boolean(), true), }), }), remote_timeline: coerceObject({ - pinnedHosts: z.string().array().catch([]), + pinnedHosts: v.fallback(v.array(v.string()), []), }), notifications: coerceObject({ quickFilter: coerceObject({ - active: z.string().catch('all'), - advanced: z.boolean().catch(false), - show: z.boolean().catch(true), + active: v.fallback(v.string(), 'all'), + advanced: v.fallback(v.boolean(), false), + show: v.fallback(v.boolean(), true), }), - sounds: z.record(z.boolean()).catch({}), + sounds: v.fallback(v.record(v.string(), v.boolean()), {}), }), - frequentlyUsedEmojis: z.record(z.number()).catch({}), - frequentlyUsedLanguages: z.record(z.number()).catch({}), + frequentlyUsedEmojis: v.fallback(v.record(v.string(), v.number()), {}), + frequentlyUsedLanguages: v.fallback(v.record(v.string(), v.number()), {}), - saved: z.boolean().catch(true), + saved: v.fallback(v.boolean(), true), - demo: z.boolean().catch(false), + demo: v.fallback(v.boolean(), false), }); -type Settings = z.infer; +type Settings = v.InferOutput; export { settingsSchema, type Settings }; diff --git a/packages/pl-fe/src/schemas/pleroma.ts b/packages/pl-fe/src/schemas/pleroma.ts index c7fff80c2..78a3d4b90 100644 --- a/packages/pl-fe/src/schemas/pleroma.ts +++ b/packages/pl-fe/src/schemas/pleroma.ts @@ -1,20 +1,26 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { coerceObject } from './utils'; -const mrfSimpleSchema = coerceObject({ - accept: z.string().array().catch([]), - avatar_removal: z.string().array().catch([]), - banner_removal: z.string().array().catch([]), - federated_timeline_removal: z.string().array().catch([]), - followers_only: z.string().array().catch([]), - media_nsfw: z.string().array().catch([]), - media_removal: z.string().array().catch([]), - reject: z.string().array().catch([]), - reject_deletes: z.string().array().catch([]), - report_removal: z.string().array().catch([]), -}); +const mrfSimpleSchema = coerceObject(v.entriesFromList( + [ + 'accept', + 'avatar_removal', + 'banner_removal', + 'federated_timeline_removal', + 'followers_only', + 'media_nsfw', + 'media_removal', + 'reject', + 'reject_deletes', + 'report_removal', + ], + v.fallback(v.array(v.string()), []), +)); -type MRFSimple = z.infer; +(window as any).v = v; +(window as any).mrfSimpleSchema = mrfSimpleSchema; + +type MRFSimple = v.InferOutput; export { mrfSimpleSchema, type MRFSimple }; diff --git a/packages/pl-fe/src/schemas/utils.ts b/packages/pl-fe/src/schemas/utils.ts index 8af87e166..d4038009e 100644 --- a/packages/pl-fe/src/schemas/utils.ts +++ b/packages/pl-fe/src/schemas/utils.ts @@ -1,3 +1,4 @@ +import * as v from 'valibot'; import z from 'zod'; import type { CustomEmoji } from 'pl-api'; @@ -18,8 +19,13 @@ const makeCustomEmojiMap = (customEmojis: CustomEmoji[]) => result[`:${emoji.shortcode}:`] = emoji; return result; }, {}); + /** zod schema to force the value into an object, if it isn't already. */ -const coerceObject = (shape: T) => - z.object({}).passthrough().catch({}).pipe(z.object(shape)); +const coerceObject = (shape: T) => + v.pipe( + v.any(), + v.transform((input) => typeof input === 'object' ? input : {}), + v.object(shape), + ); export { filteredArray, makeCustomEmojiMap, coerceObject }; diff --git a/packages/pl-fe/src/stores/settings.ts b/packages/pl-fe/src/stores/settings.ts index 2b76c4da5..f04b4ce4c 100644 --- a/packages/pl-fe/src/stores/settings.ts +++ b/packages/pl-fe/src/stores/settings.ts @@ -1,4 +1,5 @@ import { produce } from 'immer'; +import * as v from 'valibot'; import { create } from 'zustand'; import { settingsSchema, type Settings } from 'pl-fe/schemas/pl-fe/settings'; @@ -6,7 +7,7 @@ import { settingsSchema, type Settings } from 'pl-fe/schemas/pl-fe/settings'; import type { Emoji } from 'pl-fe/features/emoji'; import type { APIEntity } from 'pl-fe/types/entities'; -const settingsSchemaPartial = settingsSchema.partial(); +const settingsSchemaPartial = v.partial(settingsSchema); type State = { defaultSettings: Settings; @@ -35,22 +36,22 @@ const changeSetting = (object: APIEntity, path: string[], value: any) => { const mergeSettings = (state: State) => state.settings = { ...state.defaultSettings, ...state.userSettings }; const useSettingsStore = create((set) => ({ - defaultSettings: settingsSchema.parse({}), + defaultSettings: v.parse(settingsSchema, {}), userSettings: {}, - settings: settingsSchema.parse({}), + settings: v.parse(settingsSchema, {}), loadDefaultSettings: (settings: APIEntity) => set(produce((state: State) => { if (typeof settings !== 'object') return; - state.defaultSettings = settingsSchema.parse(settings); + state.defaultSettings = v.parse(settingsSchema, settings); mergeSettings(state); })), loadUserSettings: (settings?: APIEntity) => set(produce((state: State) => { if (typeof settings !== 'object') return; - state.userSettings = settingsSchemaPartial.parse(settings); + state.userSettings = v.parse(settingsSchemaPartial, settings); mergeSettings(state); })), diff --git a/packages/pl-fe/src/utils/config-db.ts b/packages/pl-fe/src/utils/config-db.ts index 43dd66c0f..9eda99bae 100644 --- a/packages/pl-fe/src/utils/config-db.ts +++ b/packages/pl-fe/src/utils/config-db.ts @@ -4,8 +4,9 @@ import { Set as ImmutableSet, } from 'immutable'; import trimStart from 'lodash/trimStart'; +import * as v from 'valibot'; -import { type MRFSimple, mrfSimpleSchema } from 'pl-fe/schemas/pleroma'; +import { mrfSimpleSchema } from 'pl-fe/schemas/pleroma'; type Config = ImmutableMap; type Policy = Record; @@ -18,7 +19,7 @@ const find = ( config.isSuperset(ImmutableMap({ group, key })), ); -const toSimplePolicy = (configs: ImmutableList): MRFSimple => { +const toSimplePolicy = (configs: ImmutableList) => { const config = find(configs, ':pleroma', ':mrf_simple'); const reducer = (acc: ImmutableMap, curr: ImmutableMap) => { @@ -30,9 +31,9 @@ const toSimplePolicy = (configs: ImmutableList): MRFSimple => { if (config?.get) { const value = config.get('value', ImmutableList()); const result = value.reduce(reducer, ImmutableMap()); - return mrfSimpleSchema.parse(result.toJS()); + return v.parse(mrfSimpleSchema, result.toJS()); } else { - return mrfSimpleSchema.parse({}); + return v.parse(mrfSimpleSchema, {}); } }; From 5027a893d8ca043738f6601e5349ddc03148995e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 13 Oct 2024 22:24:18 +0200 Subject: [PATCH 09/28] pl-fe: Remove unused code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/utils/numbers.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/pl-fe/src/utils/numbers.tsx b/packages/pl-fe/src/utils/numbers.tsx index 5f19c826a..3524dbcee 100644 --- a/packages/pl-fe/src/utils/numbers.tsx +++ b/packages/pl-fe/src/utils/numbers.tsx @@ -1,13 +1,9 @@ import React from 'react'; import { FormattedNumber } from 'react-intl'; -import { z } from 'zod'; /** Check if a value is REALLY a number. */ const isNumber = (value: unknown): value is number => typeof value === 'number' && !isNaN(value); -/** The input is a number and is not NaN. */ -const realNumberSchema = z.coerce.number().refine(n => !isNaN(n)); - const roundDown = (num: number) => { if (num >= 100 && num < 1000) { num = Math.floor(num); @@ -55,7 +51,6 @@ const isIntegerId = (id: string): boolean => new RegExp(/^-?[0-9]+$/g).test(id); export { isNumber, - realNumberSchema, roundDown, shortNumberFormat, isIntegerId, From 79f0d4bbff382e9f49a08a164e12af11a358fc9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 13 Oct 2024 23:49:35 +0200 Subject: [PATCH 10/28] pl-api: Improve types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/lib/client.ts | 109 ++++++++++++++-------------------- 1 file changed, 46 insertions(+), 63 deletions(-) diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts index b91c603c8..6b20c4ba0 100644 --- a/packages/pl-api/lib/client.ts +++ b/packages/pl-api/lib/client.ts @@ -76,28 +76,15 @@ import request, { getNextLink, getPrevLink, type RequestBody, RequestMeta } from import { buildFullPath } from './utils/url'; import type { - Account, AdminAccount, AdminAnnouncement, - AdminCanonicalEmailBlock, - AdminDomainAllow, - AdminDomainBlock, - AdminEmailDomainBlock, - AdminIpBlock, AdminModerationLogEntry, AdminReport, - Chat, - ChatMessage, - Conversation, GroupRole, Instance, - Notification, PleromaConfig, - ScheduledStatus, - Scrobble, Status, StreamingEvent, - Tag, } from './entities'; import type { AdminAccountAction, @@ -209,7 +196,6 @@ import type { UploadMediaParams, } from './params'; import type { PaginatedResponse } from './responses'; -import type { ZodTypeAny } from 'zod'; class PlApiClient { @@ -241,14 +227,14 @@ class PlApiClient { } } - #paginatedGet = async (input: URL | RequestInfo, body: RequestBody, schema: ZodTypeAny): Promise> => { + #paginatedGet = async (input: URL | RequestInfo, body: RequestBody, schema: T): Promise>> => { const getMore = (input: string | null) => input ? async () => { const response = await this.request(input); return { previous: getMore(getPrevLink(response)), next: getMore(getNextLink(response)), - items: filteredArray(schema).parse(response.json) as Array, + items: filteredArray(schema).parse(response.json), partial: response.status === 206, }; } : null; @@ -258,7 +244,7 @@ class PlApiClient { return { previous: getMore(getPrevLink(response)), next: getMore(getNextLink(response)), - items: filteredArray(schema).parse(response.json) as Array, + items: filteredArray(schema).parse(response.json), partial: response.status === 206, }; }; @@ -443,7 +429,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/accounts/#statuses} */ getAccountStatuses: async (accountId: string, params?: GetAccountStatusesParams) => - this.#paginatedGet(`/api/v1/accounts/${accountId}/statuses`, { params }, statusSchema), + this.#paginatedGet(`/api/v1/accounts/${accountId}/statuses`, { params }, statusSchema), /** * Get account’s followers @@ -451,7 +437,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/accounts/#followers} */ getAccountFollowers: async (accountId: string, params?: GetAccountFollowersParams) => - this.#paginatedGet(`/api/v1/accounts/${accountId}/followers`, { params }, accountSchema), + this.#paginatedGet(`/api/v1/accounts/${accountId}/followers`, { params }, accountSchema), /** * Get account’s following @@ -459,7 +445,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/accounts/#following} */ getAccountFollowing: async (accountId: string, params?: GetAccountFollowingParams) => - this.#paginatedGet(`/api/v1/accounts/${accountId}/following`, { params }, accountSchema), + this.#paginatedGet(`/api/v1/accounts/${accountId}/following`, { params }, accountSchema), /** * Get account’s featured tags @@ -640,7 +626,7 @@ class PlApiClient { * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#apiv1pleromaaccountsidfavourites} */ getAccountFavourites: async (accountId: string, params?: GetAccountFavouritesParams) => - this.#paginatedGet(`/api/v1/pleroma/accounts/${accountId}/favourites`, { params }, statusSchema), + this.#paginatedGet(`/api/v1/pleroma/accounts/${accountId}/favourites`, { params }, statusSchema), /** * Interact with profile or status from remote account @@ -679,7 +665,7 @@ class PlApiClient { * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apiv1pleromaaccountsidscrobbles} */ getScrobbles: async (accountId: string, params?: GetScrobblesParams) => - this.#paginatedGet(`/api/v1/pleroma/accounts/${accountId}/scrobbles`, { params }, scrobbleSchema), + this.#paginatedGet(`/api/v1/pleroma/accounts/${accountId}/scrobbles`, { params }, scrobbleSchema), /** * Creates a new Listen activity for an account @@ -703,7 +689,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/bookmarks/#get} */ getBookmarks: async (params?: GetBookmarksParams) => - this.#paginatedGet('/api/v1/bookmarks', { params }, statusSchema), + this.#paginatedGet('/api/v1/bookmarks', { params }, statusSchema), /** * View favourited statuses @@ -711,14 +697,14 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/favourites/#get} */ getFavourites: async (params?: GetFavouritesParams) => - this.#paginatedGet('/api/v1/favourites', { params }, statusSchema), + this.#paginatedGet('/api/v1/favourites', { params }, statusSchema), /** * View pending follow requests * @see {@link https://docs.joinmastodon.org/methods/follow_requests/#get} */ getFollowRequests: async (params?: GetFollowRequestsParams) => - this.#paginatedGet('/api/v1/follow_requests', { params }, accountSchema), + this.#paginatedGet('/api/v1/follow_requests', { params }, accountSchema), /** * Accept follow request @@ -746,7 +732,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/endorsements/#get} */ getEndorsements: async (params?: GetEndorsementsParams) => - this.#paginatedGet('/api/v1/endorsements', { params }, accountSchema), + this.#paginatedGet('/api/v1/endorsements', { params }, accountSchema), /** * View your featured tags @@ -806,7 +792,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/followed_tags/#get} */ getFollowedTags: async (params?: GetFollowedTagsParams) => - this.#paginatedGet('/api/v1/followed_tags', { params }, tagSchema), + this.#paginatedGet('/api/v1/followed_tags', { params }, tagSchema), /** * View information about a single tag @@ -1489,14 +1475,14 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/mutes/#get} */ getMutes: async (params?: GetMutesParams) => - this.#paginatedGet('/api/v1/mutes', { params }, mutedAccountSchema), + this.#paginatedGet('/api/v1/mutes', { params }, mutedAccountSchema), /** * View blocked users * @see {@link https://docs.joinmastodon.org/methods/blocks/#get} */ getBlocks: async (params?: GetBlocksParams) => - this.#paginatedGet('/api/v1/blocks', { params }, accountSchema), + this.#paginatedGet('/api/v1/blocks', { params }, accountSchema), /** * Get domain blocks @@ -1504,7 +1490,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/domain_blocks/#get} */ getDomainBlocks: async (params?: GetDomainBlocksParams) => - this.#paginatedGet('/api/v1/domain_blocks', { params }, z.string()), + this.#paginatedGet('/api/v1/domain_blocks', { params }, z.string()), /** * Block a domain @@ -1859,7 +1845,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/statuses/#reblogged_by} */ getRebloggedBy: async (statusId: string, params?: GetRebloggedByParams) => - this.#paginatedGet(`/api/v1/statuses/${statusId}/reblogged_by`, { params }, accountSchema), + this.#paginatedGet(`/api/v1/statuses/${statusId}/reblogged_by`, { params }, accountSchema), /** * See who favourited a status @@ -1867,7 +1853,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/statuses/#favourited_by} */ getFavouritedBy: async (statusId: string, params?: GetFavouritedByParams) => - this.#paginatedGet(`/api/v1/statuses/${statusId}/favourited_by`, { params }, accountSchema), + this.#paginatedGet(`/api/v1/statuses/${statusId}/favourited_by`, { params }, accountSchema), /** * Favourite a status @@ -2090,7 +2076,7 @@ class PlApiClient { * Requires features{@link Features['quotePosts']}. */ getStatusQuotes: async (statusId: string, params?: GetStatusQuotesParams) => - this.#paginatedGet(`/api/v1/pleroma/statuses/${statusId}/quotes`, { params }, statusSchema), + this.#paginatedGet(`/api/v1/pleroma/statuses/${statusId}/quotes`, { params }, statusSchema), /** * Returns the list of accounts that have disliked the status as known by the current server @@ -2196,7 +2182,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/scheduled_statuses/#get} */ getScheduledStatuses: async (params?: GetScheduledStatusesParams) => - this.#paginatedGet('/api/v1/scheduled_statuses', { params }, scheduledStatusSchema), + this.#paginatedGet('/api/v1/scheduled_statuses', { params }, scheduledStatusSchema), /** * View a single scheduled status @@ -2239,7 +2225,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/timelines/#public} */ publicTimeline: (params?: PublicTimelineParams) => - this.#paginatedGet('/api/v1/timelines/public', { params }, statusSchema), + this.#paginatedGet('/api/v1/timelines/public', { params }, statusSchema), /** * View hashtag timeline @@ -2247,7 +2233,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/timelines/#tag} */ hashtagTimeline: (hashtag: string, params?: HashtagTimelineParams) => - this.#paginatedGet(`/api/v1/timelines/tag/${hashtag}`, { params }, statusSchema), + this.#paginatedGet(`/api/v1/timelines/tag/${hashtag}`, { params }, statusSchema), /** * View home timeline @@ -2255,7 +2241,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/timelines/#home} */ homeTimeline: (params?: HomeTimelineParams) => - this.#paginatedGet('/api/v1/timelines/home', { params }, statusSchema), + this.#paginatedGet('/api/v1/timelines/home', { params }, statusSchema), /** * View link timeline @@ -2263,7 +2249,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/timelines/#link} */ linkTimeline: (url: string, params?: HashtagTimelineParams) => - this.#paginatedGet('/api/v1/timelines/link', { params: { ...params, url } }, statusSchema), + this.#paginatedGet('/api/v1/timelines/link', { params: { ...params, url } }, statusSchema), /** * View list timeline @@ -2271,14 +2257,14 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/timelines/#list} */ listTimeline: (listId: string, params?: ListTimelineParams) => - this.#paginatedGet(`/api/v1/timelines/list/${listId}`, { params }, statusSchema), + this.#paginatedGet(`/api/v1/timelines/list/${listId}`, { params }, statusSchema), /** * View all conversations * @see {@link https://docs.joinmastodon.org/methods/conversations/#get} */ getConversations: (params?: GetConversationsParams) => - this.#paginatedGet('/api/v1/conversations', { params }, conversationSchema), + this.#paginatedGet('/api/v1/conversations', { params }, conversationSchema), /** * Remove a conversation @@ -2327,13 +2313,13 @@ class PlApiClient { * Requires features{@link Features['groups']}. */ groupTimeline: async (groupId: string, params?: GroupTimelineParams) => - this.#paginatedGet(`/api/v1/timelines/group/${groupId}`, { params }, statusSchema), + this.#paginatedGet(`/api/v1/timelines/group/${groupId}`, { params }, statusSchema), /** * Requires features{@link Features['bubbleTimeline']}. */ bubbleTimeline: async (params?: BubbleTimelineParams) => - this.#paginatedGet('/api/v1/timelines/bubble', { params }, statusSchema), + this.#paginatedGet('/api/v1/timelines/bubble', { params }, statusSchema), }; public readonly lists = { @@ -2396,7 +2382,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/lists/#accounts} */ getListAccounts: async (listId: string, params?: GetListAccountsParams) => - this.#paginatedGet(`/api/v1/lists/${listId}/accounts`, { params }, accountSchema), + this.#paginatedGet(`/api/v1/lists/${listId}/accounts`, { params }, accountSchema), /** * Add accounts to a list @@ -2502,7 +2488,7 @@ class PlApiClient { ...params.exclude_types.filter(type => PLEROMA_TYPES.includes(type)).map(type => `pleroma:${type}`), ]; - return this.#paginatedGet('/api/v1/notifications', { ...meta, params }, notificationSchema); + return this.#paginatedGet('/api/v1/notifications', { ...meta, params }, notificationSchema); }, /** @@ -2566,7 +2552,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/notifications/#get-requests} */ getNotificationRequests: async (params?: GetNotificationRequestsParams) => - this.#paginatedGet('/api/v1/notifications/requests', { params }, notificationRequestSchema), + this.#paginatedGet('/api/v1/notifications/requests', { params }, notificationRequestSchema), /** * Get a single notification request @@ -2915,7 +2901,7 @@ class PlApiClient { */ getAccounts: async (params?: AdminGetAccountsParams) => { if (this.features.mastodonAdminV2) { - return this.#paginatedGet('/api/v2/admin/accounts', { params }, adminAccountSchema); + return this.#paginatedGet('/api/v2/admin/accounts', { params }, adminAccountSchema); } else { return this.#paginatedPleromaAccounts(params ? { query: params.username, @@ -3222,7 +3208,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/admin/domain_blocks/#get} */ getDomainBlocks: (params?: AdminGetDomainBlocksParams) => - this.#paginatedGet('/api/v1/admin/domain_blocks', { params }, adminDomainBlockSchema), + this.#paginatedGet('/api/v1/admin/domain_blocks', { params }, adminDomainBlockSchema), /** * Get a single blocked domain @@ -3286,7 +3272,7 @@ class PlApiClient { */ getReports: async (params?: AdminGetReportsParams) => { if (this.features.mastodonAdmin) { - return this.#paginatedGet('/api/v1/admin/reports', { params }, adminReportSchema); + return this.#paginatedGet('/api/v1/admin/reports', { params }, adminReportSchema); } else { return this.#paginatedPleromaReports({ state: params?.resolved === true ? 'resolved' : params?.resolved === false ? 'open' : undefined, @@ -3478,7 +3464,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/admin/canonical_email_blocks/#get} */ getCanonicalEmailBlocks: async (params?: AdminGetCanonicalEmailBlocks) => - this.#paginatedGet('/api/v1/admin/canonical_email_blocks', { params }, adminCanonicalEmailBlockSchema), + this.#paginatedGet('/api/v1/admin/canonical_email_blocks', { params }, adminCanonicalEmailBlockSchema), /** * Show a single canonical email block @@ -3544,7 +3530,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/admin/domain_allows/#get} */ getDomainAllows: (params?: AdminGetDomainAllowsParams) => - this.#paginatedGet('/api/v1/admin/domain_allows', { params }, adminDomainAllowSchema), + this.#paginatedGet('/api/v1/admin/domain_allows', { params }, adminDomainAllowSchema), /** * Get a single allowed domain @@ -3588,7 +3574,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/admin/email_domain_blocks/#get} */ getEmailDomainBlocks: (params?: AdminGetEmailDomainBlocksParams) => - this.#paginatedGet('/api/v1/admin/email_domain_blocks', { params }, adminEmailDomainBlockSchema), + this.#paginatedGet('/api/v1/admin/email_domain_blocks', { params }, adminEmailDomainBlockSchema), /** * Get a single blocked email domain @@ -3632,7 +3618,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/admin/ip_blocks/#get} */ getIpBlocks: (params?: AdminGetIpBlocksParams) => - this.#paginatedGet('/api/v1/admin/ip_blocks', { params }, adminIpBlockSchema), + this.#paginatedGet('/api/v1/admin/ip_blocks', { params }, adminIpBlockSchema), /** * Get a single IP block @@ -4019,14 +4005,14 @@ class PlApiClient { * @see {@link https://docs.pleroma.social/backend/development/API/chats/#getting-a-list-of-chats} */ getChats: async (params?: GetChatsParams) => - this.#paginatedGet('/api/v2/pleroma/chats', { params }, chatSchema), + this.#paginatedGet('/api/v2/pleroma/chats', { params }, chatSchema), /** * Getting the messages for a Chat * For a given Chat id, you can get the associated messages with */ getChatMessages: async (chatId: string, params?: GetChatMessagesParams) => - this.#paginatedGet(`/api/v1/pleroma/chats/${chatId}/messages`, { params }, chatMessageSchema), + this.#paginatedGet(`/api/v1/pleroma/chats/${chatId}/messages`, { params }, chatMessageSchema), /** * Posting a chat message @@ -4088,24 +4074,21 @@ class PlApiClient { * @see {@link } */ getJoinedEvents: async (state?: 'pending' | 'reject' | 'accept', params?: GetJoinedEventsParams) => - this.#paginatedGet('/api/v1/pleroma/events/joined_events', { params: { ...params, state } }, statusSchema), + this.#paginatedGet('/api/v1/pleroma/events/joined_events', { params: { ...params, state } }, statusSchema), /** * Gets event participants * @see {@link https://github.com/mkljczk/pl/blob/fork/docs/development/API/pleroma_api.md#apiv1pleromaeventsidparticipations} */ getEventParticipations: async (statusId: string, params?: GetEventParticipationsParams) => - this.#paginatedGet(`/api/v1/pleroma/events/${statusId}/participations`, { params }, accountSchema), + this.#paginatedGet(`/api/v1/pleroma/events/${statusId}/participations`, { params }, accountSchema), /** * Gets event participation requests * @see {@link https://github.com/mkljczk/pl/blob/fork/docs/development/API/pleroma_api.md#apiv1pleromaeventsidparticipation_requests} */ getEventParticipationRequests: async (statusId: string, params?: GetEventParticipationRequestsParams) => - this.#paginatedGet<{ - account:Account; - participation_message: string; - }>(`/api/v1/pleroma/events/${statusId}/participation_requests`, { params }, z.object({ + this.#paginatedGet(`/api/v1/pleroma/events/${statusId}/participation_requests`, { params }, z.object({ account: accountSchema, participation_message: z.string().catch(''), })), @@ -4293,11 +4276,11 @@ class PlApiClient { /** Has an optional role attribute that can be used to filter by role (valid roles are `"admin"`, `"moderator"`, `"user"`). */ getGroupMemberships: async (groupId: string, role?: GroupRole, params?: GetGroupMembershipsParams) => - this.#paginatedGet(`/api/v1/groups/${groupId}/memberships`, { params: { ...params, role } }, groupMemberSchema), + this.#paginatedGet(`/api/v1/groups/${groupId}/memberships`, { params: { ...params, role } }, groupMemberSchema), /** returns an array of `Account` entities representing pending requests to join a group */ getGroupMembershipRequests: async (groupId: string, params?: GetGroupMembershipRequestsParams) => - this.#paginatedGet(`/api/v1/groups/${groupId}/membership_requests`, { params }, accountSchema), + this.#paginatedGet(`/api/v1/groups/${groupId}/membership_requests`, { params }, accountSchema), /** accept a pending request to become a group member */ acceptGroupMembershipRequest: async (groupId: string, accountId: string) => { @@ -4322,7 +4305,7 @@ class PlApiClient { /** list accounts blocked from interacting with the group */ getGroupBlocks: async (groupId: string, params?: GetGroupBlocksParams) => - this.#paginatedGet(`/api/v1/groups/${groupId}/blocks`, { params }, accountSchema), + this.#paginatedGet(`/api/v1/groups/${groupId}/blocks`, { params }, accountSchema), /** block one or more users. If they were in the group, they are also kicked of it */ blockGroupUsers: async (groupId: string, accountIds: string[]) => { From a6bc160caa667136f92b1f29587b1780332a4824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 14 Oct 2024 00:25:30 +0200 Subject: [PATCH 11/28] pl-api: Do some blind search and replace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/lib/client.ts | 510 +++++++++--------- .../pl-api/lib/entities/account-warning.ts | 22 +- packages/pl-api/lib/entities/account.ts | 114 ++-- packages/pl-api/lib/entities/admin/account.ts | 42 +- .../pl-api/lib/entities/admin/announcement.ts | 6 +- .../entities/admin/canonical-email-block.ts | 10 +- packages/pl-api/lib/entities/admin/cohort.ts | 8 +- .../pl-api/lib/entities/admin/dimension.ts | 20 +- .../pl-api/lib/entities/admin/domain-allow.ts | 10 +- .../pl-api/lib/entities/admin/domain-block.ts | 22 +- packages/pl-api/lib/entities/admin/domain.ts | 12 +- .../lib/entities/admin/email-domain-block.ts | 12 +- .../pl-api/lib/entities/admin/ip-block.ts | 10 +- packages/pl-api/lib/entities/admin/ip.ts | 6 +- packages/pl-api/lib/entities/admin/measure.ts | 14 +- .../entities/admin/moderation-log-entry.ts | 12 +- .../lib/entities/admin/pleroma-config.ts | 14 +- packages/pl-api/lib/entities/admin/relay.ts | 12 +- packages/pl-api/lib/entities/admin/report.ts | 22 +- packages/pl-api/lib/entities/admin/rule.ts | 14 +- packages/pl-api/lib/entities/admin/tag.ts | 12 +- .../lib/entities/announcement-reaction.ts | 16 +- packages/pl-api/lib/entities/announcement.ts | 16 +- packages/pl-api/lib/entities/application.ts | 20 +- packages/pl-api/lib/entities/backup.ts | 12 +- .../pl-api/lib/entities/bookmark-folder.ts | 12 +- packages/pl-api/lib/entities/chat-message.ts | 20 +- packages/pl-api/lib/entities/chat.ts | 10 +- packages/pl-api/lib/entities/context.ts | 6 +- packages/pl-api/lib/entities/conversation.ts | 12 +- packages/pl-api/lib/entities/custom-emoji.ts | 16 +- .../pl-api/lib/entities/directory/category.ts | 8 +- .../pl-api/lib/entities/directory/language.ts | 10 +- .../pl-api/lib/entities/directory/server.ts | 26 +- .../entities/directory/statistics-period.ts | 6 +- packages/pl-api/lib/entities/domain-block.ts | 12 +- .../pl-api/lib/entities/emoji-reaction.ts | 14 +- .../lib/entities/extended-description.ts | 8 +- .../pl-api/lib/entities/familiar-followers.ts | 8 +- packages/pl-api/lib/entities/featured-tag.ts | 12 +- packages/pl-api/lib/entities/filter-result.ts | 10 +- packages/pl-api/lib/entities/filter.ts | 24 +- packages/pl-api/lib/entities/group-member.ts | 8 +- .../pl-api/lib/entities/group-relationship.ts | 12 +- packages/pl-api/lib/entities/group.ts | 36 +- packages/pl-api/lib/entities/instance.ts | 124 ++--- .../pl-api/lib/entities/interaction-policy.ts | 6 +- .../lib/entities/interaction-request.ts | 14 +- packages/pl-api/lib/entities/list.ts | 12 +- packages/pl-api/lib/entities/location.ts | 30 +- packages/pl-api/lib/entities/marker.ts | 10 +- .../pl-api/lib/entities/media-attachment.ts | 40 +- packages/pl-api/lib/entities/mention.ts | 12 +- .../lib/entities/notification-policy.ts | 16 +- .../lib/entities/notification-request.ts | 8 +- packages/pl-api/lib/entities/notification.ts | 22 +- packages/pl-api/lib/entities/oauth-token.ts | 8 +- packages/pl-api/lib/entities/poll.ts | 28 +- packages/pl-api/lib/entities/preview-card.ts | 26 +- .../entities/relationship-severance-event.ts | 10 +- packages/pl-api/lib/entities/relationship.ts | 32 +- packages/pl-api/lib/entities/report.ts | 24 +- packages/pl-api/lib/entities/role.ts | 14 +- packages/pl-api/lib/entities/rule.ts | 12 +- .../pl-api/lib/entities/scheduled-status.ts | 34 +- packages/pl-api/lib/entities/scrobble.ts | 16 +- packages/pl-api/lib/entities/search.ts | 6 +- packages/pl-api/lib/entities/status-edit.ts | 16 +- packages/pl-api/lib/entities/status-source.ts | 18 +- packages/pl-api/lib/entities/status.ts | 94 ++-- .../pl-api/lib/entities/streaming-event.ts | 34 +- packages/pl-api/lib/entities/suggestion.ts | 10 +- packages/pl-api/lib/entities/tag.ts | 10 +- packages/pl-api/lib/entities/token.ts | 16 +- packages/pl-api/lib/entities/translation.ts | 30 +- packages/pl-api/lib/entities/trends-link.ts | 34 +- packages/pl-api/lib/entities/utils.ts | 6 +- .../lib/entities/web-push-subscription.ts | 10 +- packages/pl-api/package.json | 1 + packages/pl-api/yarn.lock | 5 + .../modals/manage-group-modal/index.tsx | 6 +- 81 files changed, 1019 insertions(+), 1013 deletions(-) diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts index 6b20c4ba0..13cc6dd53 100644 --- a/packages/pl-api/lib/client.ts +++ b/packages/pl-api/lib/client.ts @@ -1,4 +1,4 @@ -import z from 'zod'; +import * as v from 'valibot'; import { accountSchema, @@ -227,14 +227,14 @@ class PlApiClient { } } - #paginatedGet = async (input: URL | RequestInfo, body: RequestBody, schema: T): Promise>> => { + #paginatedGet = async >(input: URL | RequestInfo, body: RequestBody, schema: T): Promise>> => { const getMore = (input: string | null) => input ? async () => { const response = await this.request(input); return { previous: getMore(getPrevLink(response)), next: getMore(getNextLink(response)), - items: filteredArray(schema).parse(response.json), + items: v.parse(filteredArray(schema), response.json), partial: response.status === 206, }; } : null; @@ -244,7 +244,7 @@ class PlApiClient { return { previous: getMore(getPrevLink(response)), next: getMore(getNextLink(response)), - items: filteredArray(schema).parse(response.json), + items: v.parse(filteredArray(schema), response.json), partial: response.status === 206, }; }; @@ -266,7 +266,7 @@ class PlApiClient { next: response.json?.count > (params.page_size * ((params.page || 1) - 1) + response.json?.users?.length) ? () => this.#paginatedPleromaAccounts({ ...params, page: (params.page || 0) + 1 }) : null, - items: filteredArray(adminAccountSchema).parse(response.json?.users), + items: v.parse(filteredArray(adminAccountSchema), response.json?.users), partial: response.status === 206, total: response.json?.total, }; @@ -285,7 +285,7 @@ class PlApiClient { next: response.json?.total > (params.page_size * ((params.page || 1) - 1) + response.json?.reports?.length) ? () => this.#paginatedPleromaReports({ ...params, page: (params.page || 0) + 1 }) : null, - items: filteredArray(adminReportSchema).parse(response.json?.reports), + items: v.parse(filteredArray(adminReportSchema), response.json?.reports), partial: response.status === 206, total: response.json?.total, }; @@ -305,7 +305,7 @@ class PlApiClient { next: response.json?.length ? () => this.#paginatedPleromaStatuses({ ...params, page: (params.page || 0) + 1 }) : null, - items: filteredArray(statusSchema).parse(response.json), + items: v.parse(filteredArray(statusSchema), response.json), partial: response.status === 206, }; }; @@ -320,7 +320,7 @@ class PlApiClient { createApplication: async (params: CreateApplicationParams) => { const response = await this.request('/api/v1/apps', { method: 'POST', body: params }); - return applicationSchema.parse(response.json); + return v.parse(applicationSchema, response.json); }, /** @@ -331,7 +331,7 @@ class PlApiClient { verifyApplication: async () => { const response = await this.request('/api/v1/apps/verify_credentials'); - return applicationSchema.parse(response.json); + return v.parse(applicationSchema, response.json); }, }; @@ -344,7 +344,7 @@ class PlApiClient { authorize: async (params: OauthAuthorizeParams) => { const response = await this.request('/oauth/authorize', { params }); - return z.string().parse(response.json); + return v.parse(v.string(), response.json); }, /** @@ -355,7 +355,7 @@ class PlApiClient { getToken: async (params: GetTokenParams) => { const response = await this.request('/oauth/token', { method: 'POST', body: params }); - return tokenSchema.parse(response.json); + return v.parse(tokenSchema, response.json); }, /** @@ -378,15 +378,15 @@ class PlApiClient { getCaptcha: async () => { const response = await this.request('/api/pleroma/captcha'); - return z.intersection(z.object({ - type: z.string(), - }), z.record(z.any())).parse(response.json); + return v.parse(v.intersect([v.object({ + type: v.string(), + }), z.record(v.any())]), response.json); }, mfaChallenge: async (params: MfaChallengeParams) => { const response = await this.request('/oauth/mfa/challenge', { method: 'POST', body: params }); - return tokenSchema.parse(response.json); + return v.parse(tokenSchema, response.json); }, }; @@ -407,7 +407,7 @@ class PlApiClient { getAccount: async (accountId: string, params?: GetAccountParams) => { const response = await this.request(`/api/v1/accounts/${accountId}`, { params }); - return accountSchema.parse(response.json); + return v.parse(accountSchema, response.json); }, /** @@ -420,7 +420,7 @@ class PlApiClient { getAccounts: async (accountId: string[]) => { const response = await this.request('/api/v1/accounts', { params: { id: accountId } }); - return filteredArray(accountSchema).parse(response.json); + return v.parse(filteredArray(accountSchema), response.json); }, /** @@ -455,7 +455,7 @@ class PlApiClient { getAccountFeaturedTags: async (accountId: string) => { const response = await this.request(`/api/v1/accounts/${accountId}/featured_tags`); - return filteredArray(featuredTagSchema).parse(response.json); + return v.parse(filteredArray(featuredTagSchema), response.json); }, /** @@ -466,7 +466,7 @@ class PlApiClient { getAccountLists: async (accountId: string) => { const response = await this.request(`/api/v1/accounts/${accountId}/lists`); - return filteredArray(listSchema).parse(response.json); + return v.parse(filteredArray(listSchema), response.json); }, /** @@ -477,7 +477,7 @@ class PlApiClient { followAccount: async (accountId: string, params?: FollowAccountParams) => { const response = await this.request(`/api/v1/accounts/${accountId}/follow`, { method: 'POST', body: params }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -488,7 +488,7 @@ class PlApiClient { unfollowAccount: async (accountId: string) => { const response = await this.request(`/api/v1/accounts/${accountId}/unfollow`, { method: 'POST' }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -499,7 +499,7 @@ class PlApiClient { removeAccountFromFollowers: async (accountId: string) => { const response = await this.request(`/api/v1/accounts/${accountId}/remove_from_followers`, { method: 'POST' }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -510,7 +510,7 @@ class PlApiClient { pinAccount: async (accountId: string) => { const response = await this.request(`/api/v1/accounts/${accountId}/pin`, { method: 'POST' }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -521,7 +521,7 @@ class PlApiClient { unpinAccount: async (accountId: string) => { const response = await this.request(`/api/v1/accounts/${accountId}/unpin`, { method: 'POST' }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -532,7 +532,7 @@ class PlApiClient { updateAccountNote: async (accountId: string, comment: string) => { const response = await this.request(`/api/v1/accounts/${accountId}/note`, { method: 'POST', body: { comment } }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -543,7 +543,7 @@ class PlApiClient { getRelationships: async (accountIds: string[], params?: GetRelationshipsParams) => { const response = await this.request('/api/v1/accounts/relationships', { params: { ...params, id: accountIds } }); - return filteredArray(relationshipSchema).parse(response.json); + return v.parse(filteredArray(relationshipSchema), response.json); }, /** @@ -556,7 +556,7 @@ class PlApiClient { getFamiliarFollowers: async (accountIds: string[]) => { const response = await this.request('/api/v1/accounts/familiar_followers', { params: { id: accountIds } }); - return filteredArray(familiarFollowersSchema).parse(response.json); + return v.parse(filteredArray(familiarFollowersSchema), response.json); }, /** @@ -567,7 +567,7 @@ class PlApiClient { searchAccounts: async (q: string, params?: SearchAccountParams, meta?: RequestMeta) => { const response = await this.request('/api/v1/accounts/search', { ...meta, params: { ...params, q } }); - return filteredArray(accountSchema).parse(response.json); + return v.parse(filteredArray(accountSchema), response.json); }, /** @@ -578,7 +578,7 @@ class PlApiClient { lookupAccount: async (acct: string, meta?: RequestMeta) => { const response = await this.request('/api/v1/accounts/lookup', { ...meta, params: { acct } }); - return accountSchema.parse(response.json); + return v.parse(accountSchema, response.json); }, /** @@ -591,7 +591,7 @@ class PlApiClient { body: { ...params, account_id: accountId }, }); - return reportSchema.parse(response.json); + return v.parse(reportSchema, response.json); }, /** @@ -604,7 +604,7 @@ class PlApiClient { getAccountEndorsements: async (accountId: string, params?: GetAccountEndorsementsParams) => { const response = await this.request(`/api/v1/pleroma/accounts/${accountId}/endorsements`, { params }); - return filteredArray(accountSchema).parse(response.json); + return v.parse(filteredArray(accountSchema), response.json); }, /** @@ -616,7 +616,7 @@ class PlApiClient { getBirthdays: async (day: number, month: number) => { const response = await this.request('/api/v1/pleroma/birthdays', { params: { day, month } }); - return filteredArray(accountSchema).parse(response.json); + return v.parse(filteredArray(accountSchema), response.json); }, /** @@ -641,9 +641,9 @@ class PlApiClient { if (response.json?.error) throw response.json.error; - return z.object({ - url: z.string(), - }).parse(response.json); + return v.parse(v.object({ + url: v.string(), + }), response.json); }, /** @@ -678,7 +678,7 @@ class PlApiClient { const response = await this.request('/api/v1/pleroma/scrobble', { body: params }); - return scrobbleSchema.parse(response.json); + return v.parse(scrobbleSchema, response.json); }, }; @@ -713,7 +713,7 @@ class PlApiClient { acceptFollowRequest: async (accountId: string) => { const response = await this.request(`/api/v1/follow_requests/${accountId}/authorize`, { method: 'POST' }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -723,7 +723,7 @@ class PlApiClient { rejectFollowRequest: async (accountId: string) => { const response = await this.request(`/api/v1/follow_requests/${accountId}/reject`, { method: 'POST' }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -742,7 +742,7 @@ class PlApiClient { getFeaturedTags: async () => { const response = await this.request('/api/v1/featured_tags'); - return filteredArray(featuredTagSchema).parse(response.json); + return v.parse(filteredArray(featuredTagSchema), response.json); }, /** @@ -756,7 +756,7 @@ class PlApiClient { body: { name }, }); - return filteredArray(featuredTagSchema).parse(response.json); + return v.parse(filteredArray(featuredTagSchema), response.json); }, /** @@ -781,7 +781,7 @@ class PlApiClient { getFeaturedTagsSuggestions: async () => { const response = await this.request('/api/v1/featured_tags/suggestions'); - return filteredArray(tagSchema).parse(response.json); + return v.parse(filteredArray(tagSchema), response.json); }, /** @@ -802,7 +802,7 @@ class PlApiClient { getTag: async (tagId: string) => { const response = await this.request(`/api/v1/tags/${tagId}`); - return tagSchema.parse(response.json); + return v.parse(tagSchema, response.json); }, /** @@ -813,7 +813,7 @@ class PlApiClient { followTag: async (tagId: string) => { const response = await this.request(`/api/v1/tags/${tagId}/follow`, { method: 'POST' }); - return tagSchema.parse(response.json); + return v.parse(tagSchema, response.json); }, /** @@ -824,7 +824,7 @@ class PlApiClient { unfollowTag: async (tagId: string) => { const response = await this.request(`/api/v1/tags/${tagId}/unfollow`, { method: 'POST' }); - return tagSchema.parse(response.json); + return v.parse(tagSchema, response.json); }, /** @@ -840,7 +840,7 @@ class PlApiClient { { params: { limit } }, ); - return filteredArray(suggestionSchema).parse(response.json); + return v.parse(filteredArray(suggestionSchema), response.json); }, /** @@ -865,7 +865,7 @@ class PlApiClient { getBookmarkFolders: async () => { const response = await this.request('/api/v1/pleroma/bookmark_folders'); - return filteredArray(bookmarkFolderSchema).parse(response.json); + return v.parse(filteredArray(bookmarkFolderSchema), response.json); }, /** @@ -877,7 +877,7 @@ class PlApiClient { createBookmarkFolder: async (params: CreateBookmarkFolderParams) => { const response = await this.request('/api/v1/pleroma/bookmark_folders', { method: 'POST', body: params }); - return bookmarkFolderSchema.parse(response.json); + return v.parse(bookmarkFolderSchema, response.json); }, /** @@ -889,7 +889,7 @@ class PlApiClient { updateBookmarkFolder: async (bookmarkFolderId: string, params: UpdateBookmarkFolderParams) => { const response = await this.request(`/api/v1/pleroma/bookmark_folders/${bookmarkFolderId}`, { method: 'PATCH', body: params }); - return bookmarkFolderSchema.parse(response.json); + return v.parse(bookmarkFolderSchema, response.json); }, /** @@ -901,7 +901,7 @@ class PlApiClient { deleteBookmarkFolder: async (bookmarkFolderId: string) => { const response = await this.request(`/api/v1/pleroma/bookmark_folders/${bookmarkFolderId}`, { method: 'DELETE' }); - return bookmarkFolderSchema.parse(response.json); + return v.parse(bookmarkFolderSchema, response.json); }, }; @@ -919,7 +919,7 @@ class PlApiClient { body: { language: params.locale, ...params }, }); - return tokenSchema.parse(response.json); + return v.parse(tokenSchema, response.json); }, /** @@ -930,7 +930,7 @@ class PlApiClient { verifyCredentials: async () => { const response = await this.request('/api/v1/accounts/verify_credentials'); - return credentialAccountSchema.parse(response.json); + return v.parse(credentialAccountSchema, response.json); }, /** @@ -955,7 +955,7 @@ class PlApiClient { body: params, }); - return credentialAccountSchema.parse(response.json); + return v.parse(credentialAccountSchema, response.json); }, /** @@ -966,7 +966,7 @@ class PlApiClient { deleteAvatar: async () => { const response = await this.request('/api/v1/profile/avatar', { method: 'DELETE' }); - return credentialAccountSchema.parse(response.json); + return v.parse(credentialAccountSchema, response.json); }, /** @@ -977,7 +977,7 @@ class PlApiClient { deleteHeader: async () => { const response = await this.request('/api/v1/profile/header', { method: 'DELETE' }); - return credentialAccountSchema.parse(response.json); + return v.parse(credentialAccountSchema, response.json); }, /** @@ -999,7 +999,7 @@ class PlApiClient { createBackup: async () => { const response = await this.request('/api/v1/pleroma/backups', { method: 'POST' }); - return backupSchema.parse(response.json); + return v.parse(backupSchema, response.json); }, /** @@ -1010,7 +1010,7 @@ class PlApiClient { getBackups: async () => { const response = await this.request('/api/v1/pleroma/backups'); - return filteredArray(backupSchema).parse(response.json); + return v.parse(filteredArray(backupSchema), response.json); }, /** @@ -1022,7 +1022,7 @@ class PlApiClient { getAccountAliases: async () => { const response = await this.request('/api/v1/pleroma/aliases'); - return z.object({ aliases: filteredArray(z.string()) }).parse(response.json); + return v.parse(v.object({ aliases: filteredArray(v.string()) }), response.json); }, /** @@ -1035,7 +1035,7 @@ class PlApiClient { addAccountAlias: async (alias: string) => { const response = await this.request('/api/v1/pleroma/aliases', { method: 'PUT', body: { alias } }); - return z.object({ status: z.literal('success') }).parse(response.json); + return v.parse(v.object({ status: v.literal('success') }), response.json); }, /** @@ -1048,7 +1048,7 @@ class PlApiClient { deleteAccountAlias: async (alias: string) => { const response = await this.request('/api/v1/pleroma/aliases', { method: 'DELETE', body: { alias } }); - return z.object({ status: z.literal('success') }).parse(response.json); + return v.parse(v.object({ status: v.literal('success') }), response.json); }, /** @@ -1060,7 +1060,7 @@ class PlApiClient { getOauthTokens: async () => { const response = await this.request('/api/oauth_tokens'); - return filteredArray(oauthTokenSchema).parse(response.json); + return v.parse(filteredArray(oauthTokenSchema), response.json); }, /** @@ -1221,12 +1221,12 @@ class PlApiClient { getMfaSettings: async () => { const response = await this.request('/api/pleroma/accounts/mfa'); - return z.object({ - settings: z.object({ - enabled: z.boolean(), - totp: z.boolean(), + return v.parse(v.object({ + settings: v.object({ + enabled: v.boolean(), + totp: v.boolean(), }), - }).parse(response.json); + }), response.json); }, /** @@ -1235,9 +1235,9 @@ class PlApiClient { getMfaBackupCodes: async () => { const response = await this.request('/api/pleroma/accounts/mfa/backup_codes'); - return z.object({ - codes: z.array(z.string()), - }).parse(response.json); + return v.parse(v.object({ + codes: v.array(v.string()), + }), response.json); }, /** @@ -1246,10 +1246,10 @@ class PlApiClient { getMfaSetup: async (method: 'totp') => { const response = await this.request(`/api/pleroma/accounts/mfa/setup/${method}`); - return z.object({ - key: z.string(), - provisioning_uri: z.string(), - }).parse(response.json); + return v.parse(v.object({ + key: v.string(), + provisioning_uri: v.string(), + }), response.json); }, /** @@ -1366,7 +1366,7 @@ class PlApiClient { if (response.json?.error) throw response.json.error; - return z.object({ status: z.string() }).parse(response.json); + return v.parse(v.object({ status: v.string() }), response.json); }, /** @@ -1378,7 +1378,7 @@ class PlApiClient { getInteractionPolicies: async () => { const response = await this.request('/api/v1/interaction_policies/defaults'); - return interactionPoliciesSchema.parse(response.json); + return v.parse(interactionPoliciesSchema, response.json); }, /** @@ -1390,7 +1390,7 @@ class PlApiClient { updateInteractionPolicies: async (params: UpdateInteractionPoliciesParams) => { const response = await this.request('/api/v1/interaction_policies/defaults', { method: 'PATCH', body: params }); - return interactionPoliciesSchema.parse(response.json); + return v.parse(interactionPoliciesSchema, response.json); }, /** @@ -1401,7 +1401,7 @@ class PlApiClient { getAvailableFrontends: async () => { const response = await this.request('/api/v1/akkoma/preferred_frontend/available'); - return z.array(z.string()).parse(response.json); + return v.parse(v.array(v.string()), response.json); }, /** @@ -1414,7 +1414,7 @@ class PlApiClient { setPreferredFrontend: async (frontendName: string) => { const response = await this.request('/api/v1/akkoma/preferred_frontend', { method: 'PUT', body: { frontend_name: frontendName } }); - return z.object({ frontend_name: z.string() }).parse(response.json); + return v.parse(v.object({ frontend_name: v.string() }), response.json); }, }; @@ -1427,7 +1427,7 @@ class PlApiClient { blockAccount: async (accountId: string) => { const response = await this.request(`/api/v1/accounts/${accountId}/block`, { method: 'POST' }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -1438,7 +1438,7 @@ class PlApiClient { unblockAccount: async (accountId: string) => { const response = await this.request(`/api/v1/accounts/${accountId}/unblock`, { method: 'POST' }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -1451,7 +1451,7 @@ class PlApiClient { muteAccount: async (accountId: string, params?: MuteAccountParams) => { const response = await this.request(`/api/v1/accounts/${accountId}/mute`, { method: 'POST', body: params }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -1464,7 +1464,7 @@ class PlApiClient { unmuteAccount: async (accountId: string) => { const response = await this.request(`/api/v1/accounts/${accountId}/unmute`, { method: 'POST' }); - return relationshipSchema.parse(response.json); + return v.parse(relationshipSchema, response.json); }, /** @@ -1490,7 +1490,7 @@ class PlApiClient { * @see {@link https://docs.joinmastodon.org/methods/domain_blocks/#get} */ getDomainBlocks: async (params?: GetDomainBlocksParams) => - this.#paginatedGet('/api/v1/domain_blocks', { params }, z.string()), + this.#paginatedGet('/api/v1/domain_blocks', { params }, v.string()), /** * Block a domain @@ -1531,7 +1531,7 @@ class PlApiClient { getFilters: async () => { const response = await this.request(this.features.filtersV2 ? '/api/v2/filters' : '/api/v1/filters'); - return filteredArray(filterSchema).parse(response.json); + return v.parse(filteredArray(filterSchema), response.json); }, /** @@ -1548,7 +1548,7 @@ class PlApiClient { : `/api/v1/filters/${filterId}`, ); - return filterSchema.parse(response.json); + return v.parse(filterSchema, response.json); }, /** @@ -1574,7 +1574,7 @@ class PlApiClient { }, ); - return filterSchema.parse(response.json); + return v.parse(filterSchema, response.json); }, /** @@ -1600,7 +1600,7 @@ class PlApiClient { }, ); - return filterSchema.parse(response.json); + return v.parse(filterSchema, response.json); }, /** @@ -1631,7 +1631,7 @@ class PlApiClient { getFilterKeywords: async (filterId: string) => { const response = await this.request(`/api/v2/filters/${filterId}/keywords`); - return filteredArray(filterKeywordSchema).parse(response.json); + return v.parse(filteredArray(filterKeywordSchema), response.json); }, /** @@ -1647,7 +1647,7 @@ class PlApiClient { body: { keyword, whole_word }, }); - return filterKeywordSchema.parse(response.json); + return v.parse(filterKeywordSchema, response.json); }, /** @@ -1660,7 +1660,7 @@ class PlApiClient { getFilterKeyword: async (filterId: string) => { const response = await this.request(`/api/v2/filters/keywords/${filterId}`); - return filterKeywordSchema.parse(response.json); + return v.parse(filterKeywordSchema, response.json); }, /** @@ -1676,7 +1676,7 @@ class PlApiClient { body: { keyword, whole_word }, }); - return filterKeywordSchema.parse(response.json); + return v.parse(filterKeywordSchema, response.json); }, /** @@ -1702,7 +1702,7 @@ class PlApiClient { getFilterStatuses: async (filterId: string) => { const response = await this.request(`/api/v2/filters/${filterId}/statuses`); - return filteredArray(filterStatusSchema).parse(response.json); + return v.parse(filteredArray(filterStatusSchema), response.json); }, /** @@ -1718,7 +1718,7 @@ class PlApiClient { body: { status_id: statusId }, }); - return filterStatusSchema.parse(response.json); + return v.parse(filterStatusSchema, response.json); }, /** @@ -1731,7 +1731,7 @@ class PlApiClient { getFilterStatus: async (statusId: string) => { const response = await this.request(`/api/v2/filters/statuses/${statusId}`); - return filterStatusSchema.parse(response.json); + return v.parse(filterStatusSchema, response.json); }, /** @@ -1761,8 +1761,8 @@ class PlApiClient { body: params, }); - if (response.json?.scheduled_at) return scheduledStatusSchema.parse(response.json); - return statusSchema.parse(response.json); + if (response.json?.scheduled_at) return v.parse(scheduledStatusSchema, response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1773,7 +1773,7 @@ class PlApiClient { getStatus: async (statusId: string, params?: GetStatusParams) => { const response = await this.request(`/api/v1/statuses/${statusId}`, { params }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1786,7 +1786,7 @@ class PlApiClient { getStatuses: async (statusIds: string[], params?: GetStatusesParams) => { const response = await this.request('/api/v1/statuses', { params: { ...params, id: statusIds } }); - return filteredArray(statusSchema).parse(response.json); + return v.parse(filteredArray(statusSchema), response.json); }, /** @@ -1797,7 +1797,7 @@ class PlApiClient { deleteStatus: async (statusId: string) => { const response = await this.request(`/api/v1/statuses/${statusId}`, { method: 'DELETE' }); - return statusSourceSchema.parse(response.json); + return v.parse(statusSourceSchema, response.json); }, /** @@ -1808,7 +1808,7 @@ class PlApiClient { getContext: async (statusId: string, params?: GetStatusContextParams) => { const response = await this.request(`/api/v1/statuses/${statusId}/context`, { params }); - return contextSchema.parse(response.json); + return v.parse(contextSchema, response.json); }, /** @@ -1825,7 +1825,7 @@ class PlApiClient { response = await this.request(`/api/v1/statuses/${statusId}/translate`, { method: 'POST', body: { lang } }); } - return translationSchema.parse(response.json); + return v.parse(translationSchema, response.json); }, /** @@ -1836,7 +1836,7 @@ class PlApiClient { translateStatuses: async (statusIds: Array, lang: string) => { const response = await this.request('/api/v1/pl/statuses/translate', { method: 'POST', body: { ids: statusIds, lang } }); - return filteredArray(translationSchema).parse(response.json); + return v.parse(filteredArray(translationSchema), response.json); }, /** @@ -1863,7 +1863,7 @@ class PlApiClient { favouriteStatus: async (statusId: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/favourite`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1874,7 +1874,7 @@ class PlApiClient { unfavouriteStatus: async (statusId: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/unfavourite`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1885,7 +1885,7 @@ class PlApiClient { reblogStatus: async (statusId: string, visibility?: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/reblog`, { method: 'POST', body: { visibility } }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1896,7 +1896,7 @@ class PlApiClient { unreblogStatus: async (statusId: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/unreblog`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1907,7 +1907,7 @@ class PlApiClient { bookmarkStatus: async (statusId: string, folderId?: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/bookmark`, { method: 'POST', body: { folder_id: folderId } }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1918,7 +1918,7 @@ class PlApiClient { unbookmarkStatus: async (statusId: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/unbookmark`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1929,7 +1929,7 @@ class PlApiClient { muteStatus: async (statusId: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/mute`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1940,7 +1940,7 @@ class PlApiClient { unmuteStatus: async (statusId: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/unmute`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1951,7 +1951,7 @@ class PlApiClient { pinStatus: async (statusId: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/pin`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1962,7 +1962,7 @@ class PlApiClient { unpinStatus: async (statusId: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/unpin`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1973,7 +1973,7 @@ class PlApiClient { editStatus: async (statusId: string, params: EditStatusParams) => { const response = await this.request(`/api/v1/statuses/${statusId}`, { method: 'PUT', body: params }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -1984,7 +1984,7 @@ class PlApiClient { getStatusHistory: async (statusId: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/history`); - return filteredArray(statusEditSchema).parse(response.json); + return v.parse(filteredArray(statusEditSchema), response.json); }, /** @@ -1995,7 +1995,7 @@ class PlApiClient { getStatusSource: async (statusId: string) => { const response = await this.request(`/api/v1/statuses/${statusId}/source`); - return statusSourceSchema.parse(response.json); + return v.parse(statusSourceSchema, response.json); }, /** @@ -2028,7 +2028,7 @@ class PlApiClient { }, []); } - return filteredArray(emojiReactionSchema).parse(response?.json || []); + return v.parse(filteredArray(emojiReactionSchema), response?.json || []); }, /** @@ -2048,7 +2048,7 @@ class PlApiClient { response = await this.request(`/api/v1/statuses/${statusId}/react/${encodeURIComponent(emoji)}`, { method: 'POST' }); } - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -2067,7 +2067,7 @@ class PlApiClient { response = await this.request(`/api/v1/statuses/${statusId}/unreact/${encodeURIComponent(emoji)}`, { method: 'POST' }); } - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -2087,7 +2087,7 @@ class PlApiClient { getDislikedBy: async (statusId: string) => { const response = await this.request(`/api/friendica/statuses/${statusId}/disliked_by`); - return filteredArray(accountSchema).parse(response.json); + return v.parse(filteredArray(accountSchema), response.json); }, /** @@ -2097,7 +2097,7 @@ class PlApiClient { dislikeStatus: async (statusId: string) => { const response = await this.request(`/api/friendica/statuses/${statusId}/dislike`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -2107,7 +2107,7 @@ class PlApiClient { undislikeStatus: async (statusId: string) => { const response = await this.request(`/api/friendica/statuses/${statusId}/undislike`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, }; @@ -2123,7 +2123,7 @@ class PlApiClient { { ...meta, method: 'POST', body: params, contentType: '' }, ); - return mediaAttachmentSchema.parse(response.json); + return v.parse(mediaAttachmentSchema, response.json); }, /** @@ -2134,7 +2134,7 @@ class PlApiClient { getMedia: async (attachmentId: string) => { const response = await this.request(`/api/v1/media/${attachmentId}`); - return mediaAttachmentSchema.parse(response.json); + return v.parse(mediaAttachmentSchema, response.json); }, /** @@ -2148,7 +2148,7 @@ class PlApiClient { body: params, contentType: params.thumbnail ? '' : undefined, }); - return mediaAttachmentSchema.parse(response.json); + return v.parse(mediaAttachmentSchema, response.json); }, }; @@ -2161,7 +2161,7 @@ class PlApiClient { getPoll: async (pollId: string) => { const response = await this.request(`/api/v1/polls/${pollId}`); - return pollSchema.parse(response.json); + return v.parse(pollSchema, response.json); }, /** @@ -2172,7 +2172,7 @@ class PlApiClient { vote: async (pollId: string, choices: number[]) => { const response = await this.request(`/api/v1/polls/${pollId}/votes`, { method: 'POST', body: { choices } }); - return pollSchema.parse(response.json); + return v.parse(pollSchema, response.json); }, }; @@ -2191,7 +2191,7 @@ class PlApiClient { getScheduledStatus: async (scheduledStatusId: string) => { const response = await this.request(`/api/v1/scheduled_statuses/${scheduledStatusId}`); - return scheduledStatusSchema.parse(response.json); + return v.parse(scheduledStatusSchema, response.json); }, /** @@ -2204,7 +2204,7 @@ class PlApiClient { body: { scheduled_at }, }); - return scheduledStatusSchema.parse(response.json); + return v.parse(scheduledStatusSchema, response.json); }, /** @@ -2284,7 +2284,7 @@ class PlApiClient { markConversationRead: async (conversationId: string) => { const response = await this.request(`/api/v1/conversations/${conversationId}/read`, { method: 'POST' }); - return conversationSchema.parse(response.json); + return v.parse(conversationSchema, response.json); }, /** @@ -2295,7 +2295,7 @@ class PlApiClient { getMarkers: async (timelines?: string[]) => { const response = await this.request('/api/v1/markers', { params: { timeline: timelines } }); - return markersSchema.parse(response.json); + return v.parse(markersSchema, response.json); }, /** @@ -2306,7 +2306,7 @@ class PlApiClient { saveMarkers: async (params: SaveMarkersParams) => { const response = await this.request('/api/v1/markers', { method: 'POST', body: params }); - return markersSchema.parse(response.json); + return v.parse(markersSchema, response.json); }, /** @@ -2331,7 +2331,7 @@ class PlApiClient { getLists: async () => { const response = await this.request('/api/v1/lists'); - return filteredArray(listSchema).parse(response.json); + return v.parse(filteredArray(listSchema), response.json); }, /** @@ -2342,7 +2342,7 @@ class PlApiClient { getList: async (listId: string) => { const response = await this.request(`/api/v1/lists/${listId}`); - return listSchema.parse(response.json); + return v.parse(listSchema, response.json); }, /** @@ -2353,7 +2353,7 @@ class PlApiClient { createList: async (params: CreateListParams) => { const response = await this.request('/api/v1/lists', { method: 'POST', body: params }); - return listSchema.parse(response.json); + return v.parse(listSchema, response.json); }, /** @@ -2364,7 +2364,7 @@ class PlApiClient { updateList: async (listId: string, params: UpdateListParams) => { const response = await this.request(`/api/v1/lists/${listId}`, { method: 'PUT', body: params }); - return listSchema.parse(response.json); + return v.parse(listSchema, response.json); }, /** @@ -2420,7 +2420,7 @@ class PlApiClient { health: async () => { const response = await this.request('/api/v1/streaming/health'); - return z.literal('OK').parse(response.json); + return v.parse(v.literal('OK'), response.json); }, /** @@ -2499,7 +2499,7 @@ class PlApiClient { getNotification: async (notificationId: string) => { const response = await this.request(`/api/v1/notifications/${notificationId}`); - return notificationSchema.parse(response.json); + return v.parse(notificationSchema, response.json); }, /** @@ -2532,7 +2532,7 @@ class PlApiClient { getNotificationPolicy: async () => { const response = await this.request('/api/v1/notifications/policy'); - return notificationPolicySchema.parse(response.json); + return v.parse(notificationPolicySchema, response.json); }, /** @@ -2543,7 +2543,7 @@ class PlApiClient { updateNotificationPolicy: async (params: UpdateNotificationPolicyRequest) => { const response = await this.request('/api/v1/notifications/policy', { method: 'POST', body: params }); - return notificationPolicySchema.parse(response.json); + return v.parse(notificationPolicySchema, response.json); }, /** @@ -2562,7 +2562,7 @@ class PlApiClient { getNotificationRequest: async (notificationRequestId: string) => { const response = await this.request(`/api/v1/notifications/requests/${notificationRequestId}`); - return notificationRequestSchema.parse(response.json); + return v.parse(notificationRequestSchema, response.json); }, /** @@ -2612,7 +2612,7 @@ class PlApiClient { createSubscription: async (params: CreatePushNotificationsSubscriptionParams) => { const response = await this.request('/api/v1/push/subscription', { method: 'POST', body: params }); - return webPushSubscriptionSchema.parse(response.json); + return v.parse(webPushSubscriptionSchema, response.json); }, /** @@ -2623,7 +2623,7 @@ class PlApiClient { getSubscription: async () => { const response = await this.request('/api/v1/push/subscription'); - return webPushSubscriptionSchema.parse(response.json); + return v.parse(webPushSubscriptionSchema, response.json); }, /** @@ -2634,7 +2634,7 @@ class PlApiClient { updateSubscription: async (params: UpdatePushNotificationsSubscriptionParams) => { const response = await this.request('/api/v1/push/subscription', { method: 'PUT', body: params }); - return webPushSubscriptionSchema.parse(response.json); + return v.parse(webPushSubscriptionSchema, response.json); }, /** @@ -2657,7 +2657,7 @@ class PlApiClient { search: async (q: string, params?: SearchParams, meta?: RequestMeta) => { const response = await this.request('/api/v2/search', { ...meta, params: { ...params, q } }); - return searchSchema.parse(response.json); + return v.parse(searchSchema, response.json); }, /** @@ -2669,7 +2669,7 @@ class PlApiClient { searchLocation: async (q: string, meta?: RequestMeta) => { const response = await this.request('/api/v1/pleroma/search/location', { ...meta, params: { q } }); - return filteredArray(locationSchema).parse(response.json); + return v.parse(filteredArray(locationSchema), response.json); }, }; @@ -2687,7 +2687,7 @@ class PlApiClient { response = await this.request('/api/v1/instance'); } - const instance = instanceSchema.readonly().parse(response.json); + const instance = v.parse(instanceSchema.readonly(), response.json); this.#setInstance(instance); return instance; @@ -2701,7 +2701,7 @@ class PlApiClient { getInstancePeers: async () => { const response = await this.request('/api/v1/instance/peers'); - return z.array(z.string()).parse(response.json); + return v.parse(v.array(v.string()), response.json); }, /** @@ -2712,12 +2712,12 @@ class PlApiClient { getInstanceActivity: async () => { const response = await this.request('/api/v1/instance/activity'); - return z.array(z.object({ - week: z.string(), + return v.parse(v.array(v.object({ + week: v.string(), statuses: z.coerce.string(), logins: z.coerce.string(), registrations: z.coerce.string(), - })).parse(response.json); + })), response.json); }, /** @@ -2728,7 +2728,7 @@ class PlApiClient { getInstanceRules: async () => { const response = await this.request('/api/v1/instance/rules'); - return filteredArray(ruleSchema).parse(response.json); + return v.parse(filteredArray(ruleSchema), response.json); }, /** @@ -2739,7 +2739,7 @@ class PlApiClient { getInstanceDomainBlocks: async () => { const response = await this.request('/api/v1/instance/rules'); - return filteredArray(domainBlockSchema).parse(response.json); + return v.parse(filteredArray(domainBlockSchema), response.json); }, /** @@ -2750,7 +2750,7 @@ class PlApiClient { getInstanceExtendedDescription: async () => { const response = await this.request('/api/v1/instance/extended_description'); - return extendedDescriptionSchema.parse(response.json); + return v.parse(extendedDescriptionSchema, response.json); }, /** @@ -2773,7 +2773,7 @@ class PlApiClient { const response = await this.request('/api/v1/instance/translation_languages'); - return z.record(z.array(z.string())).parse(response.json); + return v.parse(z.record(v.array(v.string())), response.json); }, /** @@ -2784,7 +2784,7 @@ class PlApiClient { profileDirectory: async (params?: ProfileDirectoryParams) => { const response = await this.request('/api/v1/directory', { params }); - return filteredArray(accountSchema).parse(response.json); + return v.parse(filteredArray(accountSchema), response.json); }, /** @@ -2795,7 +2795,7 @@ class PlApiClient { getCustomEmojis: async () => { const response = await this.request('/api/v1/custom_emojis'); - return filteredArray(customEmojiSchema).parse(response.json); + return v.parse(filteredArray(customEmojiSchema), response.json); }, /** @@ -2806,7 +2806,7 @@ class PlApiClient { getFrontendConfigurations: async () => { const response = await this.request('/api/pleroma/frontend_configurations'); - return z.record(z.record(z.any())).catch({}).parse(response.json); + return v.parse(z.record(z.record(v.any())).catch({}), response.json); }, }; @@ -2819,7 +2819,7 @@ class PlApiClient { getTrendingTags: async (params?: GetTrendingTags) => { const response = await this.request('/api/v1/trends/tags', { params }); - return filteredArray(tagSchema).parse(response.json); + return v.parse(filteredArray(tagSchema), response.json); }, /** @@ -2830,7 +2830,7 @@ class PlApiClient { getTrendingStatuses: async (params?: GetTrendingStatuses) => { const response = await this.request('/api/v1/trends/statuses', { params }); - return filteredArray(statusSchema).parse(response.json); + return v.parse(filteredArray(statusSchema), response.json); }, /** @@ -2841,7 +2841,7 @@ class PlApiClient { getTrendingLinks: async (params?: GetTrendingLinks) => { const response = await this.request('/api/v1/trends/links', { params }); - return filteredArray(trendsLinkSchema).parse(response.json); + return v.parse(filteredArray(trendsLinkSchema), response.json); }, }; @@ -2854,7 +2854,7 @@ class PlApiClient { getAnnouncements: async () => { const response = await this.request('/api/v1/announcements'); - return filteredArray(announcementSchema).parse(response.json); + return v.parse(filteredArray(announcementSchema), response.json); }, /** @@ -2935,7 +2935,7 @@ class PlApiClient { response = await this.request(`/api/v1/admin/users/${accountId}`); } - return adminAccountSchema.parse(response.json); + return v.parse(adminAccountSchema, response.json); }, /** @@ -2955,7 +2955,7 @@ class PlApiClient { response.json = response.json?.users?.[0]; } - return adminAccountSchema.parse(response.json); + return v.parse(adminAccountSchema, response.json); }, /** @@ -2977,7 +2977,7 @@ class PlApiClient { } }); } - return adminAccountSchema.safeParse(response.json).data || {}; + return v.safeParse(adminAccountSchema, response.json).output || {}; }, /** @@ -2988,7 +2988,7 @@ class PlApiClient { deleteAccount: async (accountId: string) => { const response = await this.request(`/api/v1/admin/accounts/${accountId}`, { method: 'DELETE' }); - return adminAccountSchema.parse(response.json); + return v.parse(adminAccountSchema, response.json); }, /** @@ -3036,7 +3036,7 @@ class PlApiClient { response.json = response.json?.users?.[0]; } - return adminAccountSchema.parse(response.json); + return v.parse(adminAccountSchema, response.json); }, /** @@ -3047,7 +3047,7 @@ class PlApiClient { unsilenceAccount: async (accountId: string) => { const response = await this.request(`/api/v1/admin/accounts/${accountId}/unsilence`, { method: 'POST' }); - return adminAccountSchema.parse(response.json); + return v.parse(adminAccountSchema, response.json); }, /** @@ -3067,7 +3067,7 @@ class PlApiClient { response.json = response.json?.users?.[0]; } - return adminAccountSchema.parse(response.json); + return v.parse(adminAccountSchema, response.json); }, /** @@ -3078,7 +3078,7 @@ class PlApiClient { unsensitiveAccount: async (accountId: string) => { const response = await this.request(`/api/v1/admin/accounts/${accountId}/unsensitive`, { method: 'POST' }); - return adminAccountSchema.parse(response.json); + return v.parse(adminAccountSchema, response.json); }, /** @@ -3218,7 +3218,7 @@ class PlApiClient { getDomainBlock: async (domainBlockId: string) => { const response = await this.request(`/api/v1/admin/domain_blocks/${domainBlockId}`); - return adminDomainBlockSchema.parse(response.json); + return v.parse(adminDomainBlockSchema, response.json); }, /** @@ -3232,7 +3232,7 @@ class PlApiClient { body: { ...params, domain }, }); - return adminDomainBlockSchema.parse(response.json); + return v.parse(adminDomainBlockSchema, response.json); }, /** @@ -3246,7 +3246,7 @@ class PlApiClient { body: params, }); - return adminDomainBlockSchema.parse(response.json); + return v.parse(adminDomainBlockSchema, response.json); }, /** @@ -3293,7 +3293,7 @@ class PlApiClient { response = await this.request(`/api/v1/pleroma/admin/reports/${reportId}`); } - return adminReportSchema.parse(response.json); + return v.parse(adminReportSchema, response.json); }, /** @@ -3304,7 +3304,7 @@ class PlApiClient { updateReport: async (reportId: string, params: AdminUpdateReportParams) => { const response = await this.request(`/api/v1/admin/reports/${reportId}`, { method: 'PUT', body: params }); - return adminReportSchema.parse(response.json); + return v.parse(adminReportSchema, response.json); }, /** @@ -3315,7 +3315,7 @@ class PlApiClient { assignReportToSelf: async (reportId: string) => { const response = await this.request(`/api/v1/admin/reports/${reportId}/assign_to_self`, { method: 'POST' }); - return adminReportSchema.parse(response.json); + return v.parse(adminReportSchema, response.json); }, /** @@ -3326,7 +3326,7 @@ class PlApiClient { unassignReport: async (reportId: string) => { const response = await this.request(`/api/v1/admin/reports/${reportId}/unassign`, { method: 'POST' }); - return adminReportSchema.parse(response.json); + return v.parse(adminReportSchema, response.json); }, /** @@ -3345,7 +3345,7 @@ class PlApiClient { }); } - return adminReportSchema.parse(response.json); + return v.parse(adminReportSchema, response.json); }, /** @@ -3364,7 +3364,7 @@ class PlApiClient { }); } - return adminReportSchema.parse(response.json); + return v.parse(adminReportSchema, response.json); }, }, @@ -3394,7 +3394,7 @@ class PlApiClient { getStatus: async (statusId: string) => { const response = await this.request(`/api/v1/pleroma/admin/statuses/${statusId}`); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -3406,7 +3406,7 @@ class PlApiClient { updateStatus: async (statusId: string, params: AdminUpdateStatusParams) => { const response = await this.request(`/api/v1/pleroma/admin/statuses/${statusId}`, { method: 'PUT', body: params }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -3431,7 +3431,7 @@ class PlApiClient { getTrendingLinks: async () => { const response = await this.request('/api/v1/admin/trends/links'); - return filteredArray(trendsLinkSchema).parse(response.json); + return v.parse(filteredArray(trendsLinkSchema), response.json); }, /** @@ -3442,7 +3442,7 @@ class PlApiClient { getTrendingStatuses: async () => { const response = await this.request('/api/v1/admin/trends/statuses'); - return filteredArray(statusSchema).parse(response.json); + return v.parse(filteredArray(statusSchema), response.json); }, /** @@ -3453,7 +3453,7 @@ class PlApiClient { getTrendingTags: async () => { const response = await this.request('/api/v1/admin/trends/links'); - return filteredArray(adminTagSchema).parse(response.json); + return v.parse(filteredArray(adminTagSchema), response.json); }, }, @@ -3473,7 +3473,7 @@ class PlApiClient { getCanonicalEmailBlock: async (canonicalEmailBlockId: string) => { const response = await this.request(`/api/v1/admin/canonical_email_blocks/${canonicalEmailBlockId}`); - return adminCanonicalEmailBlockSchema.parse(response.json); + return v.parse(adminCanonicalEmailBlockSchema, response.json); }, /** @@ -3484,7 +3484,7 @@ class PlApiClient { testCanonicalEmailBlock: async (email: string) => { const response = await this.request('/api/v1/admin/canonical_email_blocks/test', { method: 'POST', body: { email } }); - return filteredArray(adminCanonicalEmailBlockSchema).parse(response.json); + return v.parse(filteredArray(adminCanonicalEmailBlockSchema), response.json); }, /** @@ -3494,7 +3494,7 @@ class PlApiClient { createCanonicalEmailBlock: async (email: string, canonical_email_hash?: string) => { const response = await this.request('/api/v1/admin/canonical_email_blocks', { method: 'POST', body: { email, canonical_email_hash } }); - return filteredArray(adminCanonicalEmailBlockSchema).parse(response.json); + return v.parse(filteredArray(adminCanonicalEmailBlockSchema), response.json); }, /** @@ -3518,7 +3518,7 @@ class PlApiClient { getDimensions: async (keys: AdminDimensionKey[], params?: AdminGetDimensionsParams) => { const response = await this.request('/api/v1/admin/dimensions', { params: { ...params, keys } }); - return filteredArray(adminDimensionSchema).parse(response.json); + return v.parse(filteredArray(adminDimensionSchema), response.json); }, }, @@ -3540,7 +3540,7 @@ class PlApiClient { getDomainAllow: async (domainAllowId: string) => { const response = await this.request(`/api/v1/admin/domain_allows/${domainAllowId}`); - return adminDomainAllowSchema.parse(response.json); + return v.parse(adminDomainAllowSchema, response.json); }, /** @@ -3551,7 +3551,7 @@ class PlApiClient { createDomainAllow: async (domain: string) => { const response = await this.request('/api/v1/admin/domain_allows', { method: 'POST', body: { domain } }); - return adminDomainAllowSchema.parse(response.json); + return v.parse(adminDomainAllowSchema, response.json); }, /** @@ -3584,7 +3584,7 @@ class PlApiClient { getEmailDomainBlock: async (emailDomainBlockId: string) => { const response = await this.request(`/api/v1/admin/email_domain_blocks/${emailDomainBlockId}`); - return adminEmailDomainBlockSchema.parse(response.json); + return v.parse(adminEmailDomainBlockSchema, response.json); }, /** @@ -3595,7 +3595,7 @@ class PlApiClient { createEmailDomainBlock: async (domain: string) => { const response = await this.request('/api/v1/admin/email_domain_blocks', { method: 'POST', body: { domain } }); - return adminEmailDomainBlockSchema.parse(response.json); + return v.parse(adminEmailDomainBlockSchema, response.json); }, /** @@ -3628,7 +3628,7 @@ class PlApiClient { getIpBlock: async (ipBlockId: string) => { const response = await this.request(`/api/v1/admin/ip_blocks/${ipBlockId}`); - return adminIpBlockSchema.parse(response.json); + return v.parse(adminIpBlockSchema, response.json); }, /** @@ -3639,7 +3639,7 @@ class PlApiClient { createIpBlock: async (params: AdminCreateIpBlockParams) => { const response = await this.request('/api/v1/admin/ip_blocks', { method: 'POST', body: params }); - return adminIpBlockSchema.parse(response.json); + return v.parse(adminIpBlockSchema, response.json); }, /** @@ -3650,7 +3650,7 @@ class PlApiClient { updateIpBlock: async (ipBlockId: string, params: AdminCreateIpBlockParams) => { const response = await this.request(`/api/v1/admin/ip_blocks/${ipBlockId}`, { method: 'POST', body: params }); - return adminIpBlockSchema.parse(response.json); + return v.parse(adminIpBlockSchema, response.json); }, /** @@ -3675,7 +3675,7 @@ class PlApiClient { getMeasures: async (keys: AdminMeasureKey[], start_at: string, end_at: string, params?: AdminGetMeasuresParams) => { const response = await this.request('/api/v1/admin/measures', { params: { ...params, keys, start_at, end_at } }); - return filteredArray(adminMeasureSchema).parse(response.json); + return v.parse(filteredArray(adminMeasureSchema), response.json); }, }, @@ -3690,7 +3690,7 @@ class PlApiClient { getRetention: async (start_at: string, end_at: string, frequency: 'day' | 'month') => { const response = await this.request('/api/v1/admin/retention', { params: { start_at, end_at, frequency } }); - return adminCohortSchema.parse(response.json); + return v.parse(adminCohortSchema, response.json); }, }, @@ -3704,7 +3704,7 @@ class PlApiClient { getAnnouncements: async (params?: AdminGetAnnouncementsParams): Promise> => { const response = await this.request('/api/v1/pleroma/admin/announcements', { params }); - const items = filteredArray(adminAnnouncementSchema).parse(response.json); + const items = v.parse(filteredArray(adminAnnouncementSchema), response.json); return { previous: null, @@ -3723,7 +3723,7 @@ class PlApiClient { getAnnouncement: async (announcementId: string) => { const response = await this.request(`/api/v1/pleroma/admin/announcements/${announcementId}`); - return adminAnnouncementSchema.parse(response.json); + return v.parse(adminAnnouncementSchema, response.json); }, /** @@ -3735,7 +3735,7 @@ class PlApiClient { createAnnouncement: async (params: AdminCreateAnnouncementParams) => { const response = await this.request('/api/v1/pleroma/admin/announcements', { method: 'POST', body: params }); - return adminAnnouncementSchema.parse(response.json); + return v.parse(adminAnnouncementSchema, response.json); }, /** @@ -3747,7 +3747,7 @@ class PlApiClient { updateAnnouncement: async (announcementId: string, params: AdminUpdateAnnouncementParams) => { const response = await this.request(`/api/v1/pleroma/admin/announcements/${announcementId}`, { method: 'PATCH', body: params }); - return adminAnnouncementSchema.parse(response.json); + return v.parse(adminAnnouncementSchema, response.json); }, /** @@ -3772,7 +3772,7 @@ class PlApiClient { getDomains: async () => { const response = await this.request('/api/v1/pleroma/admin/domains'); - return filteredArray(adminDomainSchema).parse(response.json); + return v.parse(filteredArray(adminDomainSchema), response.json); }, /** @@ -3783,7 +3783,7 @@ class PlApiClient { createDomain: async (params: AdminCreateDomainParams) => { const response = await this.request('/api/v1/pleroma/admin/domains', { method: 'POST', body: params }); - return adminDomainSchema.parse(response.json); + return v.parse(adminDomainSchema, response.json); }, /** @@ -3794,7 +3794,7 @@ class PlApiClient { updateDomain: async (domainId: string, isPublic: boolean) => { const response = await this.request(`/api/v1/pleroma/admin/domains/${domainId}`, { method: 'PATCH', body: { public: isPublic } }); - return adminDomainSchema.parse(response.json); + return v.parse(adminDomainSchema, response.json); }, /** @@ -3819,7 +3819,7 @@ class PlApiClient { getModerationLog: async ({ limit, ...params }: AdminGetModerationLogParams = {}): Promise> => { const response = await this.request('/api/v1/pleroma/admin/moderation_log', { params: { page_size: limit, ...params } }); - const items = filteredArray(adminModerationLogEntrySchema).parse(response.json.items); + const items = v.parse(filteredArray(adminModerationLogEntrySchema), response.json.items); return { previous: (params.page && params.page > 1) ? () => this.admin.moderationLog.getModerationLog({ ...params, page: params.page! - 1 }) : null, @@ -3840,7 +3840,7 @@ class PlApiClient { getRelays: async () => { const response = await this.request('/api/v1/pleroma/admin/relay'); - return filteredArray(adminRelaySchema).parse(response.json); + return v.parse(filteredArray(adminRelaySchema), response.json); }, /** @@ -3852,7 +3852,7 @@ class PlApiClient { followRelay: async (relayUrl: string) => { const response = await this.request('/api/v1/pleroma/admin/relay', { method: 'POST', body: { relay_url: relayUrl } }); - return adminRelaySchema.parse(response.json); + return v.parse(adminRelaySchema, response.json); }, /** @@ -3864,7 +3864,7 @@ class PlApiClient { unfollowRelay: async (relayUrl: string, force = false) => { const response = await this.request('/api/v1/pleroma/admin/relay', { method: 'DELETE', body: { relay_url: relayUrl, force } }); - return adminRelaySchema.parse(response.json); + return v.parse(adminRelaySchema, response.json); }, }, @@ -3878,7 +3878,7 @@ class PlApiClient { getRules: async () => { const response = await this.request('/api/v1/pleroma/admin/rules'); - return filteredArray(adminRuleSchema).parse(response.json); + return v.parse(filteredArray(adminRuleSchema), response.json); }, /** @@ -3890,7 +3890,7 @@ class PlApiClient { createRule: async (params: AdminCreateRuleParams) => { const response = await this.request('/api/v1/pleroma/admin/rules', { method: 'POST', body: params }); - return adminRuleSchema.parse(response.json); + return v.parse(adminRuleSchema, response.json); }, /** @@ -3902,7 +3902,7 @@ class PlApiClient { updateRule: async (ruleId: string, params: AdminUpdateRuleParams) => { const response = await this.request(`/api/v1/pleroma/admin/rules/${ruleId}`, { method: 'PATCH', body: params }); - return adminRuleSchema.parse(response.json); + return v.parse(adminRuleSchema, response.json); }, /** @@ -3922,13 +3922,13 @@ class PlApiClient { getPleromaConfig: async () => { const response = await this.request('/api/v1/pleroma/admin/config'); - return pleromaConfigSchema.parse(response.json); + return v.parse(pleromaConfigSchema, response.json); }, updatePleromaConfig: async (params: PleromaConfig['configs']) => { const response = await this.request('/api/v1/pleroma/admin/config', { method: 'POST', body: { configs: params } }); - return pleromaConfigSchema.parse(response.json); + return v.parse(pleromaConfigSchema, response.json); }, }, }; @@ -3941,18 +3941,18 @@ class PlApiClient { getOembed: async (url: string, maxwidth?: number, maxheight?: number) => { const response = await this.request('/api/oembed', { params: { url, maxwidth, maxheight } }); - return z.object({ - type: z.string().catch('rich'), - version: z.string().catch(''), - author_name: z.string().catch(''), - author_url: z.string().catch('').catch(''), - provider_name: z.string().catch(''), - provider_url: z.string().catch(''), - cache_age: z.number(), - html: z.string(), - width: z.number().nullable().catch(null), - height: z.number().nullable().catch(null), - }).parse(response.json); + return v.parse(v.object({ + type: v.fallback(v.string(), 'rich'), + version: v.fallback(v.string(), ''), + author_name: v.fallback(v.string(), ''), + author_url: v.fallback(v.string(), ''), + provider_name: v.fallback(v.string(), ''), + provider_url: v.fallback(v.string(), ''), + cache_age: v.number(), + html: v.string(), + width: v.fallback(v.nullable(v.number()), null), + height: v.fallback(v.nullable(v.number()), null), + }), response.json); }, }; @@ -3965,7 +3965,7 @@ class PlApiClient { createChat: async (accountId: string) => { const response = await this.request(`/api/v1/pleroma/chats/by-account-id/${accountId}`, { method: 'POST' }); - return chatSchema.parse(response.json); + return v.parse(chatSchema, response.json); }, /** @@ -3974,7 +3974,7 @@ class PlApiClient { getChat: async (chatId: string) => { const response = await this.request(`/api/v1/pleroma/chats/${chatId}`); - return chatSchema.parse(response.json); + return v.parse(chatSchema, response.json); }, /** @@ -3985,7 +3985,7 @@ class PlApiClient { markChatAsRead: async (chatId: string, last_read_id: string) => { const response = await this.request(`/api/v1/pleroma/chats/${chatId}/read`, { method: 'POST', body: { last_read_id } }); - return chatSchema.parse(response.json); + return v.parse(chatSchema, response.json); }, /** @@ -3996,7 +3996,7 @@ class PlApiClient { markChatMessageAsRead: async (chatId: string, chatMessageId: string) => { const response = await this.request(`/api/v1/pleroma/chats/${chatId}/messages/${chatMessageId}/read`, { method: 'POST' }); - return chatSchema.parse(response.json); + return v.parse(chatSchema, response.json); }, /** @@ -4022,7 +4022,7 @@ class PlApiClient { createChatMessage: async (chatId: string, params: CreateChatMessageParams) => { const response = await this.request(`/api/v1/pleroma/chats/${chatId}/messages`, { method: 'POST', body: params }); - return chatMessageSchema.parse(response.json); + return v.parse(chatMessageSchema, response.json); }, /** @@ -4033,7 +4033,7 @@ class PlApiClient { deleteChatMessage: async (chatId: string, messageId: string) => { const response = await this.request(`/api/v1/pleroma/chats/${chatId}/messages/${messageId}`, { method: 'DELETE' }); - return chatMessageSchema.parse(response.json); + return v.parse(chatMessageSchema, response.json); }, /** @@ -4044,7 +4044,7 @@ class PlApiClient { deleteChat: async (chatId: string) => { const response = await this.request(`/api/v1/pleroma/chats/${chatId}`, { method: 'DELETE' }); - return chatSchema.parse(response.json); + return v.parse(chatSchema, response.json); }, }; @@ -4056,7 +4056,7 @@ class PlApiClient { createEvent: async (params: CreateEventParams) => { const response = await this.request('/api/v1/pleroma/events', { method: 'POST', body: params }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -4066,7 +4066,7 @@ class PlApiClient { editEvent: async (statusId: string, params: EditEventParams) => { const response = await this.request(`/api/v1/pleroma/events/${statusId}`, { method: 'PUT', body: params }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -4088,9 +4088,9 @@ class PlApiClient { * @see {@link https://github.com/mkljczk/pl/blob/fork/docs/development/API/pleroma_api.md#apiv1pleromaeventsidparticipation_requests} */ getEventParticipationRequests: async (statusId: string, params?: GetEventParticipationRequestsParams) => - this.#paginatedGet(`/api/v1/pleroma/events/${statusId}/participation_requests`, { params }, z.object({ + this.#paginatedGet(`/api/v1/pleroma/events/${statusId}/participation_requests`, { params }, v.object({ account: accountSchema, - participation_message: z.string().catch(''), + participation_message: v.fallback(v.string(), ''), })), /** @@ -4100,7 +4100,7 @@ class PlApiClient { acceptEventParticipationRequest: async (statusId: string, accountId: string) => { const response = await this.request(`/api/v1/pleroma/events/${statusId}/participation_requests/${accountId}/authorize`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -4110,7 +4110,7 @@ class PlApiClient { rejectEventParticipationRequest: async (statusId: string, accountId: string) => { const response = await this.request(`/api/v1/pleroma/events/${statusId}/participation_requests/${accountId}/reject`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -4120,7 +4120,7 @@ class PlApiClient { joinEvent: async (statusId: string, participation_message?: string) => { const response = await this.request(`/api/v1/pleroma/events/${statusId}/join`, { method: 'POST', body: { participation_message } }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -4130,7 +4130,7 @@ class PlApiClient { leaveEvent: async (statusId: string) => { const response = await this.request(`/api/v1/pleroma/events/${statusId}/leave`, { method: 'POST' }); - return statusSchema.parse(response.json); + return v.parse(statusSchema, response.json); }, /** @@ -4161,7 +4161,7 @@ class PlApiClient { getInteractionRequest: async (interactionRequestId: string) => { const response = await this.request(`/api/v1/interaction_requests/${interactionRequestId}`); - return interactionRequestSchema.parse(response.json); + return v.parse(interactionRequestSchema, response.json); }, /** @@ -4172,7 +4172,7 @@ class PlApiClient { authorizeInteractionRequest: async (interactionRequestId: string) => { const response = await this.request(`/api/v1/interaction_requests/${interactionRequestId}/authorize`, { method: 'POST' }); - return interactionRequestSchema.parse(response.json); + return v.parse(interactionRequestSchema, response.json); }, /** @@ -4183,7 +4183,7 @@ class PlApiClient { rejectInteractionRequest: async (interactionRequestId: string) => { const response = await this.request(`/api/v1/interaction_requests/${interactionRequestId}/authorize`, { method: 'POST' }); - return interactionRequestSchema.parse(response.json); + return v.parse(interactionRequestSchema, response.json); }, }; @@ -4196,35 +4196,35 @@ class PlApiClient { getGroups: async (params?: AdminGetGroupsParams) => { const response = await this.request('/api/v1/admin/groups', { params }); - return filteredArray(groupSchema).parse(response.json); + return v.parse(filteredArray(groupSchema), response.json); }, /** return basic group information */ getGroup: async (groupId: string) => { const response = await this.request(`/api/v1/admin/groups/${groupId}`); - return groupSchema.parse(response.json); + return v.parse(groupSchema, response.json); }, /** suspends a group */ suspendGroup: async (groupId: string) => { const response = await this.request(`/api/v1/admin/groups/${groupId}/suspend`, { method: 'POST' }); - return groupSchema.parse(response.json); + return v.parse(groupSchema, response.json); }, /** lift a suspension */ unsuspendGroup: async (groupId: string) => { const response = await this.request(`/api/v1/admin/groups/${groupId}/unsuspend`, { method: 'POST' }); - return groupSchema.parse(response.json); + return v.parse(groupSchema, response.json); }, /** deletes an already-suspended group */ deleteGroup: async (groupId: string) => { const response = await this.request(`/api/v1/admin/groups/${groupId}`, { method: 'DELETE' }); - return groupSchema.parse(response.json); + return v.parse(groupSchema, response.json); }, }, }, @@ -4235,7 +4235,7 @@ class PlApiClient { getGroups: async () => { const response = await this.request('/api/v1/groups'); - return filteredArray(groupSchema).parse(response.json); + return v.parse(filteredArray(groupSchema), response.json); }, /** create a group with the given attributes (`display_name`, `note`, `avatar` and `header`). Sets the user who made the request as group administrator */ @@ -4246,14 +4246,14 @@ class PlApiClient { contentType: params.avatar || params.header ? '' : undefined, }); - return groupSchema.parse(response.json); + return v.parse(groupSchema, response.json); }, /** returns the `Group` entity describing a given group */ getGroup: async (groupId: string) => { const response = await this.request(`/api/v1/groups/${groupId}`); - return groupSchema.parse(response.json); + return v.parse(groupSchema, response.json); }, /** update group attributes (`display_name`, `note`, `avatar` and `header`) */ @@ -4264,7 +4264,7 @@ class PlApiClient { contentType: params.avatar || params.header ? '' : undefined, }); - return groupSchema.parse(response.json); + return v.parse(groupSchema, response.json); }, /** irreversibly deletes the group */ @@ -4325,14 +4325,14 @@ class PlApiClient { joinGroup: async (groupId: string) => { const response = await this.request(`/api/v1/groups/${groupId}/join`, { method: 'POST' }); - return groupRelationshipSchema.parse(response.json); + return v.parse(groupRelationshipSchema, response.json); }, /** leaves a given group */ leaveGroup: async (groupId: string) => { const response = await this.request(`/api/v1/groups/${groupId}/leave`, { method: 'POST' }); - return groupRelationshipSchema.parse(response.json); + return v.parse(groupRelationshipSchema, response.json); }, /** kick one or more group members */ @@ -4346,20 +4346,20 @@ class PlApiClient { promoteGroupUsers: async (groupId: string, accountIds: string[], role: GroupRole) => { const response = await this.request(`/api/v1/groups/${groupId}/promote`, { method: 'POST', params: { account_ids: accountIds, role } }); - return filteredArray(groupMemberSchema).parse(response.json); + return v.parse(filteredArray(groupMemberSchema), response.json); }, /** demote one or more accounts to role `new_role`. Returns an error unless every of the target account has a strictly lower role than the user (you cannot demote someone with the same role as you), or if any target account already has a role lower than `new_role`. Valid roles are `admin`, `moderator` and `user`. */ demoteGroupUsers: async (groupId: string, accountIds: string[], role: GroupRole) => { const response = await this.request(`/api/v1/groups/${groupId}/demote`, { method: 'POST', params: { account_ids: accountIds, role } }); - return filteredArray(groupMemberSchema).parse(response.json); + return v.parse(filteredArray(groupMemberSchema), response.json); }, getGroupRelationships: async (groupIds: string[]) => { const response = await this.request('/api/v1/groups/relationships', { params: { id: groupIds } }); - return filteredArray(groupRelationshipSchema).parse(response.json); + return v.parse(filteredArray(groupRelationshipSchema), response.json); }, }, }; diff --git a/packages/pl-api/lib/entities/account-warning.ts b/packages/pl-api/lib/entities/account-warning.ts index 3a00a76c8..1065dbc38 100644 --- a/packages/pl-api/lib/entities/account-warning.ts +++ b/packages/pl-api/lib/entities/account-warning.ts @@ -1,25 +1,25 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; import { dateSchema } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/Appeal/} */ -const appealSchema = z.object({ - text: z.string(), - state: z.enum(['approved', 'rejected', 'pending']), +const appealSchema = v.object({ + text: v.string(), + state: v.picklist(['approved', 'rejected', 'pending']), }); /** @see {@link https://docs.joinmastodon.org/entities/AccountWarning/} */ -const accountWarningSchema = z.object({ - id: z.string(), - action: z.enum(['none', 'disable', 'mark_statuses_as_sensitive', 'delete_statuses', 'sensitive', 'silence', 'suspend']), - text: z.string().catch(''), - status_ids: z.array(z.string()).catch([]), +const accountWarningSchema = v.object({ + id: v.string(), + action: v.picklist(['none', 'disable', 'mark_statuses_as_sensitive', 'delete_statuses', 'sensitive', 'silence', 'suspend']), + text: v.fallback(v.string(), ''), + status_ids: v.fallback(v.array(v.string()), []), target_account: accountSchema, - appeal: appealSchema.nullable().catch(null), + appeal: v.fallback(v.nullable(appealSchema), null), created_at: dateSchema, }); -type AccountWarning = z.infer; +type AccountWarning = v.InferOutput; export { accountWarningSchema, type AccountWarning }; diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index e4a2c2b07..ee9a970e2 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -1,5 +1,5 @@ import pick from 'lodash.pick'; -import z from 'zod'; +import * as v from 'valibot'; import { customEmojiSchema } from './custom-emoji'; import { relationshipSchema } from './relationship'; @@ -61,64 +61,64 @@ const preprocessAccount = (account: any) => { }; }; -const fieldSchema = z.object({ - name: z.string(), - value: z.string(), +const fieldSchema = v.object({ + name: v.string(), + value: v.string(), verified_at: z.string().datetime({ offset: true }).nullable().catch(null), }); -const baseAccountSchema = z.object({ - id: z.string(), - username: z.string().catch(''), - acct: z.string().catch(''), +const baseAccountSchema = v.object({ + id: v.string(), + username: v.fallback(v.string(), ''), + acct: v.fallback(v.string(), ''), url: z.string().url(), - display_name: z.string().catch(''), - note: z.string().catch(''), - avatar: z.string().catch(''), + display_name: v.fallback(v.string(), ''), + note: v.fallback(v.string(), ''), + avatar: v.fallback(v.string(), ''), avatar_static: z.string().url().catch(''), header: z.string().url().catch(''), header_static: z.string().url().catch(''), - locked: z.boolean().catch(false), + locked: v.fallback(v.boolean(), false), fields: filteredArray(fieldSchema), emojis: filteredArray(customEmojiSchema), - bot: z.boolean().catch(false), - group: z.boolean().catch(false), - discoverable: z.boolean().catch(false), - noindex: z.boolean().nullable().catch(null), - suspended: z.boolean().optional().catch(undefined), - limited: z.boolean().optional().catch(undefined), + bot: v.fallback(v.boolean(), false), + group: v.fallback(v.boolean(), false), + discoverable: v.fallback(v.boolean(), false), + noindex: v.fallback(v.optional(v.nullable()), null), + suspended: v.fallback(v.optional(v.boolean()), undefined), + limited: v.fallback(v.optional(v.boolean()), undefined), created_at: z.string().datetime().catch(new Date().toUTCString()), last_status_at: z.string().date().nullable().catch(null), - statuses_count: z.number().catch(0), - followers_count: z.number().catch(0), - following_count: z.number().catch(0), + statuses_count: v.fallback(v.number(), 0), + followers_count: v.fallback(v.number(), 0), + following_count: v.fallback(v.number(), 0), roles: filteredArray(roleSchema), - fqn: z.string().nullable().catch(null), - ap_id: z.string().nullable().catch(null), - background_image: z.string().nullable().catch(null), + fqn: v.fallback(v.nullable(v.string()), null), + ap_id: v.fallback(v.nullable(v.string()), null), + background_image: v.fallback(v.nullable(v.string()), null), relationship: relationshipSchema.optional().catch(undefined), - is_moderator: z.boolean().optional().catch(undefined), - is_admin: z.boolean().optional().catch(undefined), - is_suggested: z.boolean().optional().catch(undefined), - hide_favorites: z.boolean().catch(true), - hide_followers: z.boolean().optional().catch(undefined), - hide_follows: z.boolean().optional().catch(undefined), - hide_followers_count: z.boolean().optional().catch(undefined), - hide_follows_count: z.boolean().optional().catch(undefined), - accepts_chat_messages: z.boolean().nullable().catch(null), - favicon: z.string().optional().catch(undefined), + is_moderator: v.fallback(v.optional(v.boolean()), undefined), + is_admin: v.fallback(v.optional(v.boolean()), undefined), + is_suggested: v.fallback(v.optional(v.boolean()), undefined), + hide_favorites: v.fallback(v.boolean(), true), + hide_followers: v.fallback(v.optional(v.boolean()), undefined), + hide_follows: v.fallback(v.optional(v.boolean()), undefined), + hide_followers_count: v.fallback(v.optional(v.boolean()), undefined), + hide_follows_count: v.fallback(v.optional(v.boolean()), undefined), + accepts_chat_messages: v.fallback(v.optional(v.nullable()), null), + favicon: v.fallback(v.optional(v.string()), undefined), birthday: z.string().date().optional().catch(undefined), - deactivated: z.boolean().optional().catch(undefined), + deactivated: v.fallback(v.optional(v.boolean()), undefined), - location: z.string().optional().catch(undefined), + location: v.fallback(v.optional(v.string()), undefined), local: z.boolean().optional().catch(false), - avatar_description: z.string().catch(''), - enable_rss: z.boolean().catch(false), - header_description: z.string().catch(''), + avatar_description: v.fallback(v.string(), ''), + enable_rss: v.fallback(v.boolean(), false), + header_description: v.fallback(v.string(), ''), - verified: z.boolean().optional().catch(undefined), + verified: v.fallback(v.optional(v.boolean()), undefined), __meta: coerceObject({ pleroma: z.any().optional().catch(undefined), @@ -137,47 +137,47 @@ type WithMoved = { moved: Account | null; }; -type Account = z.infer & WithMoved; +type Account = v.InferOutput & WithMoved; const accountSchema: z.ZodType = untypedAccountSchema as any; const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({ - source: z.object({ - note: z.string().catch(''), + source: v.object({ + note: v.fallback(v.string(), ''), fields: filteredArray(fieldSchema), privacy: z.enum(['public', 'unlisted', 'private', 'direct']), - sensitive: z.boolean().catch(false), - language: z.string().nullable().catch(null), + sensitive: v.fallback(v.boolean(), false), + language: v.fallback(v.nullable(v.string()), null), follow_requests_count: z.number().int().nonnegative().catch(0), show_role: z.boolean().optional().nullable().catch(undefined), no_rich_text: z.boolean().optional().nullable().catch(undefined), - discoverable: z.boolean().optional().catch(undefined), - actor_type: z.string().optional().catch(undefined), - show_birthday: z.boolean().optional().catch(undefined), + discoverable: v.fallback(v.optional(v.boolean()), undefined), + actor_type: v.fallback(v.optional(v.string()), undefined), + show_birthday: v.fallback(v.optional(v.boolean()), undefined), }).nullable().catch(null), - role: roleSchema.nullable().catch(null), + role: v.fallback(v.nullable(roleSchema), null), settings_store: z.record(z.any()).optional().catch(undefined), - chat_token: z.string().optional().catch(undefined), - allow_following_move: z.boolean().optional().catch(undefined), + chat_token: v.fallback(v.optional(v.string()), undefined), + allow_following_move: v.fallback(v.optional(v.boolean()), undefined), unread_conversation_count: z.number().optional().catch(undefined), unread_notifications_count: z.number().optional().catch(undefined), - notification_settings: z.object({ - block_from_strangers: z.boolean().catch(false), - hide_notification_contents: z.boolean().catch(false), + notification_settings: v.object({ + block_from_strangers: v.fallback(v.boolean(), false), + hide_notification_contents: v.fallback(v.boolean(), false), }).optional().catch(undefined), })); -type CredentialAccount = z.infer & WithMoved; +type CredentialAccount = v.InferOutput & WithMoved; const credentialAccountSchema: z.ZodType = untypedCredentialAccountSchema as any; const untypedMutedAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({ - mute_expires_at: dateSchema.nullable().catch(null), + mute_expires_at: v.fallback(v.nullable(dateSchema), null), })); -type MutedAccount = z.infer & WithMoved; +type MutedAccount = v.InferOutput & WithMoved; const mutedAccountSchema: z.ZodType = untypedMutedAccountSchema as any; diff --git a/packages/pl-api/lib/entities/admin/account.ts b/packages/pl-api/lib/entities/admin/account.ts index 3e25324aa..e142ccfe3 100644 --- a/packages/pl-api/lib/entities/admin/account.ts +++ b/packages/pl-api/lib/entities/admin/account.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { accountSchema } from '../account'; import { roleSchema } from '../role'; @@ -36,32 +36,32 @@ const adminAccountSchema = z.preprocess((account: any) => { }; } return account; -}, z.object({ - id: z.string(), - username: z.string(), - domain: z.string().nullable().catch(null), +}, v.object({ + id: v.string(), + username: v.string(), + domain: v.fallback(v.nullable(v.string()), null), created_at: dateSchema, - email: z.string().nullable().catch(null), + email: v.fallback(v.nullable(v.string()), null), ip: z.string().ip().nullable().catch(null), ips: filteredArray(adminIpSchema), - locale: z.string().nullable().catch(null), - invite_request: z.string().nullable().catch(null), - role: roleSchema.nullable().catch(null), - confirmed: z.boolean().catch(false), - approved: z.boolean().catch(false), - disabled: z.boolean().catch(false), - silenced: z.boolean().catch(false), - suspended: z.boolean().catch(false), - account: accountSchema.nullable().catch(null), - created_by_application_id: z.string().optional().catch(undefined), - invited_by_account_id: z.string().optional().catch(undefined), + locale: v.fallback(v.nullable(v.string()), null), + invite_request: v.fallback(v.nullable(v.string()), null), + role: v.fallback(v.nullable(roleSchema), null), + confirmed: v.fallback(v.boolean(), false), + approved: v.fallback(v.boolean(), false), + disabled: v.fallback(v.boolean(), false), + silenced: v.fallback(v.boolean(), false), + suspended: v.fallback(v.boolean(), false), + account: v.fallback(v.nullable(accountSchema), null), + created_by_application_id: v.fallback(v.optional(v.string()), undefined), + invited_by_account_id: v.fallback(v.optional(v.string()), undefined), - actor_type: z.string().nullable().catch(null), - display_name: z.string().nullable().catch(null), - suggested: z.boolean().nullable().catch(null), + actor_type: v.fallback(v.nullable(v.string()), null), + display_name: v.fallback(v.nullable(v.string()), null), + suggested: v.fallback(v.optional(v.nullable()), null), })); -type AdminAccount = z.infer; +type AdminAccount = v.InferOutput; export { adminAccountSchema, diff --git a/packages/pl-api/lib/entities/admin/announcement.ts b/packages/pl-api/lib/entities/admin/announcement.ts index 365413d64..3a670a7f4 100644 --- a/packages/pl-api/lib/entities/admin/announcement.ts +++ b/packages/pl-api/lib/entities/admin/announcement.ts @@ -1,5 +1,5 @@ import pick from 'lodash.pick'; -import { z } from 'zod'; +import * as v from 'valibot'; import { announcementSchema } from '../announcement'; @@ -8,9 +8,9 @@ const adminAnnouncementSchema = z.preprocess((announcement: any) => ({ ...announcement, ...pick(announcement.pleroma, 'raw_content'), }), announcementSchema.extend({ - raw_content: z.string().catch(''), + raw_content: v.fallback(v.string(), ''), })); -type AdminAnnouncement = z.infer; +type AdminAnnouncement = v.InferOutput; export { adminAnnouncementSchema, type AdminAnnouncement }; diff --git a/packages/pl-api/lib/entities/admin/canonical-email-block.ts b/packages/pl-api/lib/entities/admin/canonical-email-block.ts index 817028c20..2394bb44b 100644 --- a/packages/pl-api/lib/entities/admin/canonical-email-block.ts +++ b/packages/pl-api/lib/entities/admin/canonical-email-block.ts @@ -1,12 +1,12 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_CanonicalEmailBlock/} */ -const adminCanonicalEmailBlockSchema = z.object({ - id: z.string(), - canonical_email_hash: z.string(), +const adminCanonicalEmailBlockSchema = v.object({ + id: v.string(), + canonical_email_hash: v.string(), }); -type AdminCanonicalEmailBlock = z.infer; +type AdminCanonicalEmailBlock = v.InferOutput; export { adminCanonicalEmailBlockSchema, diff --git a/packages/pl-api/lib/entities/admin/cohort.ts b/packages/pl-api/lib/entities/admin/cohort.ts index 5c3cb3ea8..72ae379d6 100644 --- a/packages/pl-api/lib/entities/admin/cohort.ts +++ b/packages/pl-api/lib/entities/admin/cohort.ts @@ -1,17 +1,17 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_Cohort/} */ -const adminCohortSchema = z.object({ +const adminCohortSchema = v.object({ period: z.string().datetime({ offset: true }), frequency: z.enum(['day', 'month']), - data: z.array(z.object({ + data: z.array(v.object({ date: z.string().datetime({ offset: true }), rate: z.number(), value: z.number().int(), })), }); -type AdminCohort = z.infer; +type AdminCohort = v.InferOutput; export { adminCohortSchema, diff --git a/packages/pl-api/lib/entities/admin/dimension.ts b/packages/pl-api/lib/entities/admin/dimension.ts index 45105a2a7..b382da6a7 100644 --- a/packages/pl-api/lib/entities/admin/dimension.ts +++ b/packages/pl-api/lib/entities/admin/dimension.ts @@ -1,18 +1,18 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_Dimension/} */ -const adminDimensionSchema = z.object({ - key: z.string(), - data: z.object({ - key: z.string(), - human_key: z.string(), - value: z.string(), - unit: z.string().optional().catch(undefined), - human_value: z.string().optional().catch(undefined), +const adminDimensionSchema = v.object({ + key: v.string(), + data: v.object({ + key: v.string(), + human_key: v.string(), + value: v.string(), + unit: v.fallback(v.optional(v.string()), undefined), + human_value: v.fallback(v.optional(v.string()), undefined), }), }); -type AdminDimension = z.infer; +type AdminDimension = v.InferOutput; export { adminDimensionSchema, diff --git a/packages/pl-api/lib/entities/admin/domain-allow.ts b/packages/pl-api/lib/entities/admin/domain-allow.ts index a012938f0..be6c73078 100644 --- a/packages/pl-api/lib/entities/admin/domain-allow.ts +++ b/packages/pl-api/lib/entities/admin/domain-allow.ts @@ -1,15 +1,15 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { dateSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_DomainAllow/} */ -const adminDomainAllowSchema = z.object({ - id: z.string(), - domain: z.string(), +const adminDomainAllowSchema = v.object({ + id: v.string(), + domain: v.string(), created_at: dateSchema, }); -type AdminDomainAllow = z.infer; +type AdminDomainAllow = v.InferOutput; export { adminDomainAllowSchema, diff --git a/packages/pl-api/lib/entities/admin/domain-block.ts b/packages/pl-api/lib/entities/admin/domain-block.ts index b1452c3ea..9d87e63a6 100644 --- a/packages/pl-api/lib/entities/admin/domain-block.ts +++ b/packages/pl-api/lib/entities/admin/domain-block.ts @@ -1,22 +1,22 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { dateSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_DomainBlock/} */ -const adminDomainBlockSchema = z.object({ - id: z.string(), - domain: z.string(), - digest: z.string(), +const adminDomainBlockSchema = v.object({ + id: v.string(), + domain: v.string(), + digest: v.string(), created_at: dateSchema, severity: z.enum(['silence', 'suspend', 'noop']), - reject_media: z.boolean(), - reject_reports: z.boolean(), - private_comment: z.string().nullable().catch(null), - public_comment: z.string().nullable().catch(null), - obfuscate: z.boolean(), + reject_media: v.boolean(), + reject_reports: v.boolean(), + private_comment: v.fallback(v.nullable(v.string()), null), + public_comment: v.fallback(v.nullable(v.string()), null), + obfuscate: v.boolean(), }); -type AdminDomainBlock = z.infer; +type AdminDomainBlock = v.InferOutput; export { adminDomainBlockSchema, diff --git a/packages/pl-api/lib/entities/admin/domain.ts b/packages/pl-api/lib/entities/admin/domain.ts index 73c3a8f58..67d6253ad 100644 --- a/packages/pl-api/lib/entities/admin/domain.ts +++ b/packages/pl-api/lib/entities/admin/domain.ts @@ -1,13 +1,13 @@ -import z from 'zod'; +import * as v from 'valibot'; -const adminDomainSchema = z.object({ - domain: z.string().catch(''), +const adminDomainSchema = v.object({ + domain: v.fallback(v.string(), ''), id: z.coerce.string(), - public: z.boolean().catch(false), - resolves: z.boolean().catch(false), + public: v.fallback(v.boolean(), false), + resolves: v.fallback(v.boolean(), false), last_checked_at: z.string().datetime().catch(''), }); -type AdminDomain = z.infer +type AdminDomain = v.InferOutput export { adminDomainSchema, type AdminDomain }; diff --git a/packages/pl-api/lib/entities/admin/email-domain-block.ts b/packages/pl-api/lib/entities/admin/email-domain-block.ts index 093ee85f5..d3c57e179 100644 --- a/packages/pl-api/lib/entities/admin/email-domain-block.ts +++ b/packages/pl-api/lib/entities/admin/email-domain-block.ts @@ -1,20 +1,20 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { dateSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_EmailDomainBlock/} */ -const adminEmailDomainBlockSchema = z.object({ - id: z.string(), - domain: z.string(), +const adminEmailDomainBlockSchema = v.object({ + id: v.string(), + domain: v.string(), created_at: dateSchema, - history: z.array(z.object({ + history: v.array(v.object({ day: z.coerce.string(), accounts: z.coerce.string(), uses: z.coerce.string(), })), }); -type AdminEmailDomainBlock = z.infer; +type AdminEmailDomainBlock = v.InferOutput; export { adminEmailDomainBlockSchema, diff --git a/packages/pl-api/lib/entities/admin/ip-block.ts b/packages/pl-api/lib/entities/admin/ip-block.ts index f3cf6bfe3..580627a08 100644 --- a/packages/pl-api/lib/entities/admin/ip-block.ts +++ b/packages/pl-api/lib/entities/admin/ip-block.ts @@ -1,18 +1,18 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { dateSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_IpBlock/} */ -const adminIpBlockSchema = z.object({ - id: z.string(), +const adminIpBlockSchema = v.object({ + id: v.string(), ip: z.string().ip(), severity: z.enum(['sign_up_requires_approval', 'sign_up_block', 'no_access']), - comment: z.string().catch(''), + comment: v.fallback(v.string(), ''), created_at: dateSchema, expires_at: z.string().datetime({ offset: true }), }); -type AdminIpBlock = z.infer; +type AdminIpBlock = v.InferOutput; export { adminIpBlockSchema, diff --git a/packages/pl-api/lib/entities/admin/ip.ts b/packages/pl-api/lib/entities/admin/ip.ts index e27b30f30..83ebb5654 100644 --- a/packages/pl-api/lib/entities/admin/ip.ts +++ b/packages/pl-api/lib/entities/admin/ip.ts @@ -1,14 +1,14 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { dateSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_Ip/} */ -const adminIpSchema = z.object({ +const adminIpSchema = v.object({ ip: z.string().ip(), used_at: dateSchema, }); -type AdminIp = z.infer; +type AdminIp = v.InferOutput; export { adminIpSchema, diff --git a/packages/pl-api/lib/entities/admin/measure.ts b/packages/pl-api/lib/entities/admin/measure.ts index 5e1cda5da..f1c9cb8de 100644 --- a/packages/pl-api/lib/entities/admin/measure.ts +++ b/packages/pl-api/lib/entities/admin/measure.ts @@ -1,19 +1,19 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_Measure/} */ -const adminMeasureSchema = z.object({ - key: z.string(), - unit: z.string().nullable().catch(null), +const adminMeasureSchema = v.object({ + key: v.string(), + unit: v.fallback(v.nullable(v.string()), null), total: z.coerce.number(), - human_value: z.string().optional().catch(undefined), + human_value: v.fallback(v.optional(v.string()), undefined), previous_total: z.coerce.string().optional().catch(undefined), - data: z.array(z.object({ + data: z.array(v.object({ date: z.string().datetime({ offset: true }), value: z.coerce.string(), })), }); -type AdminMeasure = z.infer; +type AdminMeasure = v.InferOutput; export { adminMeasureSchema, diff --git a/packages/pl-api/lib/entities/admin/moderation-log-entry.ts b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts index 0bd93ef37..b901128a1 100644 --- a/packages/pl-api/lib/entities/admin/moderation-log-entry.ts +++ b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts @@ -1,13 +1,13 @@ -import z from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminmoderation_log} */ -const adminModerationLogEntrySchema = z.object({ +const adminModerationLogEntrySchema = v.object({ id: z.coerce.string(), - data: z.record(z.string(), z.any()).catch({}), - time: z.number().catch(0), - message: z.string().catch(''), + data: z.record(v.string(), z.any()).catch({}), + time: v.fallback(v.number(), 0), + message: v.fallback(v.string(), ''), }); -type AdminModerationLogEntry = z.infer +type AdminModerationLogEntry = v.InferOutput export { adminModerationLogEntrySchema, type AdminModerationLogEntry }; diff --git a/packages/pl-api/lib/entities/admin/pleroma-config.ts b/packages/pl-api/lib/entities/admin/pleroma-config.ts index 8bb49e5f0..8fb09f5a3 100644 --- a/packages/pl-api/lib/entities/admin/pleroma-config.ts +++ b/packages/pl-api/lib/entities/admin/pleroma-config.ts @@ -1,14 +1,14 @@ -import { z } from 'zod'; +import * as v from 'valibot'; -const pleromaConfigSchema = z.object({ - configs: z.array(z.object({ +const pleromaConfigSchema = v.object({ + configs: z.array(v.object({ value: z.any(), - group: z.string(), - key: z.string(), + group: v.string(), + key: v.string(), })), - need_reboot: z.boolean(), + need_reboot: v.boolean(), }); -type PleromaConfig = z.infer +type PleromaConfig = v.InferOutput export { pleromaConfigSchema, type PleromaConfig }; diff --git a/packages/pl-api/lib/entities/admin/relay.ts b/packages/pl-api/lib/entities/admin/relay.ts index b084b2353..ce19ea3ca 100644 --- a/packages/pl-api/lib/entities/admin/relay.ts +++ b/packages/pl-api/lib/entities/admin/relay.ts @@ -1,11 +1,11 @@ -import z from 'zod'; +import * as v from 'valibot'; -const adminRelaySchema = z.preprocess((data: any) => ({ id: data.actor, ...data }), z.object({ - actor: z.string().catch(''), - id: z.string(), - followed_back: z.boolean().catch(false), +const adminRelaySchema = z.preprocess((data: any) => ({ id: data.actor, ...data }), v.object({ + actor: v.fallback(v.string(), ''), + id: v.string(), + followed_back: v.fallback(v.boolean(), false), })); -type AdminRelay = z.infer +type AdminRelay = v.InferOutput export { adminRelaySchema, type AdminRelay }; diff --git a/packages/pl-api/lib/entities/admin/report.ts b/packages/pl-api/lib/entities/admin/report.ts index a669517c4..7b64b0f67 100644 --- a/packages/pl-api/lib/entities/admin/report.ts +++ b/packages/pl-api/lib/entities/admin/report.ts @@ -1,5 +1,5 @@ import pick from 'lodash.pick'; -import { z } from 'zod'; +import * as v from 'valibot'; import { ruleSchema } from '../rule'; import { statusWithoutAccountSchema } from '../status'; @@ -24,23 +24,23 @@ const adminReportSchema = z.preprocess((report: any) => { }; } return report; -}, z.object({ - id: z.string(), - action_taken: z.boolean().optional().catch(undefined), - action_taken_at: dateSchema.nullable().catch(null), - category: z.string().optional().catch(undefined), - comment: z.string().optional().catch(undefined), - forwarded: z.boolean().optional().catch(undefined), +}, v.object({ + id: v.string(), + action_taken: v.fallback(v.optional(v.boolean()), undefined), + action_taken_at: v.fallback(v.nullable(dateSchema), null), + category: v.fallback(v.optional(v.string()), undefined), + comment: v.fallback(v.optional(v.string()), undefined), + forwarded: v.fallback(v.optional(v.boolean()), undefined), created_at: dateSchema.optional().catch(undefined), updated_at: dateSchema.optional().catch(undefined), account: adminAccountSchema, target_account: adminAccountSchema, - assigned_account: adminAccountSchema.nullable().catch(null), - action_taken_by_account: adminAccountSchema.nullable().catch(null), + assigned_account: v.fallback(v.nullable(adminAccountSchema), null), + action_taken_by_account: v.fallback(v.nullable(adminAccountSchema), null), statuses: filteredArray(statusWithoutAccountSchema), rules: filteredArray(ruleSchema), })); -type AdminReport = z.infer; +type AdminReport = v.InferOutput; export { adminReportSchema, type AdminReport }; diff --git a/packages/pl-api/lib/entities/admin/rule.ts b/packages/pl-api/lib/entities/admin/rule.ts index a3dc1d2ab..41838af6a 100644 --- a/packages/pl-api/lib/entities/admin/rule.ts +++ b/packages/pl-api/lib/entities/admin/rule.ts @@ -1,13 +1,13 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminrules} */ -const adminRuleSchema = z.object({ - id: z.string(), - text: z.string().catch(''), - hint: z.string().catch(''), - priority: z.number().nullable().catch(null), +const adminRuleSchema = v.object({ + id: v.string(), + text: v.fallback(v.string(), ''), + hint: v.fallback(v.string(), ''), + priority: v.fallback(v.nullable(v.number()), null), }); -type AdminRule = z.infer; +type AdminRule = v.InferOutput; export { adminRuleSchema, type AdminRule }; diff --git a/packages/pl-api/lib/entities/admin/tag.ts b/packages/pl-api/lib/entities/admin/tag.ts index 467de653a..983a64538 100644 --- a/packages/pl-api/lib/entities/admin/tag.ts +++ b/packages/pl-api/lib/entities/admin/tag.ts @@ -1,16 +1,16 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { tagSchema } from '../tag'; /** @see {@link https://docs.joinmastodon.org/entities/Tag/#admin} */ const adminTagSchema = tagSchema.extend({ - id: z.string(), - trendable: z.boolean(), - usable: z.boolean(), - requires_review: z.boolean(), + id: v.string(), + trendable: v.boolean(), + usable: v.boolean(), + requires_review: v.boolean(), }); -type AdminTag = z.infer; +type AdminTag = v.InferOutput; export { adminTagSchema, diff --git a/packages/pl-api/lib/entities/announcement-reaction.ts b/packages/pl-api/lib/entities/announcement-reaction.ts index 8949dbd3e..9fb1a0da7 100644 --- a/packages/pl-api/lib/entities/announcement-reaction.ts +++ b/packages/pl-api/lib/entities/announcement-reaction.ts @@ -1,15 +1,15 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/announcement/} */ -const announcementReactionSchema = z.object({ - name: z.string().catch(''), +const announcementReactionSchema = v.object({ + name: v.fallback(v.string(), ''), count: z.number().int().nonnegative().catch(0), - me: z.boolean().catch(false), - url: z.string().nullable().catch(null), - static_url: z.string().nullable().catch(null), - announcement_id: z.string().catch(''), + me: v.fallback(v.boolean(), false), + url: v.fallback(v.nullable(v.string()), null), + static_url: v.fallback(v.nullable(v.string()), null), + announcement_id: v.fallback(v.string(), ''), }); -type AnnouncementReaction = z.infer; +type AnnouncementReaction = v.InferOutput; export { announcementReactionSchema, type AnnouncementReaction }; diff --git a/packages/pl-api/lib/entities/announcement.ts b/packages/pl-api/lib/entities/announcement.ts index 535fb954e..f161e97d0 100644 --- a/packages/pl-api/lib/entities/announcement.ts +++ b/packages/pl-api/lib/entities/announcement.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { announcementReactionSchema } from './announcement-reaction'; import { customEmojiSchema } from './custom-emoji'; @@ -7,20 +7,20 @@ import { tagSchema } from './tag'; import { dateSchema, filteredArray } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/announcement/} */ -const announcementSchema = z.object({ - id: z.string(), - content: z.string().catch(''), +const announcementSchema = v.object({ + id: v.string(), + content: v.fallback(v.string(), ''), starts_at: z.string().datetime().nullable().catch(null), ends_at: z.string().datetime().nullable().catch(null), - all_day: z.boolean().catch(false), - read: z.boolean().catch(false), + all_day: v.fallback(v.boolean(), false), + read: v.fallback(v.boolean(), false), published_at: dateSchema, reactions: filteredArray(announcementReactionSchema), statuses: z.preprocess( (statuses: any) => Array.isArray(statuses) ? Object.fromEntries(statuses.map((status: any) => [status.url, status.account?.acct]) || []) : statuses, - z.record(z.string(), z.string()), + z.record(v.string(), v.string()), ), mentions: filteredArray(mentionSchema), tags: filteredArray(tagSchema), @@ -28,6 +28,6 @@ const announcementSchema = z.object({ updated_at: dateSchema, }); -type Announcement = z.infer; +type Announcement = v.InferOutput; export { announcementSchema, type Announcement }; diff --git a/packages/pl-api/lib/entities/application.ts b/packages/pl-api/lib/entities/application.ts index 03377910b..6d3949378 100644 --- a/packages/pl-api/lib/entities/application.ts +++ b/packages/pl-api/lib/entities/application.ts @@ -1,19 +1,19 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/Application/} */ -const applicationSchema = z.object({ - name: z.string().catch(''), - website: z.string().optional().catch(undefined), - client_id: z.string().optional().catch(undefined), - client_secret: z.string().optional().catch(undefined), - redirect_uri: z.string().optional().catch(undefined), +const applicationSchema = v.object({ + name: v.fallback(v.string(), ''), + website: v.fallback(v.optional(v.string()), undefined), + client_id: v.fallback(v.optional(v.string()), undefined), + client_secret: v.fallback(v.optional(v.string()), undefined), + redirect_uri: v.fallback(v.optional(v.string()), undefined), - id: z.string().optional().catch(undefined), + id: v.fallback(v.optional(v.string()), undefined), /** @deprecated */ - vapid_key: z.string().optional().catch(undefined), + vapid_key: v.fallback(v.optional(v.string()), undefined), }); -type Application = z.infer; +type Application = v.InferOutput; export { applicationSchema, type Application }; diff --git a/packages/pl-api/lib/entities/backup.ts b/packages/pl-api/lib/entities/backup.ts index b7429db37..9c4acc3d8 100644 --- a/packages/pl-api/lib/entities/backup.ts +++ b/packages/pl-api/lib/entities/backup.ts @@ -1,17 +1,17 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { dateSchema, mimeSchema } from './utils'; /** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromabackups} */ -const backupSchema = z.object({ +const backupSchema = v.object({ id: z.coerce.string(), contentType: mimeSchema, - file_size: z.number().catch(0), + file_size: v.fallback(v.number(), 0), inserted_at: dateSchema, - processed: z.boolean().catch(false), - url: z.string().catch(''), + processed: v.fallback(v.boolean(), false), + url: v.fallback(v.string(), ''), }); -type Backup = z.infer; +type Backup = v.InferOutput; export { backupSchema, type Backup }; diff --git a/packages/pl-api/lib/entities/bookmark-folder.ts b/packages/pl-api/lib/entities/bookmark-folder.ts index 2f4da29cd..edd634cf8 100644 --- a/packages/pl-api/lib/entities/bookmark-folder.ts +++ b/packages/pl-api/lib/entities/bookmark-folder.ts @@ -1,12 +1,12 @@ -import { z } from 'zod'; +import * as v from 'valibot'; -const bookmarkFolderSchema = z.object({ +const bookmarkFolderSchema = v.object({ id: z.coerce.string(), - name: z.string().catch(''), - emoji: z.string().nullable().catch(null), - emoji_url: z.string().nullable().catch(null), + name: v.fallback(v.string(), ''), + emoji: v.fallback(v.nullable(v.string()), null), + emoji_url: v.fallback(v.nullable(v.string()), null), }); -type BookmarkFolder = z.infer; +type BookmarkFolder = v.InferOutput; export { bookmarkFolderSchema, type BookmarkFolder }; diff --git a/packages/pl-api/lib/entities/chat-message.ts b/packages/pl-api/lib/entities/chat-message.ts index 09a265c67..387e3bc8e 100644 --- a/packages/pl-api/lib/entities/chat-message.ts +++ b/packages/pl-api/lib/entities/chat-message.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { customEmojiSchema } from './custom-emoji'; import { mediaAttachmentSchema } from './media-attachment'; @@ -6,18 +6,18 @@ import { previewCardSchema } from './preview-card'; import { dateSchema, filteredArray } from './utils'; /** @see {@link https://docs.pleroma.social/backend/development/API/chats/#getting-the-messages-for-a-chat} */ -const chatMessageSchema = z.object({ - id: z.string(), - content: z.string().catch(''), - chat_id: z.string(), - account_id: z.string(), +const chatMessageSchema = v.object({ + id: v.string(), + content: v.fallback(v.string(), ''), + chat_id: v.string(), + account_id: v.string(), created_at: dateSchema, emojis: filteredArray(customEmojiSchema), - attachment: mediaAttachmentSchema.nullable().catch(null), - unread: z.boolean(), - card: previewCardSchema.nullable().catch(null), + attachment: v.fallback(v.nullable(mediaAttachmentSchema), null), + unread: v.boolean(), + card: v.fallback(v.nullable(previewCardSchema), null), }); -type ChatMessage = z.infer; +type ChatMessage = v.InferOutput; export { chatMessageSchema, type ChatMessage }; diff --git a/packages/pl-api/lib/entities/chat.ts b/packages/pl-api/lib/entities/chat.ts index 1d2c1492e..2d4762e14 100644 --- a/packages/pl-api/lib/entities/chat.ts +++ b/packages/pl-api/lib/entities/chat.ts @@ -1,18 +1,18 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; import { chatMessageSchema } from './chat-message'; import { dateSchema } from './utils'; /** @see {@link https://docs.pleroma.social/backend/development/API/chats/#getting-a-list-of-chats} */ -const chatSchema = z.object({ - id: z.string(), +const chatSchema = v.object({ + id: v.string(), account: accountSchema, unread: z.number().int(), - last_message: chatMessageSchema.nullable().catch(null), + last_message: v.fallback(v.nullable(chatMessageSchema), null), created_at: dateSchema, }); -type Chat = z.infer; +type Chat = v.InferOutput; export { chatSchema, type Chat }; diff --git a/packages/pl-api/lib/entities/context.ts b/packages/pl-api/lib/entities/context.ts index 8e7cd2e42..75c32d786 100644 --- a/packages/pl-api/lib/entities/context.ts +++ b/packages/pl-api/lib/entities/context.ts @@ -1,13 +1,13 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { statusSchema } from './status'; /** @see {@link https://docs.joinmastodon.org/entities/Context/} */ -const contextSchema = z.object({ +const contextSchema = v.object({ ancestors: z.array(statusSchema), descendants: z.array(statusSchema), }); -type Context = z.infer; +type Context = v.InferOutput; export { contextSchema, type Context }; diff --git a/packages/pl-api/lib/entities/conversation.ts b/packages/pl-api/lib/entities/conversation.ts index 719d0c398..8bdb375f1 100644 --- a/packages/pl-api/lib/entities/conversation.ts +++ b/packages/pl-api/lib/entities/conversation.ts @@ -1,17 +1,17 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { filteredArray } from './utils'; import { accountSchema, statusSchema } from '.'; /** @see {@link https://docs.joinmastodon.org/entities/Conversation} */ -const conversationSchema = z.object({ - id: z.string(), - unread: z.boolean().catch(false), +const conversationSchema = v.object({ + id: v.string(), + unread: v.fallback(v.boolean(), false), accounts: filteredArray(accountSchema), - last_status: statusSchema.nullable().catch(null), + last_status: v.fallback(v.nullable(statusSchema), null), }); -type Conversation = z.infer; +type Conversation = v.InferOutput; export { conversationSchema, type Conversation }; diff --git a/packages/pl-api/lib/entities/custom-emoji.ts b/packages/pl-api/lib/entities/custom-emoji.ts index 1b9f2313c..5723990a7 100644 --- a/packages/pl-api/lib/entities/custom-emoji.ts +++ b/packages/pl-api/lib/entities/custom-emoji.ts @@ -1,17 +1,17 @@ -import z from 'zod'; +import * as v from 'valibot'; /** * Represents a custom emoji. * @see {@link https://docs.joinmastodon.org/entities/CustomEmoji/} */ -const customEmojiSchema = z.object({ - shortcode: z.string(), - url: z.string(), - static_url: z.string().catch(''), - visible_in_picker: z.boolean().catch(true), - category: z.string().nullable().catch(null), +const customEmojiSchema = v.object({ + shortcode: v.string(), + url: v.string(), + static_url: v.fallback(v.string(), ''), + visible_in_picker: v.fallback(v.boolean(), true), + category: v.fallback(v.nullable(v.string()), null), }); -type CustomEmoji = z.infer; +type CustomEmoji = v.InferOutput; export { customEmojiSchema, type CustomEmoji }; diff --git a/packages/pl-api/lib/entities/directory/category.ts b/packages/pl-api/lib/entities/directory/category.ts index d40bf48df..19b35cdde 100644 --- a/packages/pl-api/lib/entities/directory/category.ts +++ b/packages/pl-api/lib/entities/directory/category.ts @@ -1,10 +1,10 @@ -import { z } from 'zod'; +import * as v from 'valibot'; -const directoryCategorySchema = z.object({ - category: z.string(), +const directoryCategorySchema = v.object({ + category: v.string(), servers_count: z.coerce.number().nullable().catch(null), }); -type DirectoryCategory = z.infer; +type DirectoryCategory = v.InferOutput; export { directoryCategorySchema, type DirectoryCategory }; diff --git a/packages/pl-api/lib/entities/directory/language.ts b/packages/pl-api/lib/entities/directory/language.ts index 4ac2e1071..e97b397d8 100644 --- a/packages/pl-api/lib/entities/directory/language.ts +++ b/packages/pl-api/lib/entities/directory/language.ts @@ -1,11 +1,11 @@ -import { z } from 'zod'; +import * as v from 'valibot'; -const directoryLanguageSchema = z.object({ - locale: z.string(), - language: z.string(), +const directoryLanguageSchema = v.object({ + locale: v.string(), + language: v.string(), servers_count: z.coerce.number().nullable().catch(null), }); -type DirectoryLanguage = z.infer; +type DirectoryLanguage = v.InferOutput; export { directoryLanguageSchema, type DirectoryLanguage }; diff --git a/packages/pl-api/lib/entities/directory/server.ts b/packages/pl-api/lib/entities/directory/server.ts index 7ed72c320..231cbff3e 100644 --- a/packages/pl-api/lib/entities/directory/server.ts +++ b/packages/pl-api/lib/entities/directory/server.ts @@ -1,21 +1,21 @@ -import { z } from 'zod'; +import * as v from 'valibot'; -const directoryServerSchema = z.object({ - domain: z.string(), - version: z.string(), - description: z.string(), - languages: z.array(z.string()), - region: z.string(), - categories: z.array(z.string()), +const directoryServerSchema = v.object({ + domain: v.string(), + version: v.string(), + description: v.string(), + languages: z.array(v.string()), + region: v.string(), + categories: z.array(v.string()), proxied_thumbnail: z.string().url().nullable().catch(null), - blurhash: z.string().nullable().catch(null), + blurhash: v.fallback(v.nullable(v.string()), null), total_users: z.coerce.number(), last_week_users: z.coerce.number(), - approval_required: z.boolean(), - language: z.string(), - category: z.string(), + approval_required: v.boolean(), + language: v.string(), + category: v.string(), }); -type DirectoryServer = z.infer; +type DirectoryServer = v.InferOutput; export { directoryServerSchema, type DirectoryServer }; diff --git a/packages/pl-api/lib/entities/directory/statistics-period.ts b/packages/pl-api/lib/entities/directory/statistics-period.ts index 7df181346..ab3086cab 100644 --- a/packages/pl-api/lib/entities/directory/statistics-period.ts +++ b/packages/pl-api/lib/entities/directory/statistics-period.ts @@ -1,12 +1,12 @@ -import { z } from 'zod'; +import * as v from 'valibot'; -const directoryStatisticsPeriodSchema = z.object({ +const directoryStatisticsPeriodSchema = v.object({ period: z.string().date(), server_count: z.coerce.number().nullable().catch(null), user_count: z.coerce.number().nullable().catch(null), active_user_count: z.coerce.number().nullable().catch(null), }); -type DirectoryStatisticsPeriod = z.infer; +type DirectoryStatisticsPeriod = v.InferOutput; export { directoryStatisticsPeriodSchema, type DirectoryStatisticsPeriod }; diff --git a/packages/pl-api/lib/entities/domain-block.ts b/packages/pl-api/lib/entities/domain-block.ts index 87f3fea0f..97063a7ed 100644 --- a/packages/pl-api/lib/entities/domain-block.ts +++ b/packages/pl-api/lib/entities/domain-block.ts @@ -1,13 +1,13 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/DomainBlock} */ -const domainBlockSchema = z.object({ - domain: z.string(), - digest: z.string(), +const domainBlockSchema = v.object({ + domain: v.string(), + digest: v.string(), severity: z.enum(['silence', 'suspend']), - comment: z.string().optional().catch(undefined), + comment: v.fallback(v.optional(v.string()), undefined), }); -type DomainBlock = z.infer; +type DomainBlock = v.InferOutput; export { domainBlockSchema, type DomainBlock }; diff --git a/packages/pl-api/lib/entities/emoji-reaction.ts b/packages/pl-api/lib/entities/emoji-reaction.ts index 45e335bf8..917cdb0d2 100644 --- a/packages/pl-api/lib/entities/emoji-reaction.ts +++ b/packages/pl-api/lib/entities/emoji-reaction.ts @@ -1,20 +1,20 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; import { emojiSchema, filteredArray } from './utils'; -const baseEmojiReactionSchema = z.object({ - count: z.number().nullable().catch(null), - me: z.boolean().catch(false), +const baseEmojiReactionSchema = v.object({ + count: v.fallback(v.nullable(v.number()), null), + me: v.fallback(v.boolean(), false), name: emojiSchema, url: z.literal(undefined).catch(undefined), static_url: z.literal(undefined).catch(undefined), accounts: filteredArray(accountSchema), - account_ids: filteredArray(z.string()).catch([]), + account_ids: filteredArray(v.string()).catch([]), }); const customEmojiReactionSchema = baseEmojiReactionSchema.extend({ - name: z.string(), + name: v.string(), url: z.string().url(), static_url: z.string().url(), }); @@ -29,6 +29,6 @@ const emojiReactionSchema = z.preprocess((reaction: any) => reaction ? { ...reaction, } : null, baseEmojiReactionSchema.or(customEmojiReactionSchema)); -type EmojiReaction = z.infer; +type EmojiReaction = v.InferOutput; export { emojiReactionSchema, type EmojiReaction }; diff --git a/packages/pl-api/lib/entities/extended-description.ts b/packages/pl-api/lib/entities/extended-description.ts index 2594917ab..be7fb5ab6 100644 --- a/packages/pl-api/lib/entities/extended-description.ts +++ b/packages/pl-api/lib/entities/extended-description.ts @@ -1,13 +1,13 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { dateSchema } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/ExtendedDescription} */ -const extendedDescriptionSchema = z.object({ +const extendedDescriptionSchema = v.object({ updated_at: dateSchema, - content: z.string(), + content: v.string(), }); -type ExtendedDescription = z.infer; +type ExtendedDescription = v.InferOutput; export { extendedDescriptionSchema, type ExtendedDescription }; diff --git a/packages/pl-api/lib/entities/familiar-followers.ts b/packages/pl-api/lib/entities/familiar-followers.ts index f61b1a01c..ddf1e5c91 100644 --- a/packages/pl-api/lib/entities/familiar-followers.ts +++ b/packages/pl-api/lib/entities/familiar-followers.ts @@ -1,14 +1,14 @@ -import z from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; import { filteredArray } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/FamiliarFollowers/} */ -const familiarFollowersSchema = z.object({ - id: z.string(), +const familiarFollowersSchema = v.object({ + id: v.string(), accounts: filteredArray(accountSchema), }); -type FamiliarFollowers = z.infer +type FamiliarFollowers = v.InferOutput export { familiarFollowersSchema, type FamiliarFollowers }; diff --git a/packages/pl-api/lib/entities/featured-tag.ts b/packages/pl-api/lib/entities/featured-tag.ts index 6aa8d90cb..dfd7781c6 100644 --- a/packages/pl-api/lib/entities/featured-tag.ts +++ b/packages/pl-api/lib/entities/featured-tag.ts @@ -1,14 +1,14 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/FeaturedTag/} */ -const featuredTagSchema = z.object({ - id: z.string(), - name: z.string(), - url: z.string().optional().catch(undefined), +const featuredTagSchema = v.object({ + id: v.string(), + name: v.string(), + url: v.fallback(v.optional(v.string()), undefined), statuses_count: z.number(), last_status_at: z.number(), }); -type FeaturedTag = z.infer; +type FeaturedTag = v.InferOutput; export { featuredTagSchema, type FeaturedTag }; diff --git a/packages/pl-api/lib/entities/filter-result.ts b/packages/pl-api/lib/entities/filter-result.ts index b995e6c46..36b7c1b42 100644 --- a/packages/pl-api/lib/entities/filter-result.ts +++ b/packages/pl-api/lib/entities/filter-result.ts @@ -1,14 +1,14 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { filterSchema } from './filter'; /** @see {@link https://docs.joinmastodon.org/entities/FilterResult/} */ -const filterResultSchema = z.object({ +const filterResultSchema = v.object({ filter: filterSchema, - keyword_matches: z.array(z.string()).nullable().catch(null), - status_matches: z.array(z.string()).nullable().catch(null), + keyword_matches: z.array(v.string()).nullable().catch(null), + status_matches: z.array(v.string()).nullable().catch(null), }); -type FilterResult = z.infer; +type FilterResult = v.InferOutput; export { filterResultSchema, type FilterResult }; diff --git a/packages/pl-api/lib/entities/filter.ts b/packages/pl-api/lib/entities/filter.ts index 1553d10e7..7e27850a4 100644 --- a/packages/pl-api/lib/entities/filter.ts +++ b/packages/pl-api/lib/entities/filter.ts @@ -1,18 +1,18 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { filteredArray } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/FilterKeyword/} */ -const filterKeywordSchema = z.object({ - id: z.string(), - keyword: z.string(), - whole_word: z.boolean(), +const filterKeywordSchema = v.object({ + id: v.string(), + keyword: v.string(), + whole_word: v.boolean(), }); /** @see {@link https://docs.joinmastodon.org/entities/FilterStatus/} */ -const filterStatusSchema = z.object({ - id: z.string(), - status_id: z.string(), +const filterStatusSchema = v.object({ + id: v.string(), + status_id: v.string(), }); /** @see {@link https://docs.joinmastodon.org/entities/Filter/} */ @@ -30,9 +30,9 @@ const filterSchema = z.preprocess((filter: any) => { }; } return filter; -}, z.object({ - id: z.string(), - title: z.string(), +}, v.object({ + id: v.string(), + title: v.string(), context: z.array(z.enum(['home', 'notifications', 'public', 'thread', 'account'])), expires_at: z.string().datetime({ offset: true }).nullable().catch(null), filter_action: z.enum(['warn', 'hide']).catch('warn'), @@ -40,6 +40,6 @@ const filterSchema = z.preprocess((filter: any) => { statuses: filteredArray(filterStatusSchema), })); -type Filter = z.infer; +type Filter = v.InferOutput; export { filterKeywordSchema, filterStatusSchema, filterSchema, type Filter }; diff --git a/packages/pl-api/lib/entities/group-member.ts b/packages/pl-api/lib/entities/group-member.ts index 37a736ca6..4eb249ea4 100644 --- a/packages/pl-api/lib/entities/group-member.ts +++ b/packages/pl-api/lib/entities/group-member.ts @@ -1,4 +1,4 @@ -import z from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; @@ -10,12 +10,12 @@ enum GroupRoles { type GroupRole =`${GroupRoles}`; -const groupMemberSchema = z.object({ - id: z.string(), +const groupMemberSchema = v.object({ + id: v.string(), account: accountSchema, role: z.nativeEnum(GroupRoles), }); -type GroupMember = z.infer; +type GroupMember = v.InferOutput; export { groupMemberSchema, type GroupMember, GroupRoles, type GroupRole }; diff --git a/packages/pl-api/lib/entities/group-relationship.ts b/packages/pl-api/lib/entities/group-relationship.ts index 18ad06748..79489787d 100644 --- a/packages/pl-api/lib/entities/group-relationship.ts +++ b/packages/pl-api/lib/entities/group-relationship.ts @@ -1,14 +1,14 @@ -import z from 'zod'; +import * as v from 'valibot'; import { GroupRoles } from './group-member'; -const groupRelationshipSchema = z.object({ - id: z.string(), - member: z.boolean().catch(false), +const groupRelationshipSchema = v.object({ + id: v.string(), + member: v.fallback(v.boolean(), false), role: z.nativeEnum(GroupRoles).catch(GroupRoles.USER), - requested: z.boolean().catch(false), + requested: v.fallback(v.boolean(), false), }); -type GroupRelationship = z.infer; +type GroupRelationship = v.InferOutput; export { groupRelationshipSchema, type GroupRelationship }; diff --git a/packages/pl-api/lib/entities/group.ts b/packages/pl-api/lib/entities/group.ts index fdf286946..31e5e60bf 100644 --- a/packages/pl-api/lib/entities/group.ts +++ b/packages/pl-api/lib/entities/group.ts @@ -1,33 +1,33 @@ -import z from 'zod'; +import * as v from 'valibot'; import { customEmojiSchema } from './custom-emoji'; import { groupRelationshipSchema } from './group-relationship'; import { filteredArray } from './utils'; -const groupSchema = z.object({ - avatar: z.string().catch(''), - avatar_static: z.string().catch(''), +const groupSchema = v.object({ + avatar: v.fallback(v.string(), ''), + avatar_static: v.fallback(v.string(), ''), created_at: z.string().datetime().catch(new Date().toUTCString()), - display_name: z.string().catch(''), - domain: z.string().catch(''), + display_name: v.fallback(v.string(), ''), + domain: v.fallback(v.string(), ''), emojis: filteredArray(customEmojiSchema), - header: z.string().catch(''), - header_static: z.string().catch(''), + header: v.fallback(v.string(), ''), + header_static: v.fallback(v.string(), ''), id: z.coerce.string(), - locked: z.boolean().catch(false), - membership_required: z.boolean().catch(false), - members_count: z.number().catch(0), - owner: z.object({ id: z.string() }).nullable().catch(null), + locked: v.fallback(v.boolean(), false), + membership_required: v.fallback(v.boolean(), false), + members_count: v.fallback(v.number(), 0), + owner: v.object({ id: z.string() }).nullable().catch(null), note: z.string().transform(note => note === '

' ? '' : note).catch(''), - relationship: groupRelationshipSchema.nullable().catch(null), // Dummy field to be overwritten later + relationship: v.fallback(v.nullable(groupRelationshipSchema), null), // Dummy field to be overwritten later statuses_visibility: z.string().catch('public'), - uri: z.string().catch(''), - url: z.string().catch(''), + uri: v.fallback(v.string(), ''), + url: v.fallback(v.string(), ''), - avatar_description: z.string().catch(''), - header_description: z.string().catch(''), + avatar_description: v.fallback(v.string(), ''), + header_description: v.fallback(v.string(), ''), }); -type Group = z.infer; +type Group = v.InferOutput; export { groupSchema, type Group }; diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts index 52bec49e5..b6950f262 100644 --- a/packages/pl-api/lib/entities/instance.ts +++ b/packages/pl-api/lib/entities/instance.ts @@ -1,5 +1,5 @@ /* eslint sort-keys: "error" */ -import z from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; import { ruleSchema } from './rule'; @@ -108,8 +108,8 @@ const fixVersion = (version: string) => { }; const configurationSchema = coerceObject({ - accounts: z.object({ - allow_custom_css: z.boolean(), + accounts: v.object({ + allow_custom_css: v.boolean(), max_featured_tags: z.number().int(), max_profile_fields: z.number().int(), }).nullable().catch(null), @@ -136,7 +136,7 @@ const configurationSchema = coerceObject({ min_expiration: z.number().catch(300), }), reactions: coerceObject({ - max_reactions: z.number().catch(0), + max_reactions: v.fallback(v.number(), 0), }), statuses: coerceObject({ characters_reserved_per_url: z.number().optional().catch(undefined), @@ -145,13 +145,13 @@ const configurationSchema = coerceObject({ }), translation: coerceObject({ - enabled: z.boolean().catch(false), + enabled: v.fallback(v.boolean(), false), }), urls: coerceObject({ streaming: z.string().url().optional().catch(undefined), }), vapid: coerceObject({ - public_key: z.string().catch(''), + public_key: v.fallback(v.string(), ''), }), }); @@ -162,25 +162,25 @@ const contactSchema = coerceObject({ const pleromaSchema = coerceObject({ metadata: coerceObject({ - account_activation_required: z.boolean().catch(false), - birthday_min_age: z.number().catch(0), - birthday_required: z.boolean().catch(false), + account_activation_required: v.fallback(v.boolean(), false), + birthday_min_age: v.fallback(v.number(), 0), + birthday_required: v.fallback(v.boolean(), false), description_limit: z.number().catch(1500), - features: z.string().array().catch([]), + features: v.fallback(v.array(v.string()), []), federation: coerceObject({ - enabled: z.boolean().catch(true), // Assume true unless explicitly false + enabled: v.fallback(v.boolean(), true), // Assume true unless explicitly false mrf_policies: z.string().array().optional().catch(undefined), mrf_simple: coerceObject({ - accept: z.string().array().catch([]), - avatar_removal: z.string().array().catch([]), - banner_removal: z.string().array().catch([]), - federated_timeline_removal: z.string().array().catch([]), - followers_only: z.string().array().catch([]), - media_nsfw: z.string().array().catch([]), - media_removal: z.string().array().catch([]), - reject: z.string().array().catch([]), - reject_deletes: z.string().array().catch([]), - report_removal: z.string().array().catch([]), + accept: v.fallback(v.array(v.string()), []), + avatar_removal: v.fallback(v.array(v.string()), []), + banner_removal: v.fallback(v.array(v.string()), []), + federated_timeline_removal: v.fallback(v.array(v.string()), []), + followers_only: v.fallback(v.array(v.string()), []), + media_nsfw: v.fallback(v.array(v.string()), []), + media_removal: v.fallback(v.array(v.string()), []), + reject: v.fallback(v.array(v.string()), []), + reject_deletes: v.fallback(v.array(v.string()), []), + report_removal: v.fallback(v.array(v.string()), []), }), }), fields_limits: coerceObject({ @@ -189,50 +189,50 @@ const pleromaSchema = coerceObject({ value_length: z.number().nonnegative().catch(2047), }), markup: coerceObject({ - allow_headings: z.boolean().catch(false), - allow_inline_images: z.boolean().catch(false), + allow_headings: v.fallback(v.boolean(), false), + allow_inline_images: v.fallback(v.boolean(), false), }), migration_cooldown_period: z.number().optional().catch(undefined), multitenancy: coerceObject({ domains: z .array( - z.object({ + v.object({ domain: z.coerce.string(), - id: z.string(), - public: z.boolean().catch(false), + id: v.string(), + public: v.fallback(v.boolean(), false), }), ) .optional(), - enabled: z.boolean().catch(false), + enabled: v.fallback(v.boolean(), false), }), post_formats: z.string().array().optional().catch(undefined), restrict_unauthenticated: coerceObject({ activities: coerceObject({ - local: z.boolean().catch(false), - remote: z.boolean().catch(false), + local: v.fallback(v.boolean(), false), + remote: v.fallback(v.boolean(), false), }), profiles: coerceObject({ - local: z.boolean().catch(false), - remote: z.boolean().catch(false), + local: v.fallback(v.boolean(), false), + remote: v.fallback(v.boolean(), false), }), timelines: coerceObject({ - bubble: z.boolean().catch(false), - federated: z.boolean().catch(false), - local: z.boolean().catch(false), + bubble: v.fallback(v.boolean(), false), + federated: v.fallback(v.boolean(), false), + local: v.fallback(v.boolean(), false), }), }), translation: coerceObject({ - allow_remote: z.boolean().catch(true), - allow_unauthenticated: z.boolean().catch(false), + allow_remote: v.fallback(v.boolean(), true), + allow_unauthenticated: v.fallback(v.boolean(), false), source_languages: z.string().array().optional().catch(undefined), target_languages: z.string().array().optional().catch(undefined), }), }), - oauth_consumer_strategies: z.string().array().catch([]), + oauth_consumer_strategies: v.fallback(v.array(v.string()), []), stats: coerceObject({ mau: z.number().optional().catch(undefined), }), - vapid_public_key: z.string().catch(''), + vapid_public_key: v.fallback(v.string(), ''), }); const pleromaPollLimitsSchema = coerceObject({ @@ -243,9 +243,9 @@ const pleromaPollLimitsSchema = coerceObject({ }); const registrations = coerceObject({ - approval_required: z.boolean().catch(false), - enabled: z.boolean().catch(false), - message: z.string().optional().catch(undefined), + approval_required: v.fallback(v.boolean(), false), + enabled: v.fallback(v.boolean(), false), + message: v.fallback(v.optional(v.string()), undefined), }); const statsSchema = coerceObject({ @@ -255,38 +255,38 @@ const statsSchema = coerceObject({ }); const thumbnailSchema = coerceObject({ - url: z.string().catch(''), + url: v.fallback(v.string(), ''), }); const usageSchema = coerceObject({ users: coerceObject({ - active_month: z.number().catch(0), + active_month: v.fallback(v.number(), 0), }), }); const instanceV1Schema = coerceObject({ - account_domain: z.string().catch(''), - approval_required: z.boolean().catch(false), + account_domain: v.fallback(v.string(), ''), + approval_required: v.fallback(v.boolean(), false), configuration: configurationSchema, contact_account: accountSchema.optional().catch(undefined), - description: z.string().catch(''), + description: v.fallback(v.string(), ''), description_limit: z.number().catch(1500), email: z.string().email().catch(''), - feature_quote: z.boolean().catch(false), - fedibird_capabilities: z.array(z.string()).catch([]), - languages: z.string().array().catch([]), + feature_quote: v.fallback(v.boolean(), false), + fedibird_capabilities: z.array(v.string()).catch([]), + languages: v.fallback(v.array(v.string()), []), max_media_attachments: z.number().optional().catch(undefined), max_toot_chars: z.number().optional().catch(undefined), pleroma: pleromaSchema, poll_limits: pleromaPollLimitsSchema, - registrations: z.boolean().catch(false), + registrations: v.fallback(v.boolean(), false), rules: filteredArray(ruleSchema), - short_description: z.string().catch(''), + short_description: v.fallback(v.string(), ''), stats: statsSchema, - thumbnail: z.string().catch(''), - title: z.string().catch(''), + thumbnail: v.fallback(v.string(), ''), + title: v.fallback(v.string(), ''), upload_limit: z.number().optional().catch(undefined), - uri: z.string().catch(''), + uri: v.fallback(v.string(), ''), urls: coerceObject({ streaming_api: z.string().url().optional().catch(undefined), }), @@ -307,21 +307,21 @@ const instanceSchema = z.preprocess((data: any) => { return instanceV1ToV2({ ...data, api_versions: apiVersions }); }, coerceObject({ - account_domain: z.string().catch(''), + account_domain: v.fallback(v.string(), ''), api_versions: z.record(z.number()).catch({}), configuration: configurationSchema, contact: contactSchema, - description: z.string().catch(''), - domain: z.string().catch(''), - feature_quote: z.boolean().catch(false), - fedibird_capabilities: z.array(z.string()).catch([]), - languages: z.string().array().catch([]), + description: v.fallback(v.string(), ''), + domain: v.fallback(v.string(), ''), + feature_quote: v.fallback(v.boolean(), false), + fedibird_capabilities: z.array(v.string()).catch([]), + languages: v.fallback(v.array(v.string()), []), pleroma: pleromaSchema, registrations: registrations, rules: filteredArray(ruleSchema), stats: statsSchema, thumbnail: thumbnailSchema, - title: z.string().catch(''), + title: v.fallback(v.string(), ''), usage: usageSchema, version: z.string().catch('0.0.0'), }).transform((instance) => { @@ -333,6 +333,6 @@ const instanceSchema = z.preprocess((data: any) => { }; })); -type Instance = z.infer; +type Instance = v.InferOutput; export { instanceSchema, type Instance }; diff --git a/packages/pl-api/lib/entities/interaction-policy.ts b/packages/pl-api/lib/entities/interaction-policy.ts index d53cfbaf6..975743c92 100644 --- a/packages/pl-api/lib/entities/interaction-policy.ts +++ b/packages/pl-api/lib/entities/interaction-policy.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { coerceObject } from './utils'; @@ -16,7 +16,7 @@ const interactionPolicySchema = coerceObject({ can_reply: interactionPolicyRuleSchema, }); -type InteractionPolicy = z.infer; +type InteractionPolicy = v.InferOutput; const interactionPoliciesSchema = coerceObject({ public: interactionPolicySchema, @@ -25,7 +25,7 @@ const interactionPoliciesSchema = coerceObject({ direct: interactionPolicySchema, }); -type InteractionPolicies = z.infer; +type InteractionPolicies = v.InferOutput; export { interactionPolicySchema, interactionPoliciesSchema, type InteractionPolicy, type InteractionPolicies }; diff --git a/packages/pl-api/lib/entities/interaction-request.ts b/packages/pl-api/lib/entities/interaction-request.ts index 0b679ac2b..92b70a1f1 100644 --- a/packages/pl-api/lib/entities/interaction-request.ts +++ b/packages/pl-api/lib/entities/interaction-request.ts @@ -1,21 +1,21 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; import { statusSchema } from './status'; /** @see {@link https://docs.gotosocial.org/en/latest/api/swagger.yaml#/definitions/interactionRequest} */ -const interactionRequestSchema = z.object({ +const interactionRequestSchema = v.object({ accepted_at: z.string().datetime().nullable().catch(null), account: accountSchema, created_at: z.string().datetime(), - id: z.string(), + id: v.string(), rejected_at: z.string().datetime().nullable().catch(null), - reply: statusSchema.nullable().catch(null), - status: statusSchema.nullable().catch(null), + reply: v.fallback(v.nullable(statusSchema), null), + status: v.fallback(v.nullable(statusSchema), null), type: z.enum(['favourite', 'reply', 'reblog']), - uri: z.string().nullable().catch(null), + uri: v.fallback(v.nullable(v.string()), null), }); -type InteractionRequest = z.infer; +type InteractionRequest = v.InferOutput; export { interactionRequestSchema, type InteractionRequest }; diff --git a/packages/pl-api/lib/entities/list.ts b/packages/pl-api/lib/entities/list.ts index c1d5d1b7c..2a0cac1c4 100644 --- a/packages/pl-api/lib/entities/list.ts +++ b/packages/pl-api/lib/entities/list.ts @@ -1,13 +1,13 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/List/} */ -const listSchema = z.object({ +const listSchema = v.object({ id: z.coerce.string(), - title: z.string(), - replies_policy: z.string().optional().catch(undefined), - exclusive: z.boolean().optional().catch(undefined), + title: v.string(), + replies_policy: v.fallback(v.optional(v.string()), undefined), + exclusive: v.fallback(v.optional(v.boolean()), undefined), }); -type List = z.infer; +type List = v.InferOutput; export { listSchema, type List }; diff --git a/packages/pl-api/lib/entities/location.ts b/packages/pl-api/lib/entities/location.ts index 0e5edcb1d..b9391c444 100644 --- a/packages/pl-api/lib/entities/location.ts +++ b/packages/pl-api/lib/entities/location.ts @@ -1,23 +1,23 @@ -import { z } from 'zod'; +import * as v from 'valibot'; -const locationSchema = z.object({ +const locationSchema = v.object({ url: z.string().url().catch(''), - description: z.string().catch(''), - country: z.string().catch(''), - locality: z.string().catch(''), - region: z.string().catch(''), - postal_code: z.string().catch(''), - street: z.string().catch(''), - origin_id: z.string().catch(''), - origin_provider: z.string().catch(''), - type: z.string().catch(''), - timezone: z.string().catch(''), - geom: z.object({ + description: v.fallback(v.string(), ''), + country: v.fallback(v.string(), ''), + locality: v.fallback(v.string(), ''), + region: v.fallback(v.string(), ''), + postal_code: v.fallback(v.string(), ''), + street: v.fallback(v.string(), ''), + origin_id: v.fallback(v.string(), ''), + origin_provider: v.fallback(v.string(), ''), + type: v.fallback(v.string(), ''), + timezone: v.fallback(v.string(), ''), + geom: v.object({ coordinates: z.tuple([z.number(), z.number()]).nullable().catch(null), - srid: z.string().catch(''), + srid: v.fallback(v.string(), ''), }).nullable().catch(null), }); -type Location = z.infer; +type Location = v.InferOutput; export { locationSchema, type Location }; diff --git a/packages/pl-api/lib/entities/marker.ts b/packages/pl-api/lib/entities/marker.ts index b4b355c79..597d88b12 100644 --- a/packages/pl-api/lib/entities/marker.ts +++ b/packages/pl-api/lib/entities/marker.ts @@ -1,23 +1,23 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { dateSchema } from './utils'; const markerSchema = z.preprocess((marker: any) => marker ? ({ unread_count: marker.pleroma?.unread_count, ...marker, -}) : null, z.object({ - last_read_id: z.string(), +}) : null, v.object({ + last_read_id: v.string(), version: z.number().int(), updated_at: dateSchema, unread_count: z.number().int().optional().catch(undefined), })); /** @see {@link https://docs.joinmastodon.org/entities/Marker/} */ -type Marker = z.infer; +type Marker = v.InferOutput; const markersSchema = z.record(markerSchema); -type Markers = z.infer; +type Markers = v.InferOutput; export { markerSchema, diff --git a/packages/pl-api/lib/entities/media-attachment.ts b/packages/pl-api/lib/entities/media-attachment.ts index 2017f129e..778e69eac 100644 --- a/packages/pl-api/lib/entities/media-attachment.ts +++ b/packages/pl-api/lib/entities/media-attachment.ts @@ -1,5 +1,5 @@ import { isBlurhashValid } from 'blurhash'; -import { z } from 'zod'; +import * as v from 'valibot'; import { mimeSchema } from './utils'; @@ -14,31 +14,31 @@ const blurhashSchema = z.string().superRefine((value, ctx) => { } }); -const baseAttachmentSchema = z.object({ - id: z.string(), - type: z.string(), +const baseAttachmentSchema = v.object({ + id: v.string(), + type: v.string(), url: z.string().url().catch(''), preview_url: z.string().url().catch(''), remote_url: z.string().url().nullable().catch(null), - description: z.string().catch(''), - blurhash: blurhashSchema.nullable().catch(null), + description: v.fallback(v.string(), ''), + blurhash: v.fallback(v.nullable(blurhashSchema), null), - mime_type: mimeSchema.nullable().catch(null), + mime_type: v.fallback(v.nullable(mimeSchema), null), }); -const imageMetaSchema = z.object({ +const imageMetaSchema = v.object({ width: z.number(), height: z.number(), size: z.string().regex(/\d+x\d+$/).nullable().catch(null), - aspect: z.number().nullable().catch(null), + aspect: v.fallback(v.nullable(v.number()), null), }); const imageAttachmentSchema = baseAttachmentSchema.extend({ type: z.literal('image'), - meta: z.object({ + meta: v.object({ original: imageMetaSchema.optional().catch(undefined), small: imageMetaSchema.optional().catch(undefined), - focus: z.object({ + focus: v.object({ x: z.number().min(-1).max(1), y: z.number().min(-1).max(1), }).optional().catch(undefined), @@ -47,7 +47,7 @@ const imageAttachmentSchema = baseAttachmentSchema.extend({ const videoAttachmentSchema = baseAttachmentSchema.extend({ type: z.literal('video'), - meta: z.object({ + meta: v.object({ duration: z.number().optional().catch(undefined), original: imageMetaSchema.extend({ frame_rate: z.string().regex(/\d+\/\d+$/).nullable().catch(null), @@ -60,7 +60,7 @@ const videoAttachmentSchema = baseAttachmentSchema.extend({ const gifvAttachmentSchema = baseAttachmentSchema.extend({ type: z.literal('gifv'), - meta: z.object({ + meta: v.object({ duration: z.number().optional().catch(undefined), original: imageMetaSchema.optional().catch(undefined), }).catch({}), @@ -68,15 +68,15 @@ const gifvAttachmentSchema = baseAttachmentSchema.extend({ const audioAttachmentSchema = baseAttachmentSchema.extend({ type: z.literal('audio'), - meta: z.object({ + meta: v.object({ duration: z.number().optional().catch(undefined), - colors: z.object({ - background: z.string().optional().catch(undefined), - foreground: z.string().optional().catch(undefined), - accent: z.string().optional().catch(undefined), + colors: v.object({ + background: v.fallback(v.optional(v.string()), undefined), + foreground: v.fallback(v.optional(v.string()), undefined), + accent: v.fallback(v.optional(v.string()), undefined), duration: z.number().optional().catch(undefined), }).optional().catch(undefined), - original: z.object({ + original: v.object({ duration: z.number().optional().catch(undefined), bitrate: z.number().nonnegative().optional().catch(undefined), }).optional().catch(undefined), @@ -104,6 +104,6 @@ const mediaAttachmentSchema = z.preprocess((data: any) => { unknownAttachmentSchema, ])); -type MediaAttachment = z.infer; +type MediaAttachment = v.InferOutput; export { blurhashSchema, mediaAttachmentSchema, type MediaAttachment }; diff --git a/packages/pl-api/lib/entities/mention.ts b/packages/pl-api/lib/entities/mention.ts index 81f1f7366..99a933d42 100644 --- a/packages/pl-api/lib/entities/mention.ts +++ b/packages/pl-api/lib/entities/mention.ts @@ -1,11 +1,11 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/Status/#Mention} */ -const mentionSchema = z.object({ - id: z.string(), - username: z.string().catch(''), +const mentionSchema = v.object({ + id: v.string(), + username: v.fallback(v.string(), ''), url: z.string().url().catch(''), - acct: z.string(), + acct: v.string(), }).transform((mention) => { if (!mention.username) { mention.username = mention.acct.split('@')[0]; @@ -14,6 +14,6 @@ const mentionSchema = z.object({ return mention; }); -type Mention = z.infer; +type Mention = v.InferOutput; export { mentionSchema, type Mention }; diff --git a/packages/pl-api/lib/entities/notification-policy.ts b/packages/pl-api/lib/entities/notification-policy.ts index 769af206e..a4400cf81 100644 --- a/packages/pl-api/lib/entities/notification-policy.ts +++ b/packages/pl-api/lib/entities/notification-policy.ts @@ -1,17 +1,17 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/NotificationPolicy} */ -const notificationPolicySchema = z.object({ - filter_not_following: z.boolean(), - filter_not_followers: z.boolean(), - filter_new_accounts: z.boolean(), - filter_private_mentions: z.boolean(), - summary: z.object({ +const notificationPolicySchema = v.object({ + filter_not_following: v.boolean(), + filter_not_followers: v.boolean(), + filter_new_accounts: v.boolean(), + filter_private_mentions: v.boolean(), + summary: v.object({ pending_requests_count: z.number().int(), pending_notifications_count: z.number().int(), }), }); -type NotificationPolicy = z.infer; +type NotificationPolicy = v.InferOutput; export { notificationPolicySchema, type NotificationPolicy }; diff --git a/packages/pl-api/lib/entities/notification-request.ts b/packages/pl-api/lib/entities/notification-request.ts index 33066f773..51ea8c841 100644 --- a/packages/pl-api/lib/entities/notification-request.ts +++ b/packages/pl-api/lib/entities/notification-request.ts @@ -1,12 +1,12 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { dateSchema } from './utils'; import { accountSchema, statusSchema } from '.'; /** @see {@link https://docs.joinmastodon.org/entities/NotificationRequest} */ -const notificationRequestSchema = z.object({ - id: z.string(), +const notificationRequestSchema = v.object({ + id: v.string(), created_at: dateSchema, updated_at: dateSchema, account: accountSchema, @@ -14,6 +14,6 @@ const notificationRequestSchema = z.object({ last_status: statusSchema.optional().catch(undefined), }); -type NotificationRequest = z.infer; +type NotificationRequest = v.InferOutput; export { notificationRequestSchema, type NotificationRequest }; diff --git a/packages/pl-api/lib/entities/notification.ts b/packages/pl-api/lib/entities/notification.ts index 2942a118f..633ad6e8b 100644 --- a/packages/pl-api/lib/entities/notification.ts +++ b/packages/pl-api/lib/entities/notification.ts @@ -1,5 +1,5 @@ import pick from 'lodash.pick'; -import { z } from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; import { accountWarningSchema } from './account-warning'; @@ -9,15 +9,15 @@ import { reportSchema } from './report'; import { statusSchema } from './status'; import { dateSchema } from './utils'; -const baseNotificationSchema = z.object({ +const baseNotificationSchema = v.object({ account: accountSchema, created_at: dateSchema, - id: z.string(), - group_key: z.string(), - type: z.string(), + id: v.string(), + group_key: v.string(), + type: v.string(), - is_muted: z.boolean().optional().catch(undefined), - is_seen: z.boolean().optional().catch(undefined), + is_muted: v.fallback(v.optional(v.boolean()), undefined), + is_seen: v.fallback(v.optional(v.boolean()), undefined), }); const accountNotificationSchema = baseNotificationSchema.extend({ @@ -57,8 +57,8 @@ const moveNotificationSchema = baseNotificationSchema.extend({ const emojiReactionNotificationSchema = baseNotificationSchema.extend({ type: z.literal('emoji_reaction'), - emoji: z.string(), - emoji_url: z.string().nullable().catch(null), + emoji: v.string(), + emoji_url: v.fallback(v.nullable(v.string()), null), status: statusSchema, }); @@ -70,7 +70,7 @@ const chatMentionNotificationSchema = baseNotificationSchema.extend({ const eventParticipationRequestNotificationSchema = baseNotificationSchema.extend({ type: z.enum(['participation_accepted', 'participation_request']), status: statusSchema, - participation_message: z.string().nullable().catch(null), + participation_message: v.fallback(v.nullable(v.string()), null), }); /** @see {@link https://docs.joinmastodon.org/entities/Notification/} */ @@ -94,7 +94,7 @@ const notificationSchema: z.ZodType = z.preprocess((notification: eventParticipationRequestNotificationSchema, ])) as any; -type Notification = z.infer< +type Notification = v.InferOutput< | typeof accountNotificationSchema | typeof mentionNotificationSchema | typeof statusNotificationSchema diff --git a/packages/pl-api/lib/entities/oauth-token.ts b/packages/pl-api/lib/entities/oauth-token.ts index 2c5afdd7e..cf9ea0931 100644 --- a/packages/pl-api/lib/entities/oauth-token.ts +++ b/packages/pl-api/lib/entities/oauth-token.ts @@ -1,15 +1,15 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apioauth_tokens} */ const oauthTokenSchema = z.preprocess((token: any) => ({ ...token, valid_until: token?.valid_until?.padEnd(27, 'Z'), -}), z.object({ - app_name: z.string(), +}), v.object({ + app_name: v.string(), id: z.number(), valid_until: z.string().datetime({ offset: true }), })); -type OauthToken = z.infer; +type OauthToken = v.InferOutput; export { oauthTokenSchema, type OauthToken }; diff --git a/packages/pl-api/lib/entities/poll.ts b/packages/pl-api/lib/entities/poll.ts index bfaf627be..923dc3a9a 100644 --- a/packages/pl-api/lib/entities/poll.ts +++ b/packages/pl-api/lib/entities/poll.ts @@ -1,32 +1,32 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { customEmojiSchema } from './custom-emoji'; import { filteredArray } from './utils'; -const pollOptionSchema = z.object({ - title: z.string().catch(''), - votes_count: z.number().catch(0), +const pollOptionSchema = v.object({ + title: v.fallback(v.string(), ''), + votes_count: v.fallback(v.number(), 0), - title_map: z.record(z.string()).nullable().catch(null), + title_map: z.record(v.string()).nullable().catch(null), }); /** @see {@link https://docs.joinmastodon.org/entities/Poll/} */ -const pollSchema = z.object({ +const pollSchema = v.object({ emojis: filteredArray(customEmojiSchema), - expired: z.boolean().catch(false), + expired: v.fallback(v.boolean(), false), expires_at: z.string().datetime().nullable().catch(null), - id: z.string(), - multiple: z.boolean().catch(false), + id: v.string(), + multiple: v.fallback(v.boolean(), false), options: z.array(pollOptionSchema).min(2), - voters_count: z.number().catch(0), - votes_count: z.number().catch(0), + voters_count: v.fallback(v.number(), 0), + votes_count: v.fallback(v.number(), 0), own_votes: z.array(z.number()).nonempty().nullable().catch(null), - voted: z.boolean().catch(false), + voted: v.fallback(v.boolean(), false), - non_anonymous: z.boolean().catch(false), + non_anonymous: v.fallback(v.boolean(), false), }); -type Poll = z.infer; +type Poll = v.InferOutput; type PollOption = Poll['options'][number]; export { pollSchema, type Poll, type PollOption }; diff --git a/packages/pl-api/lib/entities/preview-card.ts b/packages/pl-api/lib/entities/preview-card.ts index 1b03eba06..ad88f1f7f 100644 --- a/packages/pl-api/lib/entities/preview-card.ts +++ b/packages/pl-api/lib/entities/preview-card.ts @@ -1,24 +1,24 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/} */ -const previewCardSchema = z.object({ - author_name: z.string().catch(''), +const previewCardSchema = v.object({ + author_name: v.fallback(v.string(), ''), author_url: z.string().url().catch(''), - blurhash: z.string().nullable().catch(null), - description: z.string().catch(''), + blurhash: v.fallback(v.nullable(v.string()), null), + description: v.fallback(v.string(), ''), embed_url: z.string().url().catch(''), - height: z.number().catch(0), - html: z.string().catch(''), - image: z.string().nullable().catch(null), - image_description: z.string().catch(''), - provider_name: z.string().catch(''), + height: v.fallback(v.number(), 0), + html: v.fallback(v.string(), ''), + image: v.fallback(v.nullable(v.string()), null), + image_description: v.fallback(v.string(), ''), + provider_name: v.fallback(v.string(), ''), provider_url: z.string().url().catch(''), - title: z.string().catch(''), + title: v.fallback(v.string(), ''), type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'), url: z.string().url(), - width: z.number().catch(0), + width: v.fallback(v.number(), 0), }); -type PreviewCard = z.infer; +type PreviewCard = v.InferOutput; export { previewCardSchema, type PreviewCard }; diff --git a/packages/pl-api/lib/entities/relationship-severance-event.ts b/packages/pl-api/lib/entities/relationship-severance-event.ts index 22b33c084..42970bf76 100644 --- a/packages/pl-api/lib/entities/relationship-severance-event.ts +++ b/packages/pl-api/lib/entities/relationship-severance-event.ts @@ -1,16 +1,16 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { dateSchema } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/RelationshipSeveranceEvent/} */ -const relationshipSeveranceEventSchema = z.object({ - id: z.string(), +const relationshipSeveranceEventSchema = v.object({ + id: v.string(), type: z.enum(['domain_block', 'user_domain_block', 'account_suspension']), - purged: z.string(), + purged: v.string(), relationships_count: z.number().optional().catch(undefined), created_at: dateSchema, }); -type RelationshipSeveranceEvent = z.infer; +type RelationshipSeveranceEvent = v.InferOutput; export { relationshipSeveranceEventSchema, type RelationshipSeveranceEvent }; diff --git a/packages/pl-api/lib/entities/relationship.ts b/packages/pl-api/lib/entities/relationship.ts index eae07bad4..e2fc728af 100644 --- a/packages/pl-api/lib/entities/relationship.ts +++ b/packages/pl-api/lib/entities/relationship.ts @@ -1,22 +1,22 @@ -import z from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/Relationship/} */ -const relationshipSchema = z.object({ - blocked_by: z.boolean().catch(false), - blocking: z.boolean().catch(false), - domain_blocking: z.boolean().catch(false), - endorsed: z.boolean().catch(false), - followed_by: z.boolean().catch(false), - following: z.boolean().catch(false), - id: z.string(), - muting: z.boolean().catch(false), - muting_notifications: z.boolean().catch(false), - note: z.string().catch(''), - notifying: z.boolean().catch(false), - requested: z.boolean().catch(false), - showing_reblogs: z.boolean().catch(false), +const relationshipSchema = v.object({ + blocked_by: v.fallback(v.boolean(), false), + blocking: v.fallback(v.boolean(), false), + domain_blocking: v.fallback(v.boolean(), false), + endorsed: v.fallback(v.boolean(), false), + followed_by: v.fallback(v.boolean(), false), + following: v.fallback(v.boolean(), false), + id: v.string(), + muting: v.fallback(v.boolean(), false), + muting_notifications: v.fallback(v.boolean(), false), + note: v.fallback(v.string(), ''), + notifying: v.fallback(v.boolean(), false), + requested: v.fallback(v.boolean(), false), + showing_reblogs: v.fallback(v.boolean(), false), }); -type Relationship = z.infer; +type Relationship = v.InferOutput; export { relationshipSchema, type Relationship }; diff --git a/packages/pl-api/lib/entities/report.ts b/packages/pl-api/lib/entities/report.ts index f9573a14d..96b7c9d30 100644 --- a/packages/pl-api/lib/entities/report.ts +++ b/packages/pl-api/lib/entities/report.ts @@ -1,22 +1,22 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; import { dateSchema } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/Report/} */ -const reportSchema = z.object({ - id: z.string(), - action_taken: z.boolean().optional().catch(undefined), - action_taken_at: dateSchema.nullable().catch(null), - category: z.string().optional().catch(undefined), - comment: z.string().optional().catch(undefined), - forwarded: z.boolean().optional().catch(undefined), +const reportSchema = v.object({ + id: v.string(), + action_taken: v.fallback(v.optional(v.boolean()), undefined), + action_taken_at: v.fallback(v.nullable(dateSchema), null), + category: v.fallback(v.optional(v.string()), undefined), + comment: v.fallback(v.optional(v.string()), undefined), + forwarded: v.fallback(v.optional(v.boolean()), undefined), created_at: dateSchema.optional().catch(undefined), - status_ids: z.array(z.string()).nullable().catch(null), - rule_ids: z.array(z.string()).nullable().catch(null), - target_account: accountSchema.nullable().catch(null), + status_ids: z.array(v.string()).nullable().catch(null), + rule_ids: z.array(v.string()).nullable().catch(null), + target_account: v.fallback(v.nullable(accountSchema), null), }); -type Report = z.infer; +type Report = v.InferOutput; export { reportSchema, type Report }; diff --git a/packages/pl-api/lib/entities/role.ts b/packages/pl-api/lib/entities/role.ts index 7a80eade0..173c6ff89 100644 --- a/packages/pl-api/lib/entities/role.ts +++ b/packages/pl-api/lib/entities/role.ts @@ -1,16 +1,16 @@ -import { z } from 'zod'; +import * as v from 'valibot'; const hexSchema = z.string().regex(/^#[a-f0-9]{6}$/i); -const roleSchema = z.object({ - id: z.string().catch(''), - name: z.string().catch(''), +const roleSchema = v.object({ + id: v.fallback(v.string(), ''), + name: v.fallback(v.string(), ''), color: hexSchema.catch(''), - permissions: z.string().catch(''), - highlighted: z.boolean().catch(true), + permissions: v.fallback(v.string(), ''), + highlighted: v.fallback(v.boolean(), true), }); -type Role = z.infer; +type Role = v.InferOutput; export { roleSchema, diff --git a/packages/pl-api/lib/entities/rule.ts b/packages/pl-api/lib/entities/rule.ts index b7393f453..998a160a5 100644 --- a/packages/pl-api/lib/entities/rule.ts +++ b/packages/pl-api/lib/entities/rule.ts @@ -1,9 +1,9 @@ -import { z } from 'zod'; +import * as v from 'valibot'; -const baseRuleSchema = z.object({ - id: z.string(), - text: z.string().catch(''), - hint: z.string().catch(''), +const baseRuleSchema = v.object({ + id: v.string(), + text: v.fallback(v.string(), ''), + hint: v.fallback(v.string(), ''), }); /** @see {@link https://docs.joinmastodon.org/entities/Rule/} */ @@ -12,6 +12,6 @@ const ruleSchema = z.preprocess((data: any) => ({ hint: data.hint || data.subtext, }), baseRuleSchema); -type Rule = z.infer; +type Rule = v.InferOutput; export { ruleSchema, type Rule }; diff --git a/packages/pl-api/lib/entities/scheduled-status.ts b/packages/pl-api/lib/entities/scheduled-status.ts index 8f1094191..93ecbf9f7 100644 --- a/packages/pl-api/lib/entities/scheduled-status.ts +++ b/packages/pl-api/lib/entities/scheduled-status.ts @@ -1,36 +1,36 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { mediaAttachmentSchema } from './media-attachment'; import { filteredArray } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/ScheduledStatus/} */ -const scheduledStatusSchema = z.object({ - id: z.string(), +const scheduledStatusSchema = v.object({ + id: v.string(), scheduled_at: z.string().datetime({ offset: true }), - params: z.object({ - text: z.string().nullable().catch(null), - poll: z.object({ - options: z.array(z.string()), + params: v.object({ + text: v.fallback(v.nullable(v.string()), null), + poll: v.object({ + options: z.array(v.string()), expires_in: z.coerce.string(), - multiple: z.boolean().optional().catch(undefined), - hide_totals: z.boolean().optional().catch(undefined), + multiple: v.fallback(v.optional(v.boolean()), undefined), + hide_totals: v.fallback(v.optional(v.boolean()), undefined), }).nullable().catch(null), - media_ids: z.array(z.string()).nullable().catch(null), + media_ids: z.array(v.string()).nullable().catch(null), sensitive: z.coerce.boolean().nullable().catch(null), - spoiler_text: z.string().nullable().catch(null), + spoiler_text: v.fallback(v.nullable(v.string()), null), visibility: z.string().catch('public'), - in_reply_to_id: z.string().nullable().catch(null), - language: z.string().nullable().catch(null), + in_reply_to_id: v.fallback(v.nullable(v.string()), null), + language: v.fallback(v.nullable(v.string()), null), application_id: z.number().int().nullable().catch(null), scheduled_at: z.string().datetime({ offset: true }).nullable().catch(null), - idempotency: z.string().nullable().catch(null), - with_rate_limit: z.boolean().catch(false), + idempotency: v.fallback(v.nullable(v.string()), null), + with_rate_limit: v.fallback(v.boolean(), false), - expires_in: z.number().nullable().catch(null), + expires_in: v.fallback(v.nullable(v.number()), null), }), media_attachments: filteredArray(mediaAttachmentSchema), }); -type ScheduledStatus = z.infer; +type ScheduledStatus = v.InferOutput; export { scheduledStatusSchema, type ScheduledStatus }; diff --git a/packages/pl-api/lib/entities/scrobble.ts b/packages/pl-api/lib/entities/scrobble.ts index 92f926bfb..1dc838ee9 100644 --- a/packages/pl-api/lib/entities/scrobble.ts +++ b/packages/pl-api/lib/entities/scrobble.ts @@ -1,21 +1,21 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; const scrobbleSchema = z.preprocess((scrobble: any) => scrobble ? { external_link: scrobble.externalLink, ...scrobble, -} : null, z.object({ +} : null, v.object({ id: z.coerce.string(), account: accountSchema, created_at: z.string().datetime({ offset: true }), - title: z.string(), - artist: z.string().catch(''), - album: z.string().catch(''), - external_link: z.string().nullable().catch(null), - length: z.number().nullable().catch(null), + title: v.string(), + artist: v.fallback(v.string(), ''), + album: v.fallback(v.string(), ''), + external_link: v.fallback(v.nullable(v.string()), null), + length: v.fallback(v.nullable(v.number()), null), })); -type Scrobble = z.infer; +type Scrobble = v.InferOutput; export { scrobbleSchema, type Scrobble }; diff --git a/packages/pl-api/lib/entities/search.ts b/packages/pl-api/lib/entities/search.ts index bd32ced96..a8b0d4db6 100644 --- a/packages/pl-api/lib/entities/search.ts +++ b/packages/pl-api/lib/entities/search.ts @@ -1,17 +1,17 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { filteredArray } from './utils'; import { accountSchema, groupSchema, statusSchema, tagSchema } from '.'; /** @see {@link https://docs.joinmastodon.org/entities/Search} */ -const searchSchema = z.object({ +const searchSchema = v.object({ accounts: filteredArray(accountSchema), statuses: filteredArray(statusSchema), hashtags: filteredArray(tagSchema), groups: filteredArray(groupSchema), }); -type Search = z.infer; +type Search = v.InferOutput; export { searchSchema, type Search }; diff --git a/packages/pl-api/lib/entities/status-edit.ts b/packages/pl-api/lib/entities/status-edit.ts index 5e56f42c1..0820e7911 100644 --- a/packages/pl-api/lib/entities/status-edit.ts +++ b/packages/pl-api/lib/entities/status-edit.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; import { customEmojiSchema } from './custom-emoji'; @@ -6,21 +6,21 @@ import { mediaAttachmentSchema } from './media-attachment'; import { dateSchema, filteredArray } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/StatusEdit/} */ -const statusEditSchema = z.object({ - content: z.string().catch(''), - spoiler_text: z.string().catch(''), +const statusEditSchema = v.object({ + content: v.fallback(v.string(), ''), + spoiler_text: v.fallback(v.string(), ''), sensitive: z.coerce.boolean(), created_at: dateSchema, account: accountSchema, - poll: z.object({ - options: z.array(z.object({ - title: z.string(), + poll: v.object({ + options: z.array(v.object({ + title: v.string(), })), }).nullable().catch(null), media_attachments: filteredArray(mediaAttachmentSchema), emojis: filteredArray(customEmojiSchema), }); -type StatusEdit = z.infer; +type StatusEdit = v.InferOutput; export { statusEditSchema, type StatusEdit }; diff --git a/packages/pl-api/lib/entities/status-source.ts b/packages/pl-api/lib/entities/status-source.ts index bab98572a..d811d3092 100644 --- a/packages/pl-api/lib/entities/status-source.ts +++ b/packages/pl-api/lib/entities/status-source.ts @@ -1,20 +1,20 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { locationSchema } from './location'; /** @see {@link https://docs.joinmastodon.org/entities/StatusSource/} */ -const statusSourceSchema = z.object({ - id: z.string(), - text: z.string().catch(''), - spoiler_text: z.string().catch(''), +const statusSourceSchema = v.object({ + id: v.string(), + text: v.fallback(v.string(), ''), + spoiler_text: v.fallback(v.string(), ''), content_type: z.string().catch('text/plain'), - location: locationSchema.nullable().catch(null), + location: v.fallback(v.nullable(locationSchema), null), - text_map: z.record(z.string()).nullable().catch(null), - spoiler_text_map: z.record(z.string()).nullable().catch(null), + text_map: z.record(v.string()).nullable().catch(null), + spoiler_text_map: z.record(v.string()).nullable().catch(null), }); -type StatusSource = z.infer; +type StatusSource = v.InferOutput; export { statusSourceSchema, type StatusSource }; diff --git a/packages/pl-api/lib/entities/status.ts b/packages/pl-api/lib/entities/status.ts index 4512e8062..4abbed09f 100644 --- a/packages/pl-api/lib/entities/status.ts +++ b/packages/pl-api/lib/entities/status.ts @@ -1,5 +1,5 @@ import pick from 'lodash.pick'; -import { z } from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; import { customEmojiSchema } from './custom-emoji'; @@ -15,54 +15,54 @@ import { tagSchema } from './tag'; import { translationSchema } from './translation'; import { dateSchema, filteredArray } from './utils'; -const statusEventSchema = z.object({ - name: z.string().catch(''), +const statusEventSchema = v.object({ + name: v.fallback(v.string(), ''), start_time: z.string().datetime().nullable().catch(null), end_time: z.string().datetime().nullable().catch(null), join_mode: z.enum(['free', 'restricted', 'invite']).nullable().catch(null), - participants_count: z.number().catch(0), - location: z.object({ - name: z.string().catch(''), + participants_count: v.fallback(v.number(), 0), + location: v.object({ + name: v.fallback(v.string(), ''), url: z.string().url().catch(''), - latitude: z.number().catch(0), - longitude: z.number().catch(0), - street: z.string().catch(''), - postal_code: z.string().catch(''), - locality: z.string().catch(''), - region: z.string().catch(''), - country: z.string().catch(''), + latitude: v.fallback(v.number(), 0), + longitude: v.fallback(v.number(), 0), + street: v.fallback(v.string(), ''), + postal_code: v.fallback(v.string(), ''), + locality: v.fallback(v.string(), ''), + region: v.fallback(v.string(), ''), + country: v.fallback(v.string(), ''), }).nullable().catch(null), join_state: z.enum(['pending', 'reject', 'accept']).nullable().catch(null), }); /** @see {@link https://docs.joinmastodon.org/entities/Status/} */ -const baseStatusSchema = z.object({ - id: z.string(), +const baseStatusSchema = v.object({ + id: v.string(), uri: z.string().url().catch(''), created_at: dateSchema, account: accountSchema, - content: z.string().catch(''), + content: v.fallback(v.string(), ''), visibility: z.string().catch('public'), sensitive: z.coerce.boolean(), - spoiler_text: z.string().catch(''), + spoiler_text: v.fallback(v.string(), ''), media_attachments: filteredArray(mediaAttachmentSchema), - application: z.object({ - name: z.string(), + application: v.object({ + name: v.string(), website: z.string().url().nullable().catch(null), }).nullable().catch(null), mentions: filteredArray(mentionSchema), tags: filteredArray(tagSchema), emojis: filteredArray(customEmojiSchema), - reblogs_count: z.number().catch(0), - favourites_count: z.number().catch(0), - replies_count: z.number().catch(0), + reblogs_count: v.fallback(v.number(), 0), + favourites_count: v.fallback(v.number(), 0), + replies_count: v.fallback(v.number(), 0), url: z.string().url().catch(''), - in_reply_to_id: z.string().nullable().catch(null), - in_reply_to_account_id: z.string().nullable().catch(null), - poll: pollSchema.nullable().catch(null), - card: previewCardSchema.nullable().catch(null), - language: z.string().nullable().catch(null), - text: z.string().nullable().catch(null), + in_reply_to_id: v.fallback(v.nullable(v.string()), null), + in_reply_to_account_id: v.fallback(v.nullable(v.string()), null), + poll: v.fallback(v.nullable(pollSchema), null), + card: v.fallback(v.nullable(previewCardSchema), null), + language: v.fallback(v.nullable(v.string()), null), + text: v.fallback(v.nullable(v.string()), null), edited_at: z.string().datetime().nullable().catch(null), favourited: z.coerce.boolean(), reblogged: z.coerce.boolean(), @@ -71,32 +71,32 @@ const baseStatusSchema = z.object({ pinned: z.coerce.boolean(), filtered: filteredArray(filterResultSchema), approval_status: z.enum(['pending', 'approval', 'rejected']).nullable().catch(null), - group: groupSchema.nullable().catch(null), + group: v.fallback(v.nullable(groupSchema), null), scheduled_at: z.null().catch(null), - quote_id: z.string().nullable().catch(null), - local: z.boolean().optional().catch(undefined), - conversation_id: z.string().optional().catch(undefined), - direct_conversation_id: z.string().optional().catch(undefined), - in_reply_to_account_acct: z.string().optional().catch(undefined), + quote_id: v.fallback(v.nullable(v.string()), null), + local: v.fallback(v.optional(v.boolean()), undefined), + conversation_id: v.fallback(v.optional(v.string()), undefined), + direct_conversation_id: v.fallback(v.optional(v.string()), undefined), + in_reply_to_account_acct: v.fallback(v.optional(v.string()), undefined), expires_at: z.string().datetime({ offset: true }).optional().catch(undefined), - thread_muted: z.boolean().optional().catch(undefined), + thread_muted: v.fallback(v.optional(v.boolean()), undefined), emoji_reactions: filteredArray(emojiReactionSchema), - parent_visible: z.boolean().optional().catch(undefined), + parent_visible: v.fallback(v.optional(v.boolean()), undefined), pinned_at: z.string().datetime({ offset: true }).nullable().catch(null), - quote_visible: z.boolean().optional().catch(undefined), - quote_url: z.string().optional().catch(undefined), - quotes_count: z.number().catch(0), - bookmark_folder: z.string().nullable().catch(null), + quote_visible: v.fallback(v.optional(v.boolean()), undefined), + quote_url: v.fallback(v.optional(v.string()), undefined), + quotes_count: v.fallback(v.number(), 0), + bookmark_folder: v.fallback(v.nullable(v.string()), null), - event: statusEventSchema.nullable().catch(null), + event: v.fallback(v.nullable(statusEventSchema), null), translation: translationSchema.nullable().or(z.literal(false)).catch(null), - content_map: z.record(z.string()).nullable().catch(null), - text_map: z.record(z.string()).nullable().catch(null), - spoiler_text_map: z.record(z.string()).nullable().catch(null), + content_map: z.record(v.string()).nullable().catch(null), + text_map: z.record(v.string()).nullable().catch(null), + spoiler_text_map: z.record(v.string()).nullable().catch(null), - dislikes_count: z.number().catch(0), + dislikes_count: v.fallback(v.number(), 0), disliked: z.coerce.boolean().catch(false), interaction_policy: interactionPolicySchema, @@ -141,13 +141,13 @@ const statusSchema: z.ZodType = z.preprocess(preprocess, baseStatusSchem })) as any; const statusWithoutAccountSchema = z.preprocess(preprocess, baseStatusSchema.omit({ account: true }).extend({ - account: accountSchema.nullable().catch(null), + account: v.fallback(v.nullable(accountSchema), null), reblog: z.lazy(() => statusSchema).nullable().catch(null), quote: z.lazy(() => statusSchema).nullable().catch(null), })); -type Status = z.infer & { +type Status = v.InferOutput & { reblog: Status | null; quote: Status | null; } diff --git a/packages/pl-api/lib/entities/streaming-event.ts b/packages/pl-api/lib/entities/streaming-event.ts index 970290c46..758fe2e31 100644 --- a/packages/pl-api/lib/entities/streaming-event.ts +++ b/packages/pl-api/lib/entities/streaming-event.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { announcementSchema } from './announcement'; import { announcementReactionSchema } from './announcement-reaction'; @@ -8,24 +8,24 @@ import { markersSchema } from './marker'; import { notificationSchema } from './notification'; import { statusSchema } from './status'; -const followRelationshipUpdateSchema = z.object({ +const followRelationshipUpdateSchema = v.object({ state: z.enum(['follow_pending', 'follow_accept', 'follow_reject']), - follower: z.object({ - id: z.string(), - follower_count: z.number().nullable().catch(null), - following_count: z.number().nullable().catch(null), + follower: v.object({ + id: v.string(), + follower_count: v.fallback(v.nullable(v.number()), null), + following_count: v.fallback(v.nullable(v.number()), null), }), - following: z.object({ - id: z.string(), - follower_count: z.number().nullable().catch(null), - following_count: z.number().nullable().catch(null), + following: v.object({ + id: v.string(), + follower_count: v.fallback(v.nullable(v.number()), null), + following_count: v.fallback(v.nullable(v.number()), null), }), }); -type FollowRelationshipUpdate = z.infer; +type FollowRelationshipUpdate = v.InferOutput; -const baseStreamingEventSchema = z.object({ - stream: z.array(z.string()).catch([]), +const baseStreamingEventSchema = v.object({ + stream: z.array(v.string()).catch([]), }); const statusStreamingEventSchema = baseStreamingEventSchema.extend({ @@ -35,7 +35,7 @@ const statusStreamingEventSchema = baseStreamingEventSchema.extend({ const stringStreamingEventSchema = baseStreamingEventSchema.extend({ event: z.enum(['delete', 'announcement.delete']), - payload: z.string(), + payload: v.string(), }); const notificationStreamingEventSchema = baseStreamingEventSchema.extend({ @@ -74,8 +74,8 @@ const followRelationshipsUpdateStreamingEventSchema = baseStreamingEventSchema.e const respondStreamingEventSchema = baseStreamingEventSchema.extend({ event: z.literal('respond'), - payload: z.preprocess((payload: any) => JSON.parse(payload), z.object({ - type: z.string(), + payload: z.preprocess((payload: any) => JSON.parse(payload), v.object({ + type: v.string(), result: z.enum(['success', 'ignored', 'error']), })), }); @@ -103,7 +103,7 @@ const streamingEventSchema: z.ZodType = z.preprocess((event: any markerStreamingEventSchema, ])) as any; -type StreamingEvent = z.infer< +type StreamingEvent = v.InferOutput< | typeof statusStreamingEventSchema | typeof stringStreamingEventSchema | typeof notificationStreamingEventSchema diff --git a/packages/pl-api/lib/entities/suggestion.ts b/packages/pl-api/lib/entities/suggestion.ts index 9f568ea3e..1ea79fb53 100644 --- a/packages/pl-api/lib/entities/suggestion.ts +++ b/packages/pl-api/lib/entities/suggestion.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { accountSchema } from './account'; @@ -29,12 +29,12 @@ const suggestionSchema = z.preprocess((suggestion: any) => { } return suggestion; -}, z.object({ - source: z.string().nullable().catch(null), - sources: z.array(z.string()).catch([]), +}, v.object({ + source: v.fallback(v.nullable(v.string()), null), + sources: z.array(v.string()).catch([]), account: accountSchema, })); -type Suggestion = z.infer; +type Suggestion = v.InferOutput; export { suggestionSchema, type Suggestion }; diff --git a/packages/pl-api/lib/entities/tag.ts b/packages/pl-api/lib/entities/tag.ts index 941b2cc66..fea9a679c 100644 --- a/packages/pl-api/lib/entities/tag.ts +++ b/packages/pl-api/lib/entities/tag.ts @@ -1,19 +1,19 @@ -import { z } from 'zod'; +import * as v from 'valibot'; -const historySchema = z.object({ +const historySchema = v.object({ day: z.coerce.number(), accounts: z.coerce.number(), uses: z.coerce.number(), }); /** @see {@link https://docs.joinmastodon.org/entities/tag} */ -const tagSchema = z.object({ +const tagSchema = v.object({ name: z.string().min(1), url: z.string().url().catch(''), history: z.array(historySchema).nullable().catch(null), - following: z.boolean().optional().catch(undefined), + following: v.fallback(v.optional(v.boolean()), undefined), }); -type Tag = z.infer; +type Tag = v.InferOutput; export { historySchema, tagSchema, type Tag }; diff --git a/packages/pl-api/lib/entities/token.ts b/packages/pl-api/lib/entities/token.ts index cd8e50b2c..59d7316dd 100644 --- a/packages/pl-api/lib/entities/token.ts +++ b/packages/pl-api/lib/entities/token.ts @@ -1,18 +1,18 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/Token/} */ -const tokenSchema = z.object({ - access_token: z.string(), - token_type: z.string(), - scope: z.string(), +const tokenSchema = v.object({ + access_token: v.string(), + token_type: v.string(), + scope: v.string(), created_at: z.number().optional().catch(undefined), id: z.number().optional().catch(undefined), - refresh_token: z.string().optional().catch(undefined), + refresh_token: v.fallback(v.optional(v.string()), undefined), expires_in: z.number().optional().catch(undefined), - me: z.string().optional().catch(undefined), + me: v.fallback(v.optional(v.string()), undefined), }); -type Token = z.infer; +type Token = v.InferOutput; export { tokenSchema, type Token }; diff --git a/packages/pl-api/lib/entities/translation.ts b/packages/pl-api/lib/entities/translation.ts index 0dcc502a0..b844818fb 100644 --- a/packages/pl-api/lib/entities/translation.ts +++ b/packages/pl-api/lib/entities/translation.ts @@ -1,17 +1,17 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { filteredArray } from './utils'; -const translationPollSchema = z.object({ - id: z.string(), - options: z.array(z.object({ - title: z.string(), +const translationPollSchema = v.object({ + id: v.string(), + options: z.array(v.object({ + title: v.string(), })), }); -const translationMediaAttachment = z.object({ - id: z.string(), - description: z.string().catch(''), +const translationMediaAttachment = v.object({ + id: v.string(), + description: v.fallback(v.string(), ''), }); /** @see {@link https://docs.joinmastodon.org/entities/Translation/} */ @@ -27,16 +27,16 @@ const translationSchema = z.preprocess((translation: any) => { }; return translation; -}, z.object({ - id: z.string().nullable().catch(null), - content: z.string().catch(''), - spoiler_text: z.string().catch(''), +}, v.object({ + id: v.fallback(v.nullable(v.string()), null), + content: v.fallback(v.string(), ''), + spoiler_text: v.fallback(v.string(), ''), poll: translationPollSchema.optional().catch(undefined), media_attachments: filteredArray(translationMediaAttachment), - detected_source_language: z.string(), - provider: z.string(), + detected_source_language: v.string(), + provider: v.string(), })); -type Translation = z.infer; +type Translation = v.InferOutput; export { translationSchema, type Translation }; diff --git a/packages/pl-api/lib/entities/trends-link.ts b/packages/pl-api/lib/entities/trends-link.ts index 6c37ad420..227e10cb1 100644 --- a/packages/pl-api/lib/entities/trends-link.ts +++ b/packages/pl-api/lib/entities/trends-link.ts @@ -1,29 +1,29 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { blurhashSchema } from './media-attachment'; import { historySchema } from './tag'; /** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/#trends-link} */ -const trendsLinkSchema = z.preprocess((link: any) => ({ ...link, id: link.url }), z.object({ - id: z.string().catch(''), +const trendsLinkSchema = z.preprocess((link: any) => ({ ...link, id: link.url }), v.object({ + id: v.fallback(v.string(), ''), url: z.string().url().catch(''), - title: z.string().catch(''), - description: z.string().catch(''), + title: v.fallback(v.string(), ''), + description: v.fallback(v.string(), ''), type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'), - author_name: z.string().catch(''), - author_url: z.string().catch(''), - provider_name: z.string().catch(''), - provider_url: z.string().catch(''), - html: z.string().catch(''), - width: z.number().nullable().catch(null), - height: z.number().nullable().catch(null), - image: z.string().nullable().catch(null), - image_description: z.string().nullable().catch(null), - embed_url: z.string().catch(''), - blurhash: blurhashSchema.nullable().catch(null), + author_name: v.fallback(v.string(), ''), + author_url: v.fallback(v.string(), ''), + provider_name: v.fallback(v.string(), ''), + provider_url: v.fallback(v.string(), ''), + html: v.fallback(v.string(), ''), + width: v.fallback(v.nullable(v.number()), null), + height: v.fallback(v.nullable(v.number()), null), + image: v.fallback(v.nullable(v.string()), null), + image_description: v.fallback(v.nullable(v.string()), null), + embed_url: v.fallback(v.string(), ''), + blurhash: v.fallback(v.nullable(blurhashSchema), null), history: z.array(historySchema).nullable().catch(null), })); -type TrendsLink = z.infer; +type TrendsLink = v.InferOutput; export { trendsLinkSchema, type TrendsLink }; diff --git a/packages/pl-api/lib/entities/utils.ts b/packages/pl-api/lib/entities/utils.ts index d6a624b4f..5ebab3a53 100644 --- a/packages/pl-api/lib/entities/utils.ts +++ b/packages/pl-api/lib/entities/utils.ts @@ -1,4 +1,4 @@ -import z from 'zod'; +import * as v from 'valibot'; /** Validate to Mastodon's date format, or use the current date. */ const dateSchema = z.string().datetime({ offset: true }).catch(new Date().toUTCString()); @@ -10,7 +10,7 @@ const filteredArray = (schema: T) => arr.map((item) => { const parsed = schema.safeParse(item); return parsed.success ? parsed.data : undefined; - }).filter((item): item is z.infer => Boolean(item)) + }).filter((item): item is v.InferOutput => Boolean(item)) )); /** Validates the string as an emoji. */ @@ -21,6 +21,6 @@ const mimeSchema = z.string().regex(/^\w+\/[-+.\w]+$/); /** zod schema to force the value into an object, if it isn't already. */ const coerceObject = (shape: T) => - z.object({}).passthrough().catch({}).pipe(z.object(shape)); + v.object({}).passthrough().catch({}).pipe(z.object(shape)); export { filteredArray, emojiSchema, dateSchema, mimeSchema, coerceObject }; diff --git a/packages/pl-api/lib/entities/web-push-subscription.ts b/packages/pl-api/lib/entities/web-push-subscription.ts index 482622e5b..24ad82665 100644 --- a/packages/pl-api/lib/entities/web-push-subscription.ts +++ b/packages/pl-api/lib/entities/web-push-subscription.ts @@ -1,13 +1,13 @@ -import { z } from 'zod'; +import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/WebPushSubscription/} */ -const webPushSubscriptionSchema = z.object({ +const webPushSubscriptionSchema = v.object({ id: z.coerce.string(), - endpoint: z.string(), + endpoint: v.string(), alerts: z.record(z.boolean()), - server_key: z.string(), + server_key: v.string(), }); -type WebPushSubscription = z.infer; +type WebPushSubscription = v.InferOutput; export { webPushSubscriptionSchema, type WebPushSubscription }; diff --git a/packages/pl-api/package.json b/packages/pl-api/package.json index f790227ed..b81524f07 100644 --- a/packages/pl-api/package.json +++ b/packages/pl-api/package.json @@ -42,6 +42,7 @@ "object-to-formdata": "^4.5.1", "query-string": "^9.1.0", "semver": "^7.6.3", + "valibot": "^0.42.1", "zod": "^3.23.8" }, "module": "./dist/main.es.js", diff --git a/packages/pl-api/yarn.lock b/packages/pl-api/yarn.lock index 3bbfde0ed..bd2dfc87d 100644 --- a/packages/pl-api/yarn.lock +++ b/packages/pl-api/yarn.lock @@ -2551,6 +2551,11 @@ uri-js@^4.2.2, uri-js@^4.4.1: dependencies: punycode "^2.1.0" +valibot@^0.42.1: + version "0.42.1" + resolved "https://registry.yarnpkg.com/valibot/-/valibot-0.42.1.tgz#a31183d8e9d7552f98e22ca0977172cab8815188" + integrity sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw== + vite-plugin-dts@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/vite-plugin-dts/-/vite-plugin-dts-4.2.3.tgz#e0d9616eb574700111dbd19ae98e166541433263" diff --git a/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/index.tsx b/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/index.tsx index 69bdcb761..f2203510f 100644 --- a/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/index.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/index.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { z } from 'zod'; +import * as v from 'valibot'; import { useCreateGroup } from 'pl-fe/api/hooks'; import { Modal, Stack } from 'pl-fe/components/ui'; @@ -56,9 +56,9 @@ const CreateGroupModal: React.FC = ({ onClose }) => { setGroup(group); }, onError(error: { response?: PlfeResponse }) { - const msg = z.object({ error: z.string() }).safeParse(error?.response?.json); + const msg = v.safeParse(v.object({ error: v.string() }), error?.response?.json); if (msg.success) { - toast.error(msg.data.error); + toast.error(msg.output.error); } }, }); From ea3addf38886c192999cfdd3e8514d2137aa5143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 14 Oct 2024 20:54:44 +0200 Subject: [PATCH 12/28] pl-api: More blind search and replace before actual testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/lib/client.ts | 6 +- packages/pl-api/lib/entities/account.ts | 36 +++--- packages/pl-api/lib/entities/admin/account.ts | 4 +- packages/pl-api/lib/entities/admin/cohort.ts | 6 +- .../pl-api/lib/entities/admin/domain-block.ts | 2 +- .../pl-api/lib/entities/admin/ip-block.ts | 2 +- .../entities/admin/moderation-log-entry.ts | 2 +- .../lib/entities/admin/pleroma-config.ts | 4 +- packages/pl-api/lib/entities/admin/report.ts | 4 +- packages/pl-api/lib/entities/announcement.ts | 6 +- packages/pl-api/lib/entities/chat.ts | 2 +- .../pl-api/lib/entities/directory/category.ts | 2 +- .../pl-api/lib/entities/directory/language.ts | 2 +- .../pl-api/lib/entities/directory/server.ts | 2 +- .../entities/directory/statistics-period.ts | 6 +- packages/pl-api/lib/entities/domain-block.ts | 2 +- .../pl-api/lib/entities/emoji-reaction.ts | 8 +- packages/pl-api/lib/entities/featured-tag.ts | 4 +- packages/pl-api/lib/entities/filter-result.ts | 4 +- packages/pl-api/lib/entities/filter.ts | 6 +- packages/pl-api/lib/entities/group.ts | 4 +- packages/pl-api/lib/entities/instance.ts | 110 +++++++++--------- .../pl-api/lib/entities/interaction-policy.ts | 6 +- .../lib/entities/interaction-request.ts | 6 +- packages/pl-api/lib/entities/location.ts | 8 +- packages/pl-api/lib/entities/marker.ts | 4 +- .../pl-api/lib/entities/media-attachment.ts | 74 ++++++------ packages/pl-api/lib/entities/mention.ts | 2 +- .../lib/entities/notification-policy.ts | 4 +- .../lib/entities/notification-request.ts | 2 +- packages/pl-api/lib/entities/notification.ts | 22 ++-- packages/pl-api/lib/entities/oauth-token.ts | 2 +- packages/pl-api/lib/entities/poll.ts | 6 +- packages/pl-api/lib/entities/preview-card.ts | 10 +- .../entities/relationship-severance-event.ts | 4 +- packages/pl-api/lib/entities/report.ts | 6 +- .../pl-api/lib/entities/scheduled-status.ts | 8 +- packages/pl-api/lib/entities/status-source.ts | 4 +- packages/pl-api/lib/entities/status.ts | 50 ++++---- .../pl-api/lib/entities/streaming-event.ts | 26 ++--- packages/pl-api/lib/entities/tag.ts | 4 +- packages/pl-api/lib/entities/token.ts | 6 +- packages/pl-api/lib/entities/translation.ts | 2 +- packages/pl-api/lib/entities/trends-link.ts | 6 +- packages/pl-api/lib/entities/utils.ts | 14 ++- .../lib/entities/web-push-subscription.ts | 2 +- packages/pl-fe/src/schemas/utils.ts | 2 +- 47 files changed, 253 insertions(+), 251 deletions(-) diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts index 13cc6dd53..9e423ed81 100644 --- a/packages/pl-api/lib/client.ts +++ b/packages/pl-api/lib/client.ts @@ -380,7 +380,7 @@ class PlApiClient { return v.parse(v.intersect([v.object({ type: v.string(), - }), z.record(v.any())]), response.json); + }), v.record(v.string(), v.any())]), response.json); }, mfaChallenge: async (params: MfaChallengeParams) => { @@ -2773,7 +2773,7 @@ class PlApiClient { const response = await this.request('/api/v1/instance/translation_languages'); - return v.parse(z.record(v.array(v.string())), response.json); + return v.parse(v.record(v.string(), v.array(v.string())), response.json); }, /** @@ -2806,7 +2806,7 @@ class PlApiClient { getFrontendConfigurations: async () => { const response = await this.request('/api/pleroma/frontend_configurations'); - return v.parse(z.record(z.record(v.any())).catch({}), response.json); + return v.parse(v.fallback(v.record(v.string(), v.record(v.string(), v.any())), {}), response.json); }, }; diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index ee9a970e2..5af5a0e2d 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -64,31 +64,31 @@ const preprocessAccount = (account: any) => { const fieldSchema = v.object({ name: v.string(), value: v.string(), - verified_at: z.string().datetime({ offset: true }).nullable().catch(null), + verified_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), }); const baseAccountSchema = v.object({ id: v.string(), username: v.fallback(v.string(), ''), acct: v.fallback(v.string(), ''), - url: z.string().url(), + url: v.pipe(v.string(), v.url()), display_name: v.fallback(v.string(), ''), note: v.fallback(v.string(), ''), avatar: v.fallback(v.string(), ''), - avatar_static: z.string().url().catch(''), - header: z.string().url().catch(''), - header_static: z.string().url().catch(''), + avatar_static: v.fallback(v.pipe(v.string(), v.url()), ''), + header: v.fallback(v.pipe(v.string(), v.url()), ''), + header_static: v.fallback(v.pipe(v.string(), v.url()), ''), locked: v.fallback(v.boolean(), false), fields: filteredArray(fieldSchema), emojis: filteredArray(customEmojiSchema), bot: v.fallback(v.boolean(), false), group: v.fallback(v.boolean(), false), discoverable: v.fallback(v.boolean(), false), - noindex: v.fallback(v.optional(v.nullable()), null), + noindex: v.fallback(v.nullable(v.boolean()), null), suspended: v.fallback(v.optional(v.boolean()), undefined), limited: v.fallback(v.optional(v.boolean()), undefined), created_at: z.string().datetime().catch(new Date().toUTCString()), - last_status_at: z.string().date().nullable().catch(null), + last_status_at: v.fallback(v.nullable(z.string().date()), null), statuses_count: v.fallback(v.number(), 0), followers_count: v.fallback(v.number(), 0), following_count: v.fallback(v.number(), 0), @@ -97,7 +97,7 @@ const baseAccountSchema = v.object({ fqn: v.fallback(v.nullable(v.string()), null), ap_id: v.fallback(v.nullable(v.string()), null), background_image: v.fallback(v.nullable(v.string()), null), - relationship: relationshipSchema.optional().catch(undefined), + relationship: v.fallback(v.optional(relationshipSchema), undefined), is_moderator: v.fallback(v.optional(v.boolean()), undefined), is_admin: v.fallback(v.optional(v.boolean()), undefined), is_suggested: v.fallback(v.optional(v.boolean()), undefined), @@ -106,7 +106,7 @@ const baseAccountSchema = v.object({ hide_follows: v.fallback(v.optional(v.boolean()), undefined), hide_followers_count: v.fallback(v.optional(v.boolean()), undefined), hide_follows_count: v.fallback(v.optional(v.boolean()), undefined), - accepts_chat_messages: v.fallback(v.optional(v.nullable()), null), + accepts_chat_messages: v.fallback(v.nullable(v.boolean()), null), favicon: v.fallback(v.optional(v.string()), undefined), birthday: z.string().date().optional().catch(undefined), deactivated: v.fallback(v.optional(v.boolean()), undefined), @@ -121,13 +121,13 @@ const baseAccountSchema = v.object({ verified: v.fallback(v.optional(v.boolean()), undefined), __meta: coerceObject({ - pleroma: z.any().optional().catch(undefined), - source: z.any().optional().catch(undefined), + pleroma: v.fallback(v.any(), undefined), + source: v.fallback(v.any(), undefined), }), }); const accountWithMovedAccountSchema = baseAccountSchema.extend({ - moved: z.lazy((): typeof baseAccountSchema => accountWithMovedAccountSchema as any).nullable().catch(null), + moved: v.fallback(v.nullable(z.lazy((): typeof baseAccountSchema => accountWithMovedAccountSchema as any)), null), }); /** @see {@link https://docs.joinmastodon.org/entities/Account/} */ @@ -145,7 +145,7 @@ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWi source: v.object({ note: v.fallback(v.string(), ''), fields: filteredArray(fieldSchema), - privacy: z.enum(['public', 'unlisted', 'private', 'direct']), + privacy: v.picklist(['public', 'unlisted', 'private', 'direct']), sensitive: v.fallback(v.boolean(), false), language: v.fallback(v.nullable(v.string()), null), follow_requests_count: z.number().int().nonnegative().catch(0), @@ -158,15 +158,15 @@ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWi }).nullable().catch(null), role: v.fallback(v.nullable(roleSchema), null), - settings_store: z.record(z.any()).optional().catch(undefined), + settings_store: v.record(v.string(), v.any()).optional().catch(undefined), chat_token: v.fallback(v.optional(v.string()), undefined), allow_following_move: v.fallback(v.optional(v.boolean()), undefined), - unread_conversation_count: z.number().optional().catch(undefined), - unread_notifications_count: z.number().optional().catch(undefined), - notification_settings: v.object({ + unread_conversation_count: v.fallback(v.optional(v.number()), undefined), + unread_notifications_count: v.fallback(v.optional(v.number()), undefined), + notification_settings: v.fallback(v.optional(v.object({ block_from_strangers: v.fallback(v.boolean(), false), hide_notification_contents: v.fallback(v.boolean(), false), - }).optional().catch(undefined), + })), undefined), })); type CredentialAccount = v.InferOutput & WithMoved; diff --git a/packages/pl-api/lib/entities/admin/account.ts b/packages/pl-api/lib/entities/admin/account.ts index e142ccfe3..8b829ae86 100644 --- a/packages/pl-api/lib/entities/admin/account.ts +++ b/packages/pl-api/lib/entities/admin/account.ts @@ -42,7 +42,7 @@ const adminAccountSchema = z.preprocess((account: any) => { domain: v.fallback(v.nullable(v.string()), null), created_at: dateSchema, email: v.fallback(v.nullable(v.string()), null), - ip: z.string().ip().nullable().catch(null), + ip: v.fallback(v.nullable(z.string().ip()), null), ips: filteredArray(adminIpSchema), locale: v.fallback(v.nullable(v.string()), null), invite_request: v.fallback(v.nullable(v.string()), null), @@ -58,7 +58,7 @@ const adminAccountSchema = z.preprocess((account: any) => { actor_type: v.fallback(v.nullable(v.string()), null), display_name: v.fallback(v.nullable(v.string()), null), - suggested: v.fallback(v.optional(v.nullable()), null), + suggested: v.fallback(v.nullable(v.boolean()), null), })); type AdminAccount = v.InferOutput; diff --git a/packages/pl-api/lib/entities/admin/cohort.ts b/packages/pl-api/lib/entities/admin/cohort.ts index 72ae379d6..af4cdcdfd 100644 --- a/packages/pl-api/lib/entities/admin/cohort.ts +++ b/packages/pl-api/lib/entities/admin/cohort.ts @@ -3,11 +3,11 @@ import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_Cohort/} */ const adminCohortSchema = v.object({ period: z.string().datetime({ offset: true }), - frequency: z.enum(['day', 'month']), + frequency: v.picklist(['day', 'month']), data: z.array(v.object({ date: z.string().datetime({ offset: true }), - rate: z.number(), - value: z.number().int(), + rate: v.number(), + value: v.pipe(v.number(), v.integer()), })), }); diff --git a/packages/pl-api/lib/entities/admin/domain-block.ts b/packages/pl-api/lib/entities/admin/domain-block.ts index 9d87e63a6..d87f18338 100644 --- a/packages/pl-api/lib/entities/admin/domain-block.ts +++ b/packages/pl-api/lib/entities/admin/domain-block.ts @@ -8,7 +8,7 @@ const adminDomainBlockSchema = v.object({ domain: v.string(), digest: v.string(), created_at: dateSchema, - severity: z.enum(['silence', 'suspend', 'noop']), + severity: v.picklist(['silence', 'suspend', 'noop']), reject_media: v.boolean(), reject_reports: v.boolean(), private_comment: v.fallback(v.nullable(v.string()), null), diff --git a/packages/pl-api/lib/entities/admin/ip-block.ts b/packages/pl-api/lib/entities/admin/ip-block.ts index 580627a08..bf470a426 100644 --- a/packages/pl-api/lib/entities/admin/ip-block.ts +++ b/packages/pl-api/lib/entities/admin/ip-block.ts @@ -6,7 +6,7 @@ import { dateSchema } from '../utils'; const adminIpBlockSchema = v.object({ id: v.string(), ip: z.string().ip(), - severity: z.enum(['sign_up_requires_approval', 'sign_up_block', 'no_access']), + severity: v.picklist(['sign_up_requires_approval', 'sign_up_block', 'no_access']), comment: v.fallback(v.string(), ''), created_at: dateSchema, expires_at: z.string().datetime({ offset: true }), diff --git a/packages/pl-api/lib/entities/admin/moderation-log-entry.ts b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts index b901128a1..86ce01075 100644 --- a/packages/pl-api/lib/entities/admin/moderation-log-entry.ts +++ b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts @@ -3,7 +3,7 @@ import * as v from 'valibot'; /** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminmoderation_log} */ const adminModerationLogEntrySchema = v.object({ id: z.coerce.string(), - data: z.record(v.string(), z.any()).catch({}), + data: v.fallback(v.record(v.string(), v.any()), {}), time: v.fallback(v.number(), 0), message: v.fallback(v.string(), ''), }); diff --git a/packages/pl-api/lib/entities/admin/pleroma-config.ts b/packages/pl-api/lib/entities/admin/pleroma-config.ts index 8fb09f5a3..aee37727a 100644 --- a/packages/pl-api/lib/entities/admin/pleroma-config.ts +++ b/packages/pl-api/lib/entities/admin/pleroma-config.ts @@ -1,8 +1,8 @@ import * as v from 'valibot'; const pleromaConfigSchema = v.object({ - configs: z.array(v.object({ - value: z.any(), + configs: v.array(v.object({ + value: v.any(), group: v.string(), key: v.string(), })), diff --git a/packages/pl-api/lib/entities/admin/report.ts b/packages/pl-api/lib/entities/admin/report.ts index 7b64b0f67..4ae7c4f3c 100644 --- a/packages/pl-api/lib/entities/admin/report.ts +++ b/packages/pl-api/lib/entities/admin/report.ts @@ -31,8 +31,8 @@ const adminReportSchema = z.preprocess((report: any) => { category: v.fallback(v.optional(v.string()), undefined), comment: v.fallback(v.optional(v.string()), undefined), forwarded: v.fallback(v.optional(v.boolean()), undefined), - created_at: dateSchema.optional().catch(undefined), - updated_at: dateSchema.optional().catch(undefined), + created_at: v.fallback(v.optional(dateSchema), undefined), + updated_at: v.fallback(v.optional(dateSchema), undefined), account: adminAccountSchema, target_account: adminAccountSchema, assigned_account: v.fallback(v.nullable(adminAccountSchema), null), diff --git a/packages/pl-api/lib/entities/announcement.ts b/packages/pl-api/lib/entities/announcement.ts index f161e97d0..0be54d135 100644 --- a/packages/pl-api/lib/entities/announcement.ts +++ b/packages/pl-api/lib/entities/announcement.ts @@ -10,8 +10,8 @@ import { dateSchema, filteredArray } from './utils'; const announcementSchema = v.object({ id: v.string(), content: v.fallback(v.string(), ''), - starts_at: z.string().datetime().nullable().catch(null), - ends_at: z.string().datetime().nullable().catch(null), + starts_at: v.fallback(v.nullable(z.string().datetime()), null), + ends_at: v.fallback(v.nullable(z.string().datetime()), null), all_day: v.fallback(v.boolean(), false), read: v.fallback(v.boolean(), false), published_at: dateSchema, @@ -20,7 +20,7 @@ const announcementSchema = v.object({ (statuses: any) => Array.isArray(statuses) ? Object.fromEntries(statuses.map((status: any) => [status.url, status.account?.acct]) || []) : statuses, - z.record(v.string(), v.string()), + v.record(v.string(), v.string(), v.string()), ), mentions: filteredArray(mentionSchema), tags: filteredArray(tagSchema), diff --git a/packages/pl-api/lib/entities/chat.ts b/packages/pl-api/lib/entities/chat.ts index 2d4762e14..cddad9af7 100644 --- a/packages/pl-api/lib/entities/chat.ts +++ b/packages/pl-api/lib/entities/chat.ts @@ -8,7 +8,7 @@ import { dateSchema } from './utils'; const chatSchema = v.object({ id: v.string(), account: accountSchema, - unread: z.number().int(), + unread: v.pipe(v.number(), v.integer()), last_message: v.fallback(v.nullable(chatMessageSchema), null), created_at: dateSchema, }); diff --git a/packages/pl-api/lib/entities/directory/category.ts b/packages/pl-api/lib/entities/directory/category.ts index 19b35cdde..605d8ab0e 100644 --- a/packages/pl-api/lib/entities/directory/category.ts +++ b/packages/pl-api/lib/entities/directory/category.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; const directoryCategorySchema = v.object({ category: v.string(), - servers_count: z.coerce.number().nullable().catch(null), + servers_count: v.fallback(v.nullable(z.coerce.number()), null), }); type DirectoryCategory = v.InferOutput; diff --git a/packages/pl-api/lib/entities/directory/language.ts b/packages/pl-api/lib/entities/directory/language.ts index e97b397d8..2c0c330a6 100644 --- a/packages/pl-api/lib/entities/directory/language.ts +++ b/packages/pl-api/lib/entities/directory/language.ts @@ -3,7 +3,7 @@ import * as v from 'valibot'; const directoryLanguageSchema = v.object({ locale: v.string(), language: v.string(), - servers_count: z.coerce.number().nullable().catch(null), + servers_count: v.fallback(v.nullable(z.coerce.number()), null), }); type DirectoryLanguage = v.InferOutput; diff --git a/packages/pl-api/lib/entities/directory/server.ts b/packages/pl-api/lib/entities/directory/server.ts index 231cbff3e..3c25e44bb 100644 --- a/packages/pl-api/lib/entities/directory/server.ts +++ b/packages/pl-api/lib/entities/directory/server.ts @@ -7,7 +7,7 @@ const directoryServerSchema = v.object({ languages: z.array(v.string()), region: v.string(), categories: z.array(v.string()), - proxied_thumbnail: z.string().url().nullable().catch(null), + proxied_thumbnail: v.fallback(v.nullable(z.string().url()), null), blurhash: v.fallback(v.nullable(v.string()), null), total_users: z.coerce.number(), last_week_users: z.coerce.number(), diff --git a/packages/pl-api/lib/entities/directory/statistics-period.ts b/packages/pl-api/lib/entities/directory/statistics-period.ts index ab3086cab..f5c909c16 100644 --- a/packages/pl-api/lib/entities/directory/statistics-period.ts +++ b/packages/pl-api/lib/entities/directory/statistics-period.ts @@ -2,9 +2,9 @@ import * as v from 'valibot'; const directoryStatisticsPeriodSchema = v.object({ period: z.string().date(), - server_count: z.coerce.number().nullable().catch(null), - user_count: z.coerce.number().nullable().catch(null), - active_user_count: z.coerce.number().nullable().catch(null), + server_count: v.fallback(v.nullable(z.coerce.number()), null), + user_count: v.fallback(v.nullable(z.coerce.number()), null), + active_user_count: v.fallback(v.nullable(z.coerce.number()), null), }); type DirectoryStatisticsPeriod = v.InferOutput; diff --git a/packages/pl-api/lib/entities/domain-block.ts b/packages/pl-api/lib/entities/domain-block.ts index 97063a7ed..e5bf6e0da 100644 --- a/packages/pl-api/lib/entities/domain-block.ts +++ b/packages/pl-api/lib/entities/domain-block.ts @@ -4,7 +4,7 @@ import * as v from 'valibot'; const domainBlockSchema = v.object({ domain: v.string(), digest: v.string(), - severity: z.enum(['silence', 'suspend']), + severity: v.picklist(['silence', 'suspend']), comment: v.fallback(v.optional(v.string()), undefined), }); diff --git a/packages/pl-api/lib/entities/emoji-reaction.ts b/packages/pl-api/lib/entities/emoji-reaction.ts index 917cdb0d2..8b66d12f9 100644 --- a/packages/pl-api/lib/entities/emoji-reaction.ts +++ b/packages/pl-api/lib/entities/emoji-reaction.ts @@ -7,16 +7,16 @@ const baseEmojiReactionSchema = v.object({ count: v.fallback(v.nullable(v.number()), null), me: v.fallback(v.boolean(), false), name: emojiSchema, - url: z.literal(undefined).catch(undefined), - static_url: z.literal(undefined).catch(undefined), + url: v.fallback(v.undefined(), undefined), + static_url: v.fallback(v.undefined(), undefined), accounts: filteredArray(accountSchema), account_ids: filteredArray(v.string()).catch([]), }); const customEmojiReactionSchema = baseEmojiReactionSchema.extend({ name: v.string(), - url: z.string().url(), - static_url: z.string().url(), + url: v.pipe(v.string(), v.url()), + static_url: v.pipe(v.string(), v.url()), }); /** diff --git a/packages/pl-api/lib/entities/featured-tag.ts b/packages/pl-api/lib/entities/featured-tag.ts index dfd7781c6..f07ad3e7b 100644 --- a/packages/pl-api/lib/entities/featured-tag.ts +++ b/packages/pl-api/lib/entities/featured-tag.ts @@ -5,8 +5,8 @@ const featuredTagSchema = v.object({ id: v.string(), name: v.string(), url: v.fallback(v.optional(v.string()), undefined), - statuses_count: z.number(), - last_status_at: z.number(), + statuses_count: v.number(), + last_status_at: v.number(), }); type FeaturedTag = v.InferOutput; diff --git a/packages/pl-api/lib/entities/filter-result.ts b/packages/pl-api/lib/entities/filter-result.ts index 36b7c1b42..fcd64be77 100644 --- a/packages/pl-api/lib/entities/filter-result.ts +++ b/packages/pl-api/lib/entities/filter-result.ts @@ -5,8 +5,8 @@ import { filterSchema } from './filter'; /** @see {@link https://docs.joinmastodon.org/entities/FilterResult/} */ const filterResultSchema = v.object({ filter: filterSchema, - keyword_matches: z.array(v.string()).nullable().catch(null), - status_matches: z.array(v.string()).nullable().catch(null), + keyword_matches: v.fallback(v.nullable(v.string()), null), + status_matches: v.fallback(v.nullable(v.string()), null), }); type FilterResult = v.InferOutput; diff --git a/packages/pl-api/lib/entities/filter.ts b/packages/pl-api/lib/entities/filter.ts index 7e27850a4..5301800f5 100644 --- a/packages/pl-api/lib/entities/filter.ts +++ b/packages/pl-api/lib/entities/filter.ts @@ -33,9 +33,9 @@ const filterSchema = z.preprocess((filter: any) => { }, v.object({ id: v.string(), title: v.string(), - context: z.array(z.enum(['home', 'notifications', 'public', 'thread', 'account'])), - expires_at: z.string().datetime({ offset: true }).nullable().catch(null), - filter_action: z.enum(['warn', 'hide']).catch('warn'), + context: v.array(v.picklist(['home', 'notifications', 'public', 'thread', 'account'])), + expires_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), + filter_action: v.fallback(v.picklist(['warn', 'hide']), 'warn'), keywords: filteredArray(filterKeywordSchema), statuses: filteredArray(filterStatusSchema), })); diff --git a/packages/pl-api/lib/entities/group.ts b/packages/pl-api/lib/entities/group.ts index 31e5e60bf..ee8f8bed8 100644 --- a/packages/pl-api/lib/entities/group.ts +++ b/packages/pl-api/lib/entities/group.ts @@ -17,10 +17,10 @@ const groupSchema = v.object({ locked: v.fallback(v.boolean(), false), membership_required: v.fallback(v.boolean(), false), members_count: v.fallback(v.number(), 0), - owner: v.object({ id: z.string() }).nullable().catch(null), + owner: v.object({ id: v.string() }).nullable().catch(null), note: z.string().transform(note => note === '

' ? '' : note).catch(''), relationship: v.fallback(v.nullable(groupRelationshipSchema), null), // Dummy field to be overwritten later - statuses_visibility: z.string().catch('public'), + statuses_visibility: v.fallback(v.string(), 'public'), uri: v.fallback(v.string(), ''), url: v.fallback(v.string(), ''), diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts index b6950f262..4a1ebdb40 100644 --- a/packages/pl-api/lib/entities/instance.ts +++ b/packages/pl-api/lib/entities/instance.ts @@ -108,47 +108,47 @@ const fixVersion = (version: string) => { }; const configurationSchema = coerceObject({ - accounts: v.object({ + accounts: v.fallback(v.nullable(v.object({ allow_custom_css: v.boolean(), - max_featured_tags: z.number().int(), - max_profile_fields: z.number().int(), - }).nullable().catch(null), + max_featured_tags: v.pipe(v.number(), v.integer()), + max_profile_fields: v.pipe(v.number(), v.integer()), + })), null), chats: coerceObject({ - max_characters: z.number().catch(5000), + max_characters: v.fallback(v.number(), 5000), }), groups: coerceObject({ - max_characters_description: z.number().catch(160), - max_characters_name: z.number().catch(50), + max_characters_description: v.fallback(v.number(), 160), + max_characters_name: v.fallback(v.number(), 50), }), media_attachments: coerceObject({ - image_matrix_limit: z.number().optional().catch(undefined), - image_size_limit: z.number().optional().catch(undefined), - supported_mime_types: mimeSchema.array().optional().catch(undefined), - video_duration_limit: z.number().optional().catch(undefined), - video_frame_rate_limit: z.number().optional().catch(undefined), - video_matrix_limit: z.number().optional().catch(undefined), - video_size_limit: z.number().optional().catch(undefined), + image_matrix_limit: v.fallback(v.optional(v.number()), undefined), + image_size_limit: v.fallback(v.optional(v.number()), undefined), + supported_mime_types: v.fallback(v.optional(v.array(mimeSchema)), undefined), + video_duration_limit: v.fallback(v.optional(v.number()), undefined), + video_frame_rate_limit: v.fallback(v.optional(v.number()), undefined), + video_matrix_limit: v.fallback(v.optional(v.number()), undefined), + video_size_limit: v.fallback(v.optional(v.number()), undefined), }), polls: coerceObject({ - max_characters_per_option: z.number().catch(25), - max_expiration: z.number().catch(2629746), - max_options: z.number().catch(4), - min_expiration: z.number().catch(300), + max_characters_per_option: v.fallback(v.number(), 25), + max_expiration: v.fallback(v.number(), 2629746), + max_options: v.fallback(v.number(), 4), + min_expiration: v.fallback(v.number(), 300), }), reactions: coerceObject({ max_reactions: v.fallback(v.number(), 0), }), statuses: coerceObject({ - characters_reserved_per_url: z.number().optional().catch(undefined), - max_characters: z.number().catch(500), - max_media_attachments: z.number().catch(4), + characters_reserved_per_url: v.fallback(v.optional(v.number()), undefined), + max_characters: v.fallback(v.number(), 500), + max_media_attachments: v.fallback(v.number(), 4), }), translation: coerceObject({ enabled: v.fallback(v.boolean(), false), }), urls: coerceObject({ - streaming: z.string().url().optional().catch(undefined), + streaming: v.fallback(v.optional(v.pipe(v.string(), v.url())), undefined), }), vapid: coerceObject({ public_key: v.fallback(v.string(), ''), @@ -156,8 +156,8 @@ const configurationSchema = coerceObject({ }); const contactSchema = coerceObject({ - contact_account: accountSchema.optional().catch(undefined), - email: z.string().email().catch(''), + contact_account: v.fallback(v.optional(accountSchema), undefined), + email: v.fallback(v.pipe(v.string(), v.email()), ''), }); const pleromaSchema = coerceObject({ @@ -165,11 +165,11 @@ const pleromaSchema = coerceObject({ account_activation_required: v.fallback(v.boolean(), false), birthday_min_age: v.fallback(v.number(), 0), birthday_required: v.fallback(v.boolean(), false), - description_limit: z.number().catch(1500), + description_limit: v.fallback(v.number(), 1500), features: v.fallback(v.array(v.string()), []), federation: coerceObject({ enabled: v.fallback(v.boolean(), true), // Assume true unless explicitly false - mrf_policies: z.string().array().optional().catch(undefined), + mrf_policies: v.fallback(v.optional(v.array(v.string())), undefined), mrf_simple: coerceObject({ accept: v.fallback(v.array(v.string()), []), avatar_removal: v.fallback(v.array(v.string()), []), @@ -192,17 +192,15 @@ const pleromaSchema = coerceObject({ allow_headings: v.fallback(v.boolean(), false), allow_inline_images: v.fallback(v.boolean(), false), }), - migration_cooldown_period: z.number().optional().catch(undefined), + migration_cooldown_period: v.fallback(v.optional(v.number()), undefined), multitenancy: coerceObject({ - domains: z - .array( - v.object({ - domain: z.coerce.string(), - id: v.string(), - public: v.fallback(v.boolean(), false), - }), - ) - .optional(), + domains: v.optional(v.array( + v.object({ + domain: z.coerce.string(), + id: v.string(), + public: v.fallback(v.boolean(), false), + }), + )), enabled: v.fallback(v.boolean(), false), }), post_formats: z.string().array().optional().catch(undefined), @@ -230,16 +228,16 @@ const pleromaSchema = coerceObject({ }), oauth_consumer_strategies: v.fallback(v.array(v.string()), []), stats: coerceObject({ - mau: z.number().optional().catch(undefined), + mau: v.fallback(v.optional(v.number()), undefined), }), vapid_public_key: v.fallback(v.string(), ''), }); const pleromaPollLimitsSchema = coerceObject({ - max_expiration: z.number().optional().catch(undefined), - max_option_chars: z.number().optional().catch(undefined), - max_options: z.number().optional().catch(undefined), - min_expiration: z.number().optional().catch(undefined), + max_expiration: v.fallback(v.optional(v.number()), undefined), + max_option_chars: v.fallback(v.optional(v.number()), undefined), + max_options: v.fallback(v.optional(v.number()), undefined), + min_expiration: v.fallback(v.optional(v.number()), undefined), }); const registrations = coerceObject({ @@ -249,9 +247,9 @@ const registrations = coerceObject({ }); const statsSchema = coerceObject({ - domain_count: z.number().optional().catch(undefined), - status_count: z.number().optional().catch(undefined), - user_count: z.number().optional().catch(undefined), + domain_count: v.fallback(v.optional(v.number()), undefined), + status_count: v.fallback(v.optional(v.number()), undefined), + user_count: v.fallback(v.optional(v.number()), undefined), }); const thumbnailSchema = coerceObject({ @@ -268,15 +266,15 @@ const instanceV1Schema = coerceObject({ account_domain: v.fallback(v.string(), ''), approval_required: v.fallback(v.boolean(), false), configuration: configurationSchema, - contact_account: accountSchema.optional().catch(undefined), + contact_account: v.fallback(v.optional(accountSchema), undefined), description: v.fallback(v.string(), ''), - description_limit: z.number().catch(1500), - email: z.string().email().catch(''), + description_limit: v.fallback(v.number(), 1500), + email: v.fallback(v.pipe(v.string(), v.email()), ''), feature_quote: v.fallback(v.boolean(), false), - fedibird_capabilities: z.array(v.string()).catch([]), + fedibird_capabilities: v.fallback(v.array(v.string()), []), languages: v.fallback(v.array(v.string()), []), - max_media_attachments: z.number().optional().catch(undefined), - max_toot_chars: z.number().optional().catch(undefined), + max_media_attachments: v.fallback(v.optional(v.number()), undefined), + max_toot_chars: v.fallback(v.optional(v.number()), undefined), pleroma: pleromaSchema, poll_limits: pleromaPollLimitsSchema, registrations: v.fallback(v.boolean(), false), @@ -285,13 +283,13 @@ const instanceV1Schema = coerceObject({ stats: statsSchema, thumbnail: v.fallback(v.string(), ''), title: v.fallback(v.string(), ''), - upload_limit: z.number().optional().catch(undefined), + upload_limit: v.fallback(v.optional(v.number()), undefined), uri: v.fallback(v.string(), ''), urls: coerceObject({ - streaming_api: z.string().url().optional().catch(undefined), + streaming_api: v.fallback(v.optional(v.pipe(v.string(), v.url())), undefined), }), usage: usageSchema, - version: z.string().catch('0.0.0'), + version: v.fallback(v.string(), '0.0.0'), }); /** @see {@link https://docs.joinmastodon.org/entities/Instance/} */ @@ -308,13 +306,13 @@ const instanceSchema = z.preprocess((data: any) => { return instanceV1ToV2({ ...data, api_versions: apiVersions }); }, coerceObject({ account_domain: v.fallback(v.string(), ''), - api_versions: z.record(z.number()).catch({}), + api_versions: v.fallback(v.record(v.string(), v.number()), {}), configuration: configurationSchema, contact: contactSchema, description: v.fallback(v.string(), ''), domain: v.fallback(v.string(), ''), feature_quote: v.fallback(v.boolean(), false), - fedibird_capabilities: z.array(v.string()).catch([]), + fedibird_capabilities: v.fallback(v.array(v.string()), []), languages: v.fallback(v.array(v.string()), []), pleroma: pleromaSchema, registrations: registrations, @@ -323,7 +321,7 @@ const instanceSchema = z.preprocess((data: any) => { thumbnail: thumbnailSchema, title: v.fallback(v.string(), ''), usage: usageSchema, - version: z.string().catch('0.0.0'), + version: v.fallback(v.string(), '0.0.0'), }).transform((instance) => { const version = fixVersion(instance.version); diff --git a/packages/pl-api/lib/entities/interaction-policy.ts b/packages/pl-api/lib/entities/interaction-policy.ts index 975743c92..d76b064e2 100644 --- a/packages/pl-api/lib/entities/interaction-policy.ts +++ b/packages/pl-api/lib/entities/interaction-policy.ts @@ -2,11 +2,11 @@ import * as v from 'valibot'; import { coerceObject } from './utils'; -const interactionPolicyEntrySchema = z.enum(['public', 'followers', 'following', 'mutuals', 'mentioned', 'author', 'me']); +const interactionPolicyEntrySchema = v.picklist(['public', 'followers', 'following', 'mutuals', 'mentioned', 'author', 'me']); const interactionPolicyRuleSchema = coerceObject({ - always: z.array(interactionPolicyEntrySchema).default(['public']), - with_approval: z.array(interactionPolicyEntrySchema).default([]), + always: v.fallback(v.array(interactionPolicyEntrySchema), ['public']), + with_approval: v.fallback(v.array(interactionPolicyEntrySchema), []), }); /** @see {@link https://docs.gotosocial.org/en/latest/api/swagger/} */ diff --git a/packages/pl-api/lib/entities/interaction-request.ts b/packages/pl-api/lib/entities/interaction-request.ts index 92b70a1f1..b75288846 100644 --- a/packages/pl-api/lib/entities/interaction-request.ts +++ b/packages/pl-api/lib/entities/interaction-request.ts @@ -5,14 +5,14 @@ import { statusSchema } from './status'; /** @see {@link https://docs.gotosocial.org/en/latest/api/swagger.yaml#/definitions/interactionRequest} */ const interactionRequestSchema = v.object({ - accepted_at: z.string().datetime().nullable().catch(null), + accepted_at: v.fallback(v.nullable(z.string().datetime()), null), account: accountSchema, created_at: z.string().datetime(), id: v.string(), - rejected_at: z.string().datetime().nullable().catch(null), + rejected_at: v.fallback(v.nullable(z.string().datetime()), null), reply: v.fallback(v.nullable(statusSchema), null), status: v.fallback(v.nullable(statusSchema), null), - type: z.enum(['favourite', 'reply', 'reblog']), + type: v.picklist(['favourite', 'reply', 'reblog']), uri: v.fallback(v.nullable(v.string()), null), }); diff --git a/packages/pl-api/lib/entities/location.ts b/packages/pl-api/lib/entities/location.ts index b9391c444..37015600d 100644 --- a/packages/pl-api/lib/entities/location.ts +++ b/packages/pl-api/lib/entities/location.ts @@ -1,7 +1,7 @@ import * as v from 'valibot'; const locationSchema = v.object({ - url: z.string().url().catch(''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), description: v.fallback(v.string(), ''), country: v.fallback(v.string(), ''), locality: v.fallback(v.string(), ''), @@ -12,10 +12,10 @@ const locationSchema = v.object({ origin_provider: v.fallback(v.string(), ''), type: v.fallback(v.string(), ''), timezone: v.fallback(v.string(), ''), - geom: v.object({ - coordinates: z.tuple([z.number(), z.number()]).nullable().catch(null), + geom: v.fallback(v.nullable(v.object({ + coordinates: v.fallback(v.nullable(z.tuple([v.number(), v.number()])), null), srid: v.fallback(v.string(), ''), - }).nullable().catch(null), + })), null), }); type Location = v.InferOutput; diff --git a/packages/pl-api/lib/entities/marker.ts b/packages/pl-api/lib/entities/marker.ts index 597d88b12..07bb579e3 100644 --- a/packages/pl-api/lib/entities/marker.ts +++ b/packages/pl-api/lib/entities/marker.ts @@ -7,7 +7,7 @@ const markerSchema = z.preprocess((marker: any) => marker ? ({ ...marker, }) : null, v.object({ last_read_id: v.string(), - version: z.number().int(), + version: v.pipe(v.number(), v.integer()), updated_at: dateSchema, unread_count: z.number().int().optional().catch(undefined), })); @@ -15,7 +15,7 @@ const markerSchema = z.preprocess((marker: any) => marker ? ({ /** @see {@link https://docs.joinmastodon.org/entities/Marker/} */ type Marker = v.InferOutput; -const markersSchema = z.record(markerSchema); +const markersSchema = v.record(v.string(), markerSchema); type Markers = v.InferOutput; diff --git a/packages/pl-api/lib/entities/media-attachment.ts b/packages/pl-api/lib/entities/media-attachment.ts index 778e69eac..be24ac506 100644 --- a/packages/pl-api/lib/entities/media-attachment.ts +++ b/packages/pl-api/lib/entities/media-attachment.ts @@ -17,9 +17,9 @@ const blurhashSchema = z.string().superRefine((value, ctx) => { const baseAttachmentSchema = v.object({ id: v.string(), type: v.string(), - url: z.string().url().catch(''), - preview_url: z.string().url().catch(''), - remote_url: z.string().url().nullable().catch(null), + url: v.fallback(v.pipe(v.string(), v.url()), ''), + preview_url: v.fallback(v.pipe(v.string(), v.url()), ''), + remote_url: v.fallback(v.nullable(z.string().url()), null), description: v.fallback(v.string(), ''), blurhash: v.fallback(v.nullable(blurhashSchema), null), @@ -27,64 +27,64 @@ const baseAttachmentSchema = v.object({ }); const imageMetaSchema = v.object({ - width: z.number(), - height: z.number(), + width: v.number(), + height: v.number(), size: z.string().regex(/\d+x\d+$/).nullable().catch(null), aspect: v.fallback(v.nullable(v.number()), null), }); const imageAttachmentSchema = baseAttachmentSchema.extend({ - type: z.literal('image'), - meta: v.object({ - original: imageMetaSchema.optional().catch(undefined), - small: imageMetaSchema.optional().catch(undefined), - focus: v.object({ + type: v.literal('image'), + meta: v.fallback(v.object({ + original: v.fallback(v.optional(imageMetaSchema), undefined), + small: v.fallback(v.optional(imageMetaSchema), undefined), + focus: v.fallback(v.optional(v.object({ x: z.number().min(-1).max(1), y: z.number().min(-1).max(1), - }).optional().catch(undefined), - }).catch({}), + })), undefined), + }), {}), }); const videoAttachmentSchema = baseAttachmentSchema.extend({ - type: z.literal('video'), - meta: v.object({ - duration: z.number().optional().catch(undefined), - original: imageMetaSchema.extend({ - frame_rate: z.string().regex(/\d+\/\d+$/).nullable().catch(null), - duration: z.number().nonnegative().nullable().catch(null), - }).optional().catch(undefined), - small: imageMetaSchema.optional().catch(undefined), + type: v.literal('video'), + meta: v.fallback(v.object({ + duration: v.fallback(v.optional(v.number()), undefined), + original: v.fallback(v.optional(imageMetaSchema.extend({ + frame_rate: v.fallback(v.nullable(v.pipe(v.string(), v.regex(/\d+\/\d+$/))), null), + duration: v.fallback(v.nullable(z.number().nonnegative()), null), + })), undefined), + small: v.fallback(v.optional(imageMetaSchema), undefined), // WIP: add rest - }).catch({}), + }), {}), }); const gifvAttachmentSchema = baseAttachmentSchema.extend({ - type: z.literal('gifv'), - meta: v.object({ - duration: z.number().optional().catch(undefined), - original: imageMetaSchema.optional().catch(undefined), - }).catch({}), + type: v.literal('gifv'), + meta: v.fallback(v.object({ + duration: v.fallback(v.optional(v.number()), undefined), + original: v.fallback(v.optional(imageMetaSchema), undefined), + }), {}), }); const audioAttachmentSchema = baseAttachmentSchema.extend({ - type: z.literal('audio'), - meta: v.object({ - duration: z.number().optional().catch(undefined), - colors: v.object({ + type: v.literal('audio'), + meta: v.fallback(v.object({ + duration: v.fallback(v.optional(v.number()), undefined), + colors: v.fallback(v.optional(v.object({ background: v.fallback(v.optional(v.string()), undefined), foreground: v.fallback(v.optional(v.string()), undefined), accent: v.fallback(v.optional(v.string()), undefined), - duration: z.number().optional().catch(undefined), - }).optional().catch(undefined), - original: v.object({ - duration: z.number().optional().catch(undefined), + duration: v.fallback(v.optional(v.number()), undefined), + })), undefined), + original: v.fallback(v.optional(v.object({ + duration: v.fallback(v.optional(v.number()), undefined), bitrate: z.number().nonnegative().optional().catch(undefined), - }).optional().catch(undefined), - }).catch({}), + })), undefined), + }), {}), }); const unknownAttachmentSchema = baseAttachmentSchema.extend({ - type: z.literal('unknown'), + type: v.literal('unknown'), }); /** @see {@link https://docs.joinmastodon.org/entities/MediaAttachment} */ diff --git a/packages/pl-api/lib/entities/mention.ts b/packages/pl-api/lib/entities/mention.ts index 99a933d42..19cb4132b 100644 --- a/packages/pl-api/lib/entities/mention.ts +++ b/packages/pl-api/lib/entities/mention.ts @@ -4,7 +4,7 @@ import * as v from 'valibot'; const mentionSchema = v.object({ id: v.string(), username: v.fallback(v.string(), ''), - url: z.string().url().catch(''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), acct: v.string(), }).transform((mention) => { if (!mention.username) { diff --git a/packages/pl-api/lib/entities/notification-policy.ts b/packages/pl-api/lib/entities/notification-policy.ts index a4400cf81..ed964d15d 100644 --- a/packages/pl-api/lib/entities/notification-policy.ts +++ b/packages/pl-api/lib/entities/notification-policy.ts @@ -7,8 +7,8 @@ const notificationPolicySchema = v.object({ filter_new_accounts: v.boolean(), filter_private_mentions: v.boolean(), summary: v.object({ - pending_requests_count: z.number().int(), - pending_notifications_count: z.number().int(), + pending_requests_count: v.pipe(v.number(), v.integer()), + pending_notifications_count: v.pipe(v.number(), v.integer()), }), }); diff --git a/packages/pl-api/lib/entities/notification-request.ts b/packages/pl-api/lib/entities/notification-request.ts index 51ea8c841..75372ec7b 100644 --- a/packages/pl-api/lib/entities/notification-request.ts +++ b/packages/pl-api/lib/entities/notification-request.ts @@ -11,7 +11,7 @@ const notificationRequestSchema = v.object({ updated_at: dateSchema, account: accountSchema, notifications_count: z.coerce.string(), - last_status: statusSchema.optional().catch(undefined), + last_status: v.fallback(v.optional(statusSchema), undefined), }); type NotificationRequest = v.InferOutput; diff --git a/packages/pl-api/lib/entities/notification.ts b/packages/pl-api/lib/entities/notification.ts index 633ad6e8b..128344dcf 100644 --- a/packages/pl-api/lib/entities/notification.ts +++ b/packages/pl-api/lib/entities/notification.ts @@ -21,54 +21,54 @@ const baseNotificationSchema = v.object({ }); const accountNotificationSchema = baseNotificationSchema.extend({ - type: z.enum(['follow', 'follow_request', 'admin.sign_up', 'bite']), + type: v.picklist(['follow', 'follow_request', 'admin.sign_up', 'bite']), }); const mentionNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('mention'), - subtype: z.enum(['reply']).nullable().catch(null), + type: v.literal('mention'), + subtype: v.fallback(v.nullable(v.picklist(['reply'])), null), status: statusSchema, }); const statusNotificationSchema = baseNotificationSchema.extend({ - type: z.enum(['status', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']), + type: v.picklist(['status', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']), status: statusSchema, }); const reportNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('admin.report'), + type: v.literal('admin.report'), report: reportSchema, }); const severedRelationshipNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('severed_relationships'), + type: v.literal('severed_relationships'), relationship_severance_event: relationshipSeveranceEventSchema, }); const moderationWarningNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('moderation_warning'), + type: v.literal('moderation_warning'), moderation_warning: accountWarningSchema, }); const moveNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('move'), + type: v.literal('move'), target: accountSchema, }); const emojiReactionNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('emoji_reaction'), + type: v.literal('emoji_reaction'), emoji: v.string(), emoji_url: v.fallback(v.nullable(v.string()), null), status: statusSchema, }); const chatMentionNotificationSchema = baseNotificationSchema.extend({ - type: z.literal('chat_mention'), + type: v.literal('chat_mention'), chat_message: chatMessageSchema, }); const eventParticipationRequestNotificationSchema = baseNotificationSchema.extend({ - type: z.enum(['participation_accepted', 'participation_request']), + type: v.picklist(['participation_accepted', 'participation_request']), status: statusSchema, participation_message: v.fallback(v.nullable(v.string()), null), }); diff --git a/packages/pl-api/lib/entities/oauth-token.ts b/packages/pl-api/lib/entities/oauth-token.ts index cf9ea0931..cd97b9aac 100644 --- a/packages/pl-api/lib/entities/oauth-token.ts +++ b/packages/pl-api/lib/entities/oauth-token.ts @@ -6,7 +6,7 @@ const oauthTokenSchema = z.preprocess((token: any) => ({ valid_until: token?.valid_until?.padEnd(27, 'Z'), }), v.object({ app_name: v.string(), - id: z.number(), + id: v.number(), valid_until: z.string().datetime({ offset: true }), })); diff --git a/packages/pl-api/lib/entities/poll.ts b/packages/pl-api/lib/entities/poll.ts index 923dc3a9a..1862db118 100644 --- a/packages/pl-api/lib/entities/poll.ts +++ b/packages/pl-api/lib/entities/poll.ts @@ -7,20 +7,20 @@ const pollOptionSchema = v.object({ title: v.fallback(v.string(), ''), votes_count: v.fallback(v.number(), 0), - title_map: z.record(v.string()).nullable().catch(null), + title_map: v.record(v.string(), v.string()).nullable().catch(null), }); /** @see {@link https://docs.joinmastodon.org/entities/Poll/} */ const pollSchema = v.object({ emojis: filteredArray(customEmojiSchema), expired: v.fallback(v.boolean(), false), - expires_at: z.string().datetime().nullable().catch(null), + expires_at: v.fallback(v.nullable(z.string().datetime()), null), id: v.string(), multiple: v.fallback(v.boolean(), false), options: z.array(pollOptionSchema).min(2), voters_count: v.fallback(v.number(), 0), votes_count: v.fallback(v.number(), 0), - own_votes: z.array(z.number()).nonempty().nullable().catch(null), + own_votes: v.fallback(v.nullable(z.number()).nonempty(), null), voted: v.fallback(v.boolean(), false), non_anonymous: v.fallback(v.boolean(), false), diff --git a/packages/pl-api/lib/entities/preview-card.ts b/packages/pl-api/lib/entities/preview-card.ts index ad88f1f7f..04c4cdc9e 100644 --- a/packages/pl-api/lib/entities/preview-card.ts +++ b/packages/pl-api/lib/entities/preview-card.ts @@ -3,19 +3,19 @@ import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/} */ const previewCardSchema = v.object({ author_name: v.fallback(v.string(), ''), - author_url: z.string().url().catch(''), + author_url: v.fallback(v.pipe(v.string(), v.url()), ''), blurhash: v.fallback(v.nullable(v.string()), null), description: v.fallback(v.string(), ''), - embed_url: z.string().url().catch(''), + embed_url: v.fallback(v.pipe(v.string(), v.url()), ''), height: v.fallback(v.number(), 0), html: v.fallback(v.string(), ''), image: v.fallback(v.nullable(v.string()), null), image_description: v.fallback(v.string(), ''), provider_name: v.fallback(v.string(), ''), - provider_url: z.string().url().catch(''), + provider_url: v.fallback(v.pipe(v.string(), v.url()), ''), title: v.fallback(v.string(), ''), - type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'), - url: z.string().url(), + type: v.fallback(v.picklist(['link', 'photo', 'video', 'rich']), 'link'), + url: v.pipe(v.string(), v.url()), width: v.fallback(v.number(), 0), }); diff --git a/packages/pl-api/lib/entities/relationship-severance-event.ts b/packages/pl-api/lib/entities/relationship-severance-event.ts index 42970bf76..6ac0430ef 100644 --- a/packages/pl-api/lib/entities/relationship-severance-event.ts +++ b/packages/pl-api/lib/entities/relationship-severance-event.ts @@ -5,9 +5,9 @@ import { dateSchema } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/RelationshipSeveranceEvent/} */ const relationshipSeveranceEventSchema = v.object({ id: v.string(), - type: z.enum(['domain_block', 'user_domain_block', 'account_suspension']), + type: v.picklist(['domain_block', 'user_domain_block', 'account_suspension']), purged: v.string(), - relationships_count: z.number().optional().catch(undefined), + relationships_count: v.fallback(v.optional(v.number()), undefined), created_at: dateSchema, }); diff --git a/packages/pl-api/lib/entities/report.ts b/packages/pl-api/lib/entities/report.ts index 96b7c9d30..f9ab65d9f 100644 --- a/packages/pl-api/lib/entities/report.ts +++ b/packages/pl-api/lib/entities/report.ts @@ -11,9 +11,9 @@ const reportSchema = v.object({ category: v.fallback(v.optional(v.string()), undefined), comment: v.fallback(v.optional(v.string()), undefined), forwarded: v.fallback(v.optional(v.boolean()), undefined), - created_at: dateSchema.optional().catch(undefined), - status_ids: z.array(v.string()).nullable().catch(null), - rule_ids: z.array(v.string()).nullable().catch(null), + created_at: v.fallback(v.optional(dateSchema), undefined), + status_ids: v.fallback(v.nullable(v.string()), null), + rule_ids: v.fallback(v.nullable(v.string()), null), target_account: v.fallback(v.nullable(accountSchema), null), }); diff --git a/packages/pl-api/lib/entities/scheduled-status.ts b/packages/pl-api/lib/entities/scheduled-status.ts index 93ecbf9f7..a0ce78995 100644 --- a/packages/pl-api/lib/entities/scheduled-status.ts +++ b/packages/pl-api/lib/entities/scheduled-status.ts @@ -15,14 +15,14 @@ const scheduledStatusSchema = v.object({ multiple: v.fallback(v.optional(v.boolean()), undefined), hide_totals: v.fallback(v.optional(v.boolean()), undefined), }).nullable().catch(null), - media_ids: z.array(v.string()).nullable().catch(null), - sensitive: z.coerce.boolean().nullable().catch(null), + media_ids: v.fallback(v.nullable(v.string()), null), + sensitive: v.fallback(v.nullable(z.coerce.boolean()), null), spoiler_text: v.fallback(v.nullable(v.string()), null), visibility: z.string().catch('public'), in_reply_to_id: v.fallback(v.nullable(v.string()), null), language: v.fallback(v.nullable(v.string()), null), - application_id: z.number().int().nullable().catch(null), - scheduled_at: z.string().datetime({ offset: true }).nullable().catch(null), + application_id: v.fallback(v.nullable(z.number().int()), null), + scheduled_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), idempotency: v.fallback(v.nullable(v.string()), null), with_rate_limit: v.fallback(v.boolean(), false), diff --git a/packages/pl-api/lib/entities/status-source.ts b/packages/pl-api/lib/entities/status-source.ts index d811d3092..01516a200 100644 --- a/packages/pl-api/lib/entities/status-source.ts +++ b/packages/pl-api/lib/entities/status-source.ts @@ -11,8 +11,8 @@ const statusSourceSchema = v.object({ content_type: z.string().catch('text/plain'), location: v.fallback(v.nullable(locationSchema), null), - text_map: z.record(v.string()).nullable().catch(null), - spoiler_text_map: z.record(v.string()).nullable().catch(null), + text_map: v.record(v.string(), v.string()).nullable().catch(null), + spoiler_text_map: v.record(v.string(), v.string()).nullable().catch(null), }); type StatusSource = v.InferOutput; diff --git a/packages/pl-api/lib/entities/status.ts b/packages/pl-api/lib/entities/status.ts index 4abbed09f..8d3196965 100644 --- a/packages/pl-api/lib/entities/status.ts +++ b/packages/pl-api/lib/entities/status.ts @@ -17,13 +17,13 @@ import { dateSchema, filteredArray } from './utils'; const statusEventSchema = v.object({ name: v.fallback(v.string(), ''), - start_time: z.string().datetime().nullable().catch(null), - end_time: z.string().datetime().nullable().catch(null), - join_mode: z.enum(['free', 'restricted', 'invite']).nullable().catch(null), + start_time: v.fallback(v.nullable(z.string().datetime()), null), + end_time: v.fallback(v.nullable(z.string().datetime()), null), + join_mode: v.fallback(v.nullable(v.picklist(['free', 'restricted', 'invite'])), null), participants_count: v.fallback(v.number(), 0), - location: v.object({ + location: v.fallback(v.nullable(v.object({ name: v.fallback(v.string(), ''), - url: z.string().url().catch(''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), latitude: v.fallback(v.number(), 0), longitude: v.fallback(v.number(), 0), street: v.fallback(v.string(), ''), @@ -31,14 +31,14 @@ const statusEventSchema = v.object({ locality: v.fallback(v.string(), ''), region: v.fallback(v.string(), ''), country: v.fallback(v.string(), ''), - }).nullable().catch(null), - join_state: z.enum(['pending', 'reject', 'accept']).nullable().catch(null), + })), null), + join_state: v.fallback(v.nullable(v.picklist(['pending', 'reject', 'accept'])), null), }); /** @see {@link https://docs.joinmastodon.org/entities/Status/} */ const baseStatusSchema = v.object({ id: v.string(), - uri: z.string().url().catch(''), + uri: v.fallback(v.pipe(v.string(), v.url()), ''), created_at: dateSchema, account: accountSchema, content: v.fallback(v.string(), ''), @@ -46,55 +46,55 @@ const baseStatusSchema = v.object({ sensitive: z.coerce.boolean(), spoiler_text: v.fallback(v.string(), ''), media_attachments: filteredArray(mediaAttachmentSchema), - application: v.object({ + application: v.fallback(v.nullable(v.object({ name: v.string(), - website: z.string().url().nullable().catch(null), - }).nullable().catch(null), + website: v.fallback(v.nullable(z.string().url()), null), + })), null), mentions: filteredArray(mentionSchema), tags: filteredArray(tagSchema), emojis: filteredArray(customEmojiSchema), reblogs_count: v.fallback(v.number(), 0), favourites_count: v.fallback(v.number(), 0), replies_count: v.fallback(v.number(), 0), - url: z.string().url().catch(''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), in_reply_to_id: v.fallback(v.nullable(v.string()), null), in_reply_to_account_id: v.fallback(v.nullable(v.string()), null), poll: v.fallback(v.nullable(pollSchema), null), card: v.fallback(v.nullable(previewCardSchema), null), language: v.fallback(v.nullable(v.string()), null), text: v.fallback(v.nullable(v.string()), null), - edited_at: z.string().datetime().nullable().catch(null), + edited_at: v.fallback(v.nullable(z.string().datetime()), null), favourited: z.coerce.boolean(), reblogged: z.coerce.boolean(), muted: z.coerce.boolean(), bookmarked: z.coerce.boolean(), pinned: z.coerce.boolean(), filtered: filteredArray(filterResultSchema), - approval_status: z.enum(['pending', 'approval', 'rejected']).nullable().catch(null), + approval_status: v.fallback(v.nullable(v.picklist(['pending', 'approval', 'rejected'])), null), group: v.fallback(v.nullable(groupSchema), null), - scheduled_at: z.null().catch(null), + scheduled_at: v.fallback(v.null(), null), quote_id: v.fallback(v.nullable(v.string()), null), local: v.fallback(v.optional(v.boolean()), undefined), conversation_id: v.fallback(v.optional(v.string()), undefined), direct_conversation_id: v.fallback(v.optional(v.string()), undefined), in_reply_to_account_acct: v.fallback(v.optional(v.string()), undefined), - expires_at: z.string().datetime({ offset: true }).optional().catch(undefined), + expires_at: v.fallback(v.optional(z.string().datetime({ offset: true })), undefined), thread_muted: v.fallback(v.optional(v.boolean()), undefined), emoji_reactions: filteredArray(emojiReactionSchema), parent_visible: v.fallback(v.optional(v.boolean()), undefined), - pinned_at: z.string().datetime({ offset: true }).nullable().catch(null), + pinned_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), quote_visible: v.fallback(v.optional(v.boolean()), undefined), quote_url: v.fallback(v.optional(v.string()), undefined), quotes_count: v.fallback(v.number(), 0), bookmark_folder: v.fallback(v.nullable(v.string()), null), event: v.fallback(v.nullable(statusEventSchema), null), - translation: translationSchema.nullable().or(z.literal(false)).catch(null), + translation: translationSchema.nullable().or(v.literal(false)).catch(null), - content_map: z.record(v.string()).nullable().catch(null), - text_map: z.record(v.string()).nullable().catch(null), - spoiler_text_map: z.record(v.string()).nullable().catch(null), + content_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), + text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), + spoiler_text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), dislikes_count: v.fallback(v.number(), 0), disliked: z.coerce.boolean().catch(false), @@ -135,16 +135,16 @@ const preprocess = (status: any) => { }; const statusSchema: z.ZodType = z.preprocess(preprocess, baseStatusSchema.extend({ - reblog: z.lazy(() => statusSchema).nullable().catch(null), + reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), - quote: z.lazy(() => statusSchema).nullable().catch(null), + quote: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), })) as any; const statusWithoutAccountSchema = z.preprocess(preprocess, baseStatusSchema.omit({ account: true }).extend({ account: v.fallback(v.nullable(accountSchema), null), - reblog: z.lazy(() => statusSchema).nullable().catch(null), + reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), - quote: z.lazy(() => statusSchema).nullable().catch(null), + quote: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), })); type Status = v.InferOutput & { diff --git a/packages/pl-api/lib/entities/streaming-event.ts b/packages/pl-api/lib/entities/streaming-event.ts index 758fe2e31..cdd3ffabf 100644 --- a/packages/pl-api/lib/entities/streaming-event.ts +++ b/packages/pl-api/lib/entities/streaming-event.ts @@ -9,7 +9,7 @@ import { notificationSchema } from './notification'; import { statusSchema } from './status'; const followRelationshipUpdateSchema = v.object({ - state: z.enum(['follow_pending', 'follow_accept', 'follow_reject']), + state: v.picklist(['follow_pending', 'follow_accept', 'follow_reject']), follower: v.object({ id: v.string(), follower_count: v.fallback(v.nullable(v.number()), null), @@ -29,59 +29,59 @@ const baseStreamingEventSchema = v.object({ }); const statusStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.enum(['update', 'status.update']), + event: v.picklist(['update', 'status.update']), payload: z.preprocess((payload: any) => JSON.parse(payload), statusSchema), }); const stringStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.enum(['delete', 'announcement.delete']), + event: v.picklist(['delete', 'announcement.delete']), payload: v.string(), }); const notificationStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('notification'), + event: v.literal('notification'), payload: z.preprocess((payload: any) => JSON.parse(payload), notificationSchema), }); const emptyStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('filters_changed'), + event: v.literal('filters_changed'), }); const conversationStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('conversation'), + event: v.literal('conversation'), payload: z.preprocess((payload: any) => JSON.parse(payload), conversationSchema), }); const announcementStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('announcement'), + event: v.literal('announcement'), payload: z.preprocess((payload: any) => JSON.parse(payload), announcementSchema), }); const announcementReactionStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('announcement.reaction'), + event: v.literal('announcement.reaction'), payload: z.preprocess((payload: any) => JSON.parse(payload), announcementReactionSchema), }); const chatUpdateStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('chat_update'), + event: v.literal('chat_update'), payload: z.preprocess((payload: any) => JSON.parse(payload), chatSchema), }); const followRelationshipsUpdateStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('follow_relationships_update'), + event: v.literal('follow_relationships_update'), payload: z.preprocess((payload: any) => JSON.parse(payload), followRelationshipUpdateSchema), }); const respondStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('respond'), + event: v.literal('respond'), payload: z.preprocess((payload: any) => JSON.parse(payload), v.object({ type: v.string(), - result: z.enum(['success', 'ignored', 'error']), + result: v.picklist(['success', 'ignored', 'error']), })), }); const markerStreamingEventSchema = baseStreamingEventSchema.extend({ - event: z.literal('marker'), + event: v.literal('marker'), payload: z.preprocess((payload: any) => JSON.parse(payload), markersSchema), }); diff --git a/packages/pl-api/lib/entities/tag.ts b/packages/pl-api/lib/entities/tag.ts index fea9a679c..8c35bcc3b 100644 --- a/packages/pl-api/lib/entities/tag.ts +++ b/packages/pl-api/lib/entities/tag.ts @@ -9,8 +9,8 @@ const historySchema = v.object({ /** @see {@link https://docs.joinmastodon.org/entities/tag} */ const tagSchema = v.object({ name: z.string().min(1), - url: z.string().url().catch(''), - history: z.array(historySchema).nullable().catch(null), + url: v.fallback(v.pipe(v.string(), v.url()), ''), + history: v.fallback(v.nullable(historySchema), null), following: v.fallback(v.optional(v.boolean()), undefined), }); diff --git a/packages/pl-api/lib/entities/token.ts b/packages/pl-api/lib/entities/token.ts index 59d7316dd..3fff4e40b 100644 --- a/packages/pl-api/lib/entities/token.ts +++ b/packages/pl-api/lib/entities/token.ts @@ -5,11 +5,11 @@ const tokenSchema = v.object({ access_token: v.string(), token_type: v.string(), scope: v.string(), - created_at: z.number().optional().catch(undefined), + created_at: v.fallback(v.optional(v.number()), undefined), - id: z.number().optional().catch(undefined), + id: v.fallback(v.optional(v.number()), undefined), refresh_token: v.fallback(v.optional(v.string()), undefined), - expires_in: z.number().optional().catch(undefined), + expires_in: v.fallback(v.optional(v.number()), undefined), me: v.fallback(v.optional(v.string()), undefined), }); diff --git a/packages/pl-api/lib/entities/translation.ts b/packages/pl-api/lib/entities/translation.ts index b844818fb..4ccc310d1 100644 --- a/packages/pl-api/lib/entities/translation.ts +++ b/packages/pl-api/lib/entities/translation.ts @@ -31,7 +31,7 @@ const translationSchema = z.preprocess((translation: any) => { id: v.fallback(v.nullable(v.string()), null), content: v.fallback(v.string(), ''), spoiler_text: v.fallback(v.string(), ''), - poll: translationPollSchema.optional().catch(undefined), + poll: v.fallback(v.optional(translationPollSchema), undefined), media_attachments: filteredArray(translationMediaAttachment), detected_source_language: v.string(), provider: v.string(), diff --git a/packages/pl-api/lib/entities/trends-link.ts b/packages/pl-api/lib/entities/trends-link.ts index 227e10cb1..f43e73f0c 100644 --- a/packages/pl-api/lib/entities/trends-link.ts +++ b/packages/pl-api/lib/entities/trends-link.ts @@ -6,10 +6,10 @@ import { historySchema } from './tag'; /** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/#trends-link} */ const trendsLinkSchema = z.preprocess((link: any) => ({ ...link, id: link.url }), v.object({ id: v.fallback(v.string(), ''), - url: z.string().url().catch(''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), title: v.fallback(v.string(), ''), description: v.fallback(v.string(), ''), - type: z.enum(['link', 'photo', 'video', 'rich']).catch('link'), + type: v.fallback(v.picklist(['link', 'photo', 'video', 'rich']), 'link'), author_name: v.fallback(v.string(), ''), author_url: v.fallback(v.string(), ''), provider_name: v.fallback(v.string(), ''), @@ -21,7 +21,7 @@ const trendsLinkSchema = z.preprocess((link: any) => ({ ...link, id: link.url }) image_description: v.fallback(v.nullable(v.string()), null), embed_url: v.fallback(v.string(), ''), blurhash: v.fallback(v.nullable(blurhashSchema), null), - history: z.array(historySchema).nullable().catch(null), + history: v.fallback(v.nullable(historySchema), null), })); type TrendsLink = v.InferOutput; diff --git a/packages/pl-api/lib/entities/utils.ts b/packages/pl-api/lib/entities/utils.ts index 5ebab3a53..4bbadbc5c 100644 --- a/packages/pl-api/lib/entities/utils.ts +++ b/packages/pl-api/lib/entities/utils.ts @@ -14,13 +14,17 @@ const filteredArray = (schema: T) => )); /** Validates the string as an emoji. */ -const emojiSchema = z.string().refine((v) => /\p{Extended_Pictographic}|[\u{1F1E6}-\u{1F1FF}]{2}/u.test(v)); +const emojiSchema = v.pipe(v.string(), v.emoji()); /** MIME schema, eg `image/png`. */ -const mimeSchema = z.string().regex(/^\w+\/[-+.\w]+$/); +const mimeSchema = v.pipe(v.string(), v.regex(/^\w+\/[-+.\w]+$/)); -/** zod schema to force the value into an object, if it isn't already. */ -const coerceObject = (shape: T) => - v.object({}).passthrough().catch({}).pipe(z.object(shape)); +/** valibot schema to force the value into an object, if it isn't already. */ +const coerceObject = (shape: T) => + v.pipe( + v.any(), + v.transform((input) => typeof input === 'object' ? input : {}), + v.object(shape), + ); export { filteredArray, emojiSchema, dateSchema, mimeSchema, coerceObject }; diff --git a/packages/pl-api/lib/entities/web-push-subscription.ts b/packages/pl-api/lib/entities/web-push-subscription.ts index 24ad82665..5632ae40d 100644 --- a/packages/pl-api/lib/entities/web-push-subscription.ts +++ b/packages/pl-api/lib/entities/web-push-subscription.ts @@ -4,7 +4,7 @@ import * as v from 'valibot'; const webPushSubscriptionSchema = v.object({ id: z.coerce.string(), endpoint: v.string(), - alerts: z.record(z.boolean()), + alerts: v.record(v.string(), z.boolean()), server_key: v.string(), }); diff --git a/packages/pl-fe/src/schemas/utils.ts b/packages/pl-fe/src/schemas/utils.ts index d4038009e..775a349c6 100644 --- a/packages/pl-fe/src/schemas/utils.ts +++ b/packages/pl-fe/src/schemas/utils.ts @@ -20,7 +20,7 @@ const makeCustomEmojiMap = (customEmojis: CustomEmoji[]) => return result; }, {}); -/** zod schema to force the value into an object, if it isn't already. */ +/** valibot schema to force the value into an object, if it isn't already. */ const coerceObject = (shape: T) => v.pipe( v.any(), From 0bed543c20b9463511f90639ddcd8a3ccd609af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 14 Oct 2024 22:50:34 +0200 Subject: [PATCH 13/28] pl-api: moar blind search and replace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/lib/client.ts | 8 ++-- packages/pl-api/lib/entities/account.ts | 14 +++---- packages/pl-api/lib/entities/admin/account.ts | 4 +- packages/pl-api/lib/entities/admin/cohort.ts | 2 +- packages/pl-api/lib/entities/admin/domain.ts | 2 +- .../lib/entities/admin/email-domain-block.ts | 6 +-- packages/pl-api/lib/entities/admin/measure.ts | 8 ++-- .../entities/admin/moderation-log-entry.ts | 2 +- packages/pl-api/lib/entities/backup.ts | 2 +- .../pl-api/lib/entities/bookmark-folder.ts | 2 +- packages/pl-api/lib/entities/context.ts | 4 +- .../pl-api/lib/entities/directory/category.ts | 2 +- .../pl-api/lib/entities/directory/language.ts | 2 +- .../pl-api/lib/entities/directory/server.ts | 10 ++--- .../entities/directory/statistics-period.ts | 6 +-- .../pl-api/lib/entities/emoji-reaction.ts | 7 ++-- packages/pl-api/lib/entities/group.ts | 4 +- packages/pl-api/lib/entities/instance.ts | 10 ++--- packages/pl-api/lib/entities/list.ts | 2 +- packages/pl-api/lib/entities/marker.ts | 2 +- .../pl-api/lib/entities/media-attachment.ts | 21 +++++++---- .../lib/entities/notification-request.ts | 2 +- packages/pl-api/lib/entities/poll.ts | 4 +- .../pl-api/lib/entities/scheduled-status.ts | 14 +++---- packages/pl-api/lib/entities/scrobble.ts | 2 +- packages/pl-api/lib/entities/status-edit.ts | 8 ++-- packages/pl-api/lib/entities/status.ts | 18 ++++----- .../pl-api/lib/entities/streaming-event.ts | 37 ++++++++++++------- packages/pl-api/lib/entities/suggestion.ts | 2 +- packages/pl-api/lib/entities/tag.ts | 6 +-- packages/pl-api/lib/entities/translation.ts | 2 +- .../lib/entities/web-push-subscription.ts | 4 +- 32 files changed, 118 insertions(+), 101 deletions(-) diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts index 9e423ed81..ad955093b 100644 --- a/packages/pl-api/lib/client.ts +++ b/packages/pl-api/lib/client.ts @@ -201,7 +201,7 @@ class PlApiClient { baseURL: string; #accessToken?: string; - #instance: Instance = instanceSchema.parse({}); + #instance: Instance = v.parse(instanceSchema, {}); public request = request.bind(this) as typeof request; public features: Features = getFeatures(this.#instance); #socket?: { @@ -2714,9 +2714,9 @@ class PlApiClient { return v.parse(v.array(v.object({ week: v.string(), - statuses: z.coerce.string(), - logins: z.coerce.string(), - registrations: z.coerce.string(), + statuses: v.pipe(v.unknown(), v.transform(String)), + logins: v.pipe(v.unknown(), v.transform(String)), + registrations: v.pipe(v.unknown(), v.transform(String)), })), response.json); }, diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index 5af5a0e2d..c9816f3b9 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -7,7 +7,7 @@ import { roleSchema } from './role'; import { coerceObject, dateSchema, filteredArray } from './utils'; const filterBadges = (tags?: string[]) => - tags?.filter(tag => tag.startsWith('badge:')).map(tag => roleSchema.parse({ id: tag, name: tag.replace(/^badge:/, '') })); + tags?.filter(tag => tag.startsWith('badge:')).map(tag => v.parse(roleSchema, { id: tag, name: tag.replace(/^badge:/, '') })); const preprocessAccount = (account: any) => { if (!account?.acct) return null; @@ -112,7 +112,7 @@ const baseAccountSchema = v.object({ deactivated: v.fallback(v.optional(v.boolean()), undefined), location: v.fallback(v.optional(v.string()), undefined), - local: z.boolean().optional().catch(false), + local: v.fallback(v.optional(v.boolean()), false), avatar_description: v.fallback(v.string(), ''), enable_rss: v.fallback(v.boolean(), false), @@ -142,7 +142,7 @@ type Account = v.InferOutput & WithMoved; const accountSchema: z.ZodType = untypedAccountSchema as any; const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({ - source: v.object({ + source: v.fallback(v.nullable(v.object({ note: v.fallback(v.string(), ''), fields: filteredArray(fieldSchema), privacy: v.picklist(['public', 'unlisted', 'private', 'direct']), @@ -150,15 +150,15 @@ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWi language: v.fallback(v.nullable(v.string()), null), follow_requests_count: z.number().int().nonnegative().catch(0), - show_role: z.boolean().optional().nullable().catch(undefined), - no_rich_text: z.boolean().optional().nullable().catch(undefined), + show_role: v.fallback(v.nullable(v.optional(v.boolean())), undefined), + no_rich_text: v.fallback(v.nullable(v.optional(v.boolean())), undefined), discoverable: v.fallback(v.optional(v.boolean()), undefined), actor_type: v.fallback(v.optional(v.string()), undefined), show_birthday: v.fallback(v.optional(v.boolean()), undefined), - }).nullable().catch(null), + })), null), role: v.fallback(v.nullable(roleSchema), null), - settings_store: v.record(v.string(), v.any()).optional().catch(undefined), + settings_store: v.fallback(v.optional(v.record(v.string(), v.any())), undefined), chat_token: v.fallback(v.optional(v.string()), undefined), allow_following_move: v.fallback(v.optional(v.boolean()), undefined), unread_conversation_count: v.fallback(v.optional(v.number()), undefined), diff --git a/packages/pl-api/lib/entities/admin/account.ts b/packages/pl-api/lib/entities/admin/account.ts index 8b829ae86..74fc5e1cc 100644 --- a/packages/pl-api/lib/entities/admin/account.ts +++ b/packages/pl-api/lib/entities/admin/account.ts @@ -22,9 +22,9 @@ const adminAccountSchema = z.preprocess((account: any) => { email: account.email, invite_request: account.registration_reason, role: account.roles?.is_admin - ? roleSchema.parse({ name: 'Admin' }) + ? v.parse(roleSchema, { name: 'Admin' }) : account.roles?.moderator - ? roleSchema.parse({ name: 'Moderator ' }) : + ? v.parse(roleSchema, { name: 'Moderator ' }) : null, confirmed: account.is_confirmed, approved: account.is_approved, diff --git a/packages/pl-api/lib/entities/admin/cohort.ts b/packages/pl-api/lib/entities/admin/cohort.ts index af4cdcdfd..5275ee50a 100644 --- a/packages/pl-api/lib/entities/admin/cohort.ts +++ b/packages/pl-api/lib/entities/admin/cohort.ts @@ -4,7 +4,7 @@ import * as v from 'valibot'; const adminCohortSchema = v.object({ period: z.string().datetime({ offset: true }), frequency: v.picklist(['day', 'month']), - data: z.array(v.object({ + data: v.array(v.object({ date: z.string().datetime({ offset: true }), rate: v.number(), value: v.pipe(v.number(), v.integer()), diff --git a/packages/pl-api/lib/entities/admin/domain.ts b/packages/pl-api/lib/entities/admin/domain.ts index 67d6253ad..2eb5e6a49 100644 --- a/packages/pl-api/lib/entities/admin/domain.ts +++ b/packages/pl-api/lib/entities/admin/domain.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; const adminDomainSchema = v.object({ domain: v.fallback(v.string(), ''), - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), public: v.fallback(v.boolean(), false), resolves: v.fallback(v.boolean(), false), last_checked_at: z.string().datetime().catch(''), diff --git a/packages/pl-api/lib/entities/admin/email-domain-block.ts b/packages/pl-api/lib/entities/admin/email-domain-block.ts index d3c57e179..5c5e8ecd6 100644 --- a/packages/pl-api/lib/entities/admin/email-domain-block.ts +++ b/packages/pl-api/lib/entities/admin/email-domain-block.ts @@ -8,9 +8,9 @@ const adminEmailDomainBlockSchema = v.object({ domain: v.string(), created_at: dateSchema, history: v.array(v.object({ - day: z.coerce.string(), - accounts: z.coerce.string(), - uses: z.coerce.string(), + day: v.pipe(v.unknown(), v.transform(String)), + accounts: v.pipe(v.unknown(), v.transform(String)), + uses: v.pipe(v.unknown(), v.transform(String)), })), }); diff --git a/packages/pl-api/lib/entities/admin/measure.ts b/packages/pl-api/lib/entities/admin/measure.ts index f1c9cb8de..87ccc44f2 100644 --- a/packages/pl-api/lib/entities/admin/measure.ts +++ b/packages/pl-api/lib/entities/admin/measure.ts @@ -4,12 +4,12 @@ import * as v from 'valibot'; const adminMeasureSchema = v.object({ key: v.string(), unit: v.fallback(v.nullable(v.string()), null), - total: z.coerce.number(), + total: v.pipe(v.unknown(), v.transform(Number)), human_value: v.fallback(v.optional(v.string()), undefined), - previous_total: z.coerce.string().optional().catch(undefined), - data: z.array(v.object({ + previous_total: v.fallback(v.optional(v.pipe(v.unknown(), v.transform(String))), undefined), + data: v.array(v.object({ date: z.string().datetime({ offset: true }), - value: z.coerce.string(), + value: v.pipe(v.unknown(), v.transform(String)), })), }); diff --git a/packages/pl-api/lib/entities/admin/moderation-log-entry.ts b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts index 86ce01075..81300d5c3 100644 --- a/packages/pl-api/lib/entities/admin/moderation-log-entry.ts +++ b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; /** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminmoderation_log} */ const adminModerationLogEntrySchema = v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), data: v.fallback(v.record(v.string(), v.any()), {}), time: v.fallback(v.number(), 0), message: v.fallback(v.string(), ''), diff --git a/packages/pl-api/lib/entities/backup.ts b/packages/pl-api/lib/entities/backup.ts index 9c4acc3d8..ddc10ee0f 100644 --- a/packages/pl-api/lib/entities/backup.ts +++ b/packages/pl-api/lib/entities/backup.ts @@ -4,7 +4,7 @@ import { dateSchema, mimeSchema } from './utils'; /** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromabackups} */ const backupSchema = v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), contentType: mimeSchema, file_size: v.fallback(v.number(), 0), inserted_at: dateSchema, diff --git a/packages/pl-api/lib/entities/bookmark-folder.ts b/packages/pl-api/lib/entities/bookmark-folder.ts index edd634cf8..d07c78004 100644 --- a/packages/pl-api/lib/entities/bookmark-folder.ts +++ b/packages/pl-api/lib/entities/bookmark-folder.ts @@ -1,7 +1,7 @@ import * as v from 'valibot'; const bookmarkFolderSchema = v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), name: v.fallback(v.string(), ''), emoji: v.fallback(v.nullable(v.string()), null), emoji_url: v.fallback(v.nullable(v.string()), null), diff --git a/packages/pl-api/lib/entities/context.ts b/packages/pl-api/lib/entities/context.ts index 75c32d786..37449629b 100644 --- a/packages/pl-api/lib/entities/context.ts +++ b/packages/pl-api/lib/entities/context.ts @@ -4,8 +4,8 @@ import { statusSchema } from './status'; /** @see {@link https://docs.joinmastodon.org/entities/Context/} */ const contextSchema = v.object({ - ancestors: z.array(statusSchema), - descendants: z.array(statusSchema), + ancestors: v.array(statusSchema), + descendants: v.array(statusSchema), }); type Context = v.InferOutput; diff --git a/packages/pl-api/lib/entities/directory/category.ts b/packages/pl-api/lib/entities/directory/category.ts index 605d8ab0e..c9dbca386 100644 --- a/packages/pl-api/lib/entities/directory/category.ts +++ b/packages/pl-api/lib/entities/directory/category.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; const directoryCategorySchema = v.object({ category: v.string(), - servers_count: v.fallback(v.nullable(z.coerce.number()), null), + servers_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), }); type DirectoryCategory = v.InferOutput; diff --git a/packages/pl-api/lib/entities/directory/language.ts b/packages/pl-api/lib/entities/directory/language.ts index 2c0c330a6..346873325 100644 --- a/packages/pl-api/lib/entities/directory/language.ts +++ b/packages/pl-api/lib/entities/directory/language.ts @@ -3,7 +3,7 @@ import * as v from 'valibot'; const directoryLanguageSchema = v.object({ locale: v.string(), language: v.string(), - servers_count: v.fallback(v.nullable(z.coerce.number()), null), + servers_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), }); type DirectoryLanguage = v.InferOutput; diff --git a/packages/pl-api/lib/entities/directory/server.ts b/packages/pl-api/lib/entities/directory/server.ts index 3c25e44bb..ef8ea1b12 100644 --- a/packages/pl-api/lib/entities/directory/server.ts +++ b/packages/pl-api/lib/entities/directory/server.ts @@ -4,13 +4,13 @@ const directoryServerSchema = v.object({ domain: v.string(), version: v.string(), description: v.string(), - languages: z.array(v.string()), + languages: v.array(v.string()), region: v.string(), - categories: z.array(v.string()), - proxied_thumbnail: v.fallback(v.nullable(z.string().url()), null), + categories: v.array(v.string()), + proxied_thumbnail: v.fallback(v.nullable(v.pipe(v.string(), v.url())), null), blurhash: v.fallback(v.nullable(v.string()), null), - total_users: z.coerce.number(), - last_week_users: z.coerce.number(), + total_users: v.pipe(v.unknown(), v.transform(Number)), + last_week_users: v.pipe(v.unknown(), v.transform(Number)), approval_required: v.boolean(), language: v.string(), category: v.string(), diff --git a/packages/pl-api/lib/entities/directory/statistics-period.ts b/packages/pl-api/lib/entities/directory/statistics-period.ts index f5c909c16..e48264329 100644 --- a/packages/pl-api/lib/entities/directory/statistics-period.ts +++ b/packages/pl-api/lib/entities/directory/statistics-period.ts @@ -2,9 +2,9 @@ import * as v from 'valibot'; const directoryStatisticsPeriodSchema = v.object({ period: z.string().date(), - server_count: v.fallback(v.nullable(z.coerce.number()), null), - user_count: v.fallback(v.nullable(z.coerce.number()), null), - active_user_count: v.fallback(v.nullable(z.coerce.number()), null), + server_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), + user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), + active_user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), }); type DirectoryStatisticsPeriod = v.InferOutput; diff --git a/packages/pl-api/lib/entities/emoji-reaction.ts b/packages/pl-api/lib/entities/emoji-reaction.ts index 8b66d12f9..0e46529fc 100644 --- a/packages/pl-api/lib/entities/emoji-reaction.ts +++ b/packages/pl-api/lib/entities/emoji-reaction.ts @@ -10,10 +10,11 @@ const baseEmojiReactionSchema = v.object({ url: v.fallback(v.undefined(), undefined), static_url: v.fallback(v.undefined(), undefined), accounts: filteredArray(accountSchema), - account_ids: filteredArray(v.string()).catch([]), + account_ids: v.fallback(filteredArray(v.string()), []), }); -const customEmojiReactionSchema = baseEmojiReactionSchema.extend({ +const customEmojiReactionSchema = v.object({ + ...baseEmojiReactionSchema.entries, name: v.string(), url: v.pipe(v.string(), v.url()), static_url: v.pipe(v.string(), v.url()), @@ -27,7 +28,7 @@ const emojiReactionSchema = z.preprocess((reaction: any) => reaction ? { static_url: reaction.url, account_ids: reaction.accounts?.map((account: any) => account?.id), ...reaction, -} : null, baseEmojiReactionSchema.or(customEmojiReactionSchema)); +} : null, v.union([baseEmojiReactionSchema, customEmojiReactionSchema]); type EmojiReaction = v.InferOutput; diff --git a/packages/pl-api/lib/entities/group.ts b/packages/pl-api/lib/entities/group.ts index ee8f8bed8..1e0884696 100644 --- a/packages/pl-api/lib/entities/group.ts +++ b/packages/pl-api/lib/entities/group.ts @@ -13,11 +13,11 @@ const groupSchema = v.object({ emojis: filteredArray(customEmojiSchema), header: v.fallback(v.string(), ''), header_static: v.fallback(v.string(), ''), - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), locked: v.fallback(v.boolean(), false), membership_required: v.fallback(v.boolean(), false), members_count: v.fallback(v.number(), 0), - owner: v.object({ id: v.string() }).nullable().catch(null), + owner: v.fallback(v.nullable(v.object({ id: v.string() })), null), note: z.string().transform(note => note === '

' ? '' : note).catch(''), relationship: v.fallback(v.nullable(groupRelationshipSchema), null), // Dummy field to be overwritten later statuses_visibility: v.fallback(v.string(), 'public'), diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts index 4a1ebdb40..d289ec4d8 100644 --- a/packages/pl-api/lib/entities/instance.ts +++ b/packages/pl-api/lib/entities/instance.ts @@ -36,7 +36,7 @@ const instanceV1ToV2 = (data: any) => { uri, urls, ...instance - } = instanceV1Schema.parse(data); + } = v.parse(instanceV1Schema, data); return { ...instance, @@ -196,14 +196,14 @@ const pleromaSchema = coerceObject({ multitenancy: coerceObject({ domains: v.optional(v.array( v.object({ - domain: z.coerce.string(), + domain: v.pipe(v.unknown(), v.transform(String)), id: v.string(), public: v.fallback(v.boolean(), false), }), )), enabled: v.fallback(v.boolean(), false), }), - post_formats: z.string().array().optional().catch(undefined), + post_formats: v.fallback(v.optional(v.array(v.string())), undefined), restrict_unauthenticated: coerceObject({ activities: coerceObject({ local: v.fallback(v.boolean(), false), @@ -222,8 +222,8 @@ const pleromaSchema = coerceObject({ translation: coerceObject({ allow_remote: v.fallback(v.boolean(), true), allow_unauthenticated: v.fallback(v.boolean(), false), - source_languages: z.string().array().optional().catch(undefined), - target_languages: z.string().array().optional().catch(undefined), + source_languages: v.fallback(v.optional(v.array(v.string())), undefined), + target_languages: v.fallback(v.optional(v.array(v.string())), undefined), }), }), oauth_consumer_strategies: v.fallback(v.array(v.string()), []), diff --git a/packages/pl-api/lib/entities/list.ts b/packages/pl-api/lib/entities/list.ts index 2a0cac1c4..2ffc350a4 100644 --- a/packages/pl-api/lib/entities/list.ts +++ b/packages/pl-api/lib/entities/list.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/List/} */ const listSchema = v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), title: v.string(), replies_policy: v.fallback(v.optional(v.string()), undefined), exclusive: v.fallback(v.optional(v.boolean()), undefined), diff --git a/packages/pl-api/lib/entities/marker.ts b/packages/pl-api/lib/entities/marker.ts index 07bb579e3..9576a9803 100644 --- a/packages/pl-api/lib/entities/marker.ts +++ b/packages/pl-api/lib/entities/marker.ts @@ -9,7 +9,7 @@ const markerSchema = z.preprocess((marker: any) => marker ? ({ last_read_id: v.string(), version: v.pipe(v.number(), v.integer()), updated_at: dateSchema, - unread_count: z.number().int().optional().catch(undefined), + unread_count: v.fallback(v.optional(v.pipe(v.number(), v.integer())), undefined), })); /** @see {@link https://docs.joinmastodon.org/entities/Marker/} */ diff --git a/packages/pl-api/lib/entities/media-attachment.ts b/packages/pl-api/lib/entities/media-attachment.ts index be24ac506..341378f8a 100644 --- a/packages/pl-api/lib/entities/media-attachment.ts +++ b/packages/pl-api/lib/entities/media-attachment.ts @@ -19,7 +19,7 @@ const baseAttachmentSchema = v.object({ type: v.string(), url: v.fallback(v.pipe(v.string(), v.url()), ''), preview_url: v.fallback(v.pipe(v.string(), v.url()), ''), - remote_url: v.fallback(v.nullable(z.string().url()), null), + remote_url: v.fallback(v.nullable(v.pipe(v.string(), v.url())), null), description: v.fallback(v.string(), ''), blurhash: v.fallback(v.nullable(blurhashSchema), null), @@ -29,11 +29,12 @@ const baseAttachmentSchema = v.object({ const imageMetaSchema = v.object({ width: v.number(), height: v.number(), - size: z.string().regex(/\d+x\d+$/).nullable().catch(null), + size: v.fallback(v.nullable(v.pipe(v.string(), v.regex(/\d+x\d+$/))), null), aspect: v.fallback(v.nullable(v.number()), null), }); -const imageAttachmentSchema = baseAttachmentSchema.extend({ +const imageAttachmentSchema = v.object({ + ...baseAttachmentSchema.entries, type: v.literal('image'), meta: v.fallback(v.object({ original: v.fallback(v.optional(imageMetaSchema), undefined), @@ -45,7 +46,8 @@ const imageAttachmentSchema = baseAttachmentSchema.extend({ }), {}), }); -const videoAttachmentSchema = baseAttachmentSchema.extend({ +const videoAttachmentSchema = v.object({ + ...baseAttachmentSchema.entries, type: v.literal('video'), meta: v.fallback(v.object({ duration: v.fallback(v.optional(v.number()), undefined), @@ -58,7 +60,8 @@ const videoAttachmentSchema = baseAttachmentSchema.extend({ }), {}), }); -const gifvAttachmentSchema = baseAttachmentSchema.extend({ +const gifvAttachmentSchema = v.object({ + ...baseAttachmentSchema.entries, type: v.literal('gifv'), meta: v.fallback(v.object({ duration: v.fallback(v.optional(v.number()), undefined), @@ -66,7 +69,8 @@ const gifvAttachmentSchema = baseAttachmentSchema.extend({ }), {}), }); -const audioAttachmentSchema = baseAttachmentSchema.extend({ +const audioAttachmentSchema = v.object({ + ...baseAttachmentSchema.entries, type: v.literal('audio'), meta: v.fallback(v.object({ duration: v.fallback(v.optional(v.number()), undefined), @@ -83,7 +87,8 @@ const audioAttachmentSchema = baseAttachmentSchema.extend({ }), {}), }); -const unknownAttachmentSchema = baseAttachmentSchema.extend({ +const unknownAttachmentSchema = v.object({ + ...baseAttachmentSchema.entries, type: v.literal('unknown'), }); @@ -96,7 +101,7 @@ const mediaAttachmentSchema = z.preprocess((data: any) => { preview_url: data.url, ...data, }; -}, z.discriminatedUnion('type', [ +}, v.variant('type', [ imageAttachmentSchema, videoAttachmentSchema, gifvAttachmentSchema, diff --git a/packages/pl-api/lib/entities/notification-request.ts b/packages/pl-api/lib/entities/notification-request.ts index 75372ec7b..a03ace66e 100644 --- a/packages/pl-api/lib/entities/notification-request.ts +++ b/packages/pl-api/lib/entities/notification-request.ts @@ -10,7 +10,7 @@ const notificationRequestSchema = v.object({ created_at: dateSchema, updated_at: dateSchema, account: accountSchema, - notifications_count: z.coerce.string(), + notifications_count: v.pipe(v.unknown(), v.transform(String)), last_status: v.fallback(v.optional(statusSchema), undefined), }); diff --git a/packages/pl-api/lib/entities/poll.ts b/packages/pl-api/lib/entities/poll.ts index 1862db118..d95c7802c 100644 --- a/packages/pl-api/lib/entities/poll.ts +++ b/packages/pl-api/lib/entities/poll.ts @@ -17,10 +17,10 @@ const pollSchema = v.object({ expires_at: v.fallback(v.nullable(z.string().datetime()), null), id: v.string(), multiple: v.fallback(v.boolean(), false), - options: z.array(pollOptionSchema).min(2), + options: v.array(pollOptionSchema).min(2), voters_count: v.fallback(v.number(), 0), votes_count: v.fallback(v.number(), 0), - own_votes: v.fallback(v.nullable(z.number()).nonempty(), null), + own_votes: v.fallback(v.nullable(v.array(v.number())).nonempty(), null), voted: v.fallback(v.boolean(), false), non_anonymous: v.fallback(v.boolean(), false), diff --git a/packages/pl-api/lib/entities/scheduled-status.ts b/packages/pl-api/lib/entities/scheduled-status.ts index a0ce78995..c844c2fc4 100644 --- a/packages/pl-api/lib/entities/scheduled-status.ts +++ b/packages/pl-api/lib/entities/scheduled-status.ts @@ -9,19 +9,19 @@ const scheduledStatusSchema = v.object({ scheduled_at: z.string().datetime({ offset: true }), params: v.object({ text: v.fallback(v.nullable(v.string()), null), - poll: v.object({ - options: z.array(v.string()), - expires_in: z.coerce.string(), + poll: v.fallback(v.nullable(v.object({ + options: v.array(v.string()), + expires_in: v.pipe(v.unknown(), v.transform(String)), multiple: v.fallback(v.optional(v.boolean()), undefined), hide_totals: v.fallback(v.optional(v.boolean()), undefined), - }).nullable().catch(null), + })), null), media_ids: v.fallback(v.nullable(v.string()), null), - sensitive: v.fallback(v.nullable(z.coerce.boolean()), null), + sensitive: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Boolean))), null), spoiler_text: v.fallback(v.nullable(v.string()), null), - visibility: z.string().catch('public'), + visibility: v.fallback(v.string(), 'public'), in_reply_to_id: v.fallback(v.nullable(v.string()), null), language: v.fallback(v.nullable(v.string()), null), - application_id: v.fallback(v.nullable(z.number().int()), null), + application_id: v.fallback(v.nullable(v.pipe(v.number(), v.integer())), null), scheduled_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), idempotency: v.fallback(v.nullable(v.string()), null), with_rate_limit: v.fallback(v.boolean(), false), diff --git a/packages/pl-api/lib/entities/scrobble.ts b/packages/pl-api/lib/entities/scrobble.ts index 1dc838ee9..a2766e704 100644 --- a/packages/pl-api/lib/entities/scrobble.ts +++ b/packages/pl-api/lib/entities/scrobble.ts @@ -6,7 +6,7 @@ const scrobbleSchema = z.preprocess((scrobble: any) => scrobble ? { external_link: scrobble.externalLink, ...scrobble, } : null, v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), account: accountSchema, created_at: z.string().datetime({ offset: true }), title: v.string(), diff --git a/packages/pl-api/lib/entities/status-edit.ts b/packages/pl-api/lib/entities/status-edit.ts index 0820e7911..aacd65c41 100644 --- a/packages/pl-api/lib/entities/status-edit.ts +++ b/packages/pl-api/lib/entities/status-edit.ts @@ -9,14 +9,14 @@ import { dateSchema, filteredArray } from './utils'; const statusEditSchema = v.object({ content: v.fallback(v.string(), ''), spoiler_text: v.fallback(v.string(), ''), - sensitive: z.coerce.boolean(), + sensitive: v.pipe(v.unknown(), v.transform(Boolean)), created_at: dateSchema, account: accountSchema, - poll: v.object({ - options: z.array(v.object({ + poll: v.fallback(v.nullable(v.object({ + options: v.array(v.object({ title: v.string(), })), - }).nullable().catch(null), + })), null), media_attachments: filteredArray(mediaAttachmentSchema), emojis: filteredArray(customEmojiSchema), }); diff --git a/packages/pl-api/lib/entities/status.ts b/packages/pl-api/lib/entities/status.ts index 8d3196965..a013c44be 100644 --- a/packages/pl-api/lib/entities/status.ts +++ b/packages/pl-api/lib/entities/status.ts @@ -42,13 +42,13 @@ const baseStatusSchema = v.object({ created_at: dateSchema, account: accountSchema, content: v.fallback(v.string(), ''), - visibility: z.string().catch('public'), - sensitive: z.coerce.boolean(), + visibility: v.fallback(v.string(), 'public'), + sensitive: v.pipe(v.unknown(), v.transform(Boolean)), spoiler_text: v.fallback(v.string(), ''), media_attachments: filteredArray(mediaAttachmentSchema), application: v.fallback(v.nullable(v.object({ name: v.string(), - website: v.fallback(v.nullable(z.string().url()), null), + website: v.fallback(v.nullable(v.pipe(v.string(), v.url())), null), })), null), mentions: filteredArray(mentionSchema), tags: filteredArray(tagSchema), @@ -64,11 +64,11 @@ const baseStatusSchema = v.object({ language: v.fallback(v.nullable(v.string()), null), text: v.fallback(v.nullable(v.string()), null), edited_at: v.fallback(v.nullable(z.string().datetime()), null), - favourited: z.coerce.boolean(), - reblogged: z.coerce.boolean(), - muted: z.coerce.boolean(), - bookmarked: z.coerce.boolean(), - pinned: z.coerce.boolean(), + favourited: v.pipe(v.unknown(), v.transform(Boolean)), + reblogged: v.pipe(v.unknown(), v.transform(Boolean)), + muted: v.pipe(v.unknown(), v.transform(Boolean)), + bookmarked: v.pipe(v.unknown(), v.transform(Boolean)), + pinned: v.pipe(v.unknown(), v.transform(Boolean)), filtered: filteredArray(filterResultSchema), approval_status: v.fallback(v.nullable(v.picklist(['pending', 'approval', 'rejected'])), null), group: v.fallback(v.nullable(groupSchema), null), @@ -97,7 +97,7 @@ const baseStatusSchema = v.object({ spoiler_text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), dislikes_count: v.fallback(v.number(), 0), - disliked: z.coerce.boolean().catch(false), + disliked: v.fallback(v.pipe(v.unknown(), v.transform(Boolean)), false), interaction_policy: interactionPolicySchema, }); diff --git a/packages/pl-api/lib/entities/streaming-event.ts b/packages/pl-api/lib/entities/streaming-event.ts index cdd3ffabf..45a9f6531 100644 --- a/packages/pl-api/lib/entities/streaming-event.ts +++ b/packages/pl-api/lib/entities/streaming-event.ts @@ -25,54 +25,64 @@ const followRelationshipUpdateSchema = v.object({ type FollowRelationshipUpdate = v.InferOutput; const baseStreamingEventSchema = v.object({ - stream: z.array(v.string()).catch([]), + stream: v.fallback(v.array(v.string()), []), }); -const statusStreamingEventSchema = baseStreamingEventSchema.extend({ +const statusStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.picklist(['update', 'status.update']), payload: z.preprocess((payload: any) => JSON.parse(payload), statusSchema), }); -const stringStreamingEventSchema = baseStreamingEventSchema.extend({ +const stringStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.picklist(['delete', 'announcement.delete']), payload: v.string(), }); -const notificationStreamingEventSchema = baseStreamingEventSchema.extend({ +const notificationStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('notification'), payload: z.preprocess((payload: any) => JSON.parse(payload), notificationSchema), }); -const emptyStreamingEventSchema = baseStreamingEventSchema.extend({ +const emptyStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('filters_changed'), }); -const conversationStreamingEventSchema = baseStreamingEventSchema.extend({ +const conversationStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('conversation'), payload: z.preprocess((payload: any) => JSON.parse(payload), conversationSchema), }); -const announcementStreamingEventSchema = baseStreamingEventSchema.extend({ +const announcementStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('announcement'), payload: z.preprocess((payload: any) => JSON.parse(payload), announcementSchema), }); -const announcementReactionStreamingEventSchema = baseStreamingEventSchema.extend({ +const announcementReactionStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('announcement.reaction'), payload: z.preprocess((payload: any) => JSON.parse(payload), announcementReactionSchema), }); -const chatUpdateStreamingEventSchema = baseStreamingEventSchema.extend({ +const chatUpdateStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('chat_update'), payload: z.preprocess((payload: any) => JSON.parse(payload), chatSchema), }); -const followRelationshipsUpdateStreamingEventSchema = baseStreamingEventSchema.extend({ +const followRelationshipsUpdateStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('follow_relationships_update'), payload: z.preprocess((payload: any) => JSON.parse(payload), followRelationshipUpdateSchema), }); -const respondStreamingEventSchema = baseStreamingEventSchema.extend({ +const respondStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('respond'), payload: z.preprocess((payload: any) => JSON.parse(payload), v.object({ type: v.string(), @@ -80,7 +90,8 @@ const respondStreamingEventSchema = baseStreamingEventSchema.extend({ })), }); -const markerStreamingEventSchema = baseStreamingEventSchema.extend({ +const markerStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('marker'), payload: z.preprocess((payload: any) => JSON.parse(payload), markersSchema), }); @@ -89,7 +100,7 @@ const markerStreamingEventSchema = baseStreamingEventSchema.extend({ const streamingEventSchema: z.ZodType = z.preprocess((event: any) => ({ ...event, event: event.event?.replace(/^pleroma:/, ''), -}), z.discriminatedUnion('event', [ +}), v.variant('event', [ statusStreamingEventSchema, stringStreamingEventSchema, notificationStreamingEventSchema, diff --git a/packages/pl-api/lib/entities/suggestion.ts b/packages/pl-api/lib/entities/suggestion.ts index 1ea79fb53..c45513455 100644 --- a/packages/pl-api/lib/entities/suggestion.ts +++ b/packages/pl-api/lib/entities/suggestion.ts @@ -31,7 +31,7 @@ const suggestionSchema = z.preprocess((suggestion: any) => { return suggestion; }, v.object({ source: v.fallback(v.nullable(v.string()), null), - sources: z.array(v.string()).catch([]), + sources: v.fallback(v.array(v.string()), []), account: accountSchema, })); diff --git a/packages/pl-api/lib/entities/tag.ts b/packages/pl-api/lib/entities/tag.ts index 8c35bcc3b..60fe97ce7 100644 --- a/packages/pl-api/lib/entities/tag.ts +++ b/packages/pl-api/lib/entities/tag.ts @@ -1,9 +1,9 @@ import * as v from 'valibot'; const historySchema = v.object({ - day: z.coerce.number(), - accounts: z.coerce.number(), - uses: z.coerce.number(), + day: v.pipe(v.unknown(), v.transform(Number)), + accounts: v.pipe(v.unknown(), v.transform(Number)), + uses: v.pipe(v.unknown(), v.transform(Number)), }); /** @see {@link https://docs.joinmastodon.org/entities/tag} */ diff --git a/packages/pl-api/lib/entities/translation.ts b/packages/pl-api/lib/entities/translation.ts index 4ccc310d1..40eeee6a0 100644 --- a/packages/pl-api/lib/entities/translation.ts +++ b/packages/pl-api/lib/entities/translation.ts @@ -4,7 +4,7 @@ import { filteredArray } from './utils'; const translationPollSchema = v.object({ id: v.string(), - options: z.array(v.object({ + options: v.array(v.object({ title: v.string(), })), }); diff --git a/packages/pl-api/lib/entities/web-push-subscription.ts b/packages/pl-api/lib/entities/web-push-subscription.ts index 5632ae40d..6ed56360b 100644 --- a/packages/pl-api/lib/entities/web-push-subscription.ts +++ b/packages/pl-api/lib/entities/web-push-subscription.ts @@ -2,9 +2,9 @@ import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/WebPushSubscription/} */ const webPushSubscriptionSchema = v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), endpoint: v.string(), - alerts: v.record(v.string(), z.boolean()), + alerts: v.record(v.string(), v.boolean()), server_key: v.string(), }); From b29b488d7e1e42d97f3ec6fd1561d743823e5c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 15 Oct 2024 00:07:22 +0200 Subject: [PATCH 14/28] pl-api: moar blind search and replace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/lib/entities/account.ts | 13 +++++--- packages/pl-api/lib/entities/admin/account.ts | 2 +- .../pl-api/lib/entities/admin/announcement.ts | 3 +- packages/pl-api/lib/entities/admin/tag.ts | 3 +- .../entities/directory/statistics-period.ts | 2 +- .../pl-api/lib/entities/media-attachment.ts | 3 +- packages/pl-api/lib/entities/notification.ts | 32 ++++++++++++------- packages/pl-api/lib/entities/poll.ts | 2 +- packages/pl-api/lib/entities/role.ts | 4 +-- packages/pl-api/lib/entities/status-source.ts | 6 ++-- packages/pl-api/lib/entities/status.ts | 8 +++-- 11 files changed, 48 insertions(+), 30 deletions(-) diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index c9816f3b9..91cd28da9 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -88,7 +88,7 @@ const baseAccountSchema = v.object({ suspended: v.fallback(v.optional(v.boolean()), undefined), limited: v.fallback(v.optional(v.boolean()), undefined), created_at: z.string().datetime().catch(new Date().toUTCString()), - last_status_at: v.fallback(v.nullable(z.string().date()), null), + last_status_at: v.fallback(v.nullable(v.pipe(v.string(), v.isoDate())), null), statuses_count: v.fallback(v.number(), 0), followers_count: v.fallback(v.number(), 0), following_count: v.fallback(v.number(), 0), @@ -108,7 +108,7 @@ const baseAccountSchema = v.object({ hide_follows_count: v.fallback(v.optional(v.boolean()), undefined), accepts_chat_messages: v.fallback(v.nullable(v.boolean()), null), favicon: v.fallback(v.optional(v.string()), undefined), - birthday: z.string().date().optional().catch(undefined), + birthday: v.fallback(v.optional(v.pipe(v.string(), v.isoDate())), undefined), deactivated: v.fallback(v.optional(v.boolean()), undefined), location: v.fallback(v.optional(v.string()), undefined), @@ -126,7 +126,8 @@ const baseAccountSchema = v.object({ }), }); -const accountWithMovedAccountSchema = baseAccountSchema.extend({ +const accountWithMovedAccountSchema = v.object({ + ...baseAccountSchema.entries, moved: v.fallback(v.nullable(z.lazy((): typeof baseAccountSchema => accountWithMovedAccountSchema as any)), null), }); @@ -141,7 +142,8 @@ type Account = v.InferOutput & WithMoved; const accountSchema: z.ZodType = untypedAccountSchema as any; -const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({ +const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, v.object({ + ...accountWithMovedAccountSchema.entries, source: v.fallback(v.nullable(v.object({ note: v.fallback(v.string(), ''), fields: filteredArray(fieldSchema), @@ -173,7 +175,8 @@ type CredentialAccount = v.InferOutput & const credentialAccountSchema: z.ZodType = untypedCredentialAccountSchema as any; -const untypedMutedAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({ +const untypedMutedAccountSchema = z.preprocess(preprocessAccount, v.object({ + ...accountWithMovedAccountSchema.entries, mute_expires_at: v.fallback(v.nullable(dateSchema), null), })); diff --git a/packages/pl-api/lib/entities/admin/account.ts b/packages/pl-api/lib/entities/admin/account.ts index 74fc5e1cc..2df73feb2 100644 --- a/packages/pl-api/lib/entities/admin/account.ts +++ b/packages/pl-api/lib/entities/admin/account.ts @@ -42,7 +42,7 @@ const adminAccountSchema = z.preprocess((account: any) => { domain: v.fallback(v.nullable(v.string()), null), created_at: dateSchema, email: v.fallback(v.nullable(v.string()), null), - ip: v.fallback(v.nullable(z.string().ip()), null), + ip: v.fallback(v.nullable(v.pipe(v.string(), v.ip())), null), ips: filteredArray(adminIpSchema), locale: v.fallback(v.nullable(v.string()), null), invite_request: v.fallback(v.nullable(v.string()), null), diff --git a/packages/pl-api/lib/entities/admin/announcement.ts b/packages/pl-api/lib/entities/admin/announcement.ts index 3a670a7f4..0f44336b2 100644 --- a/packages/pl-api/lib/entities/admin/announcement.ts +++ b/packages/pl-api/lib/entities/admin/announcement.ts @@ -7,7 +7,8 @@ import { announcementSchema } from '../announcement'; const adminAnnouncementSchema = z.preprocess((announcement: any) => ({ ...announcement, ...pick(announcement.pleroma, 'raw_content'), -}), announcementSchema.extend({ +}), v.object({ + ...announcementSchema.entries, raw_content: v.fallback(v.string(), ''), })); diff --git a/packages/pl-api/lib/entities/admin/tag.ts b/packages/pl-api/lib/entities/admin/tag.ts index 983a64538..aaa2818f6 100644 --- a/packages/pl-api/lib/entities/admin/tag.ts +++ b/packages/pl-api/lib/entities/admin/tag.ts @@ -3,7 +3,8 @@ import * as v from 'valibot'; import { tagSchema } from '../tag'; /** @see {@link https://docs.joinmastodon.org/entities/Tag/#admin} */ -const adminTagSchema = tagSchema.extend({ +const adminTagSchema = v.object({ + ...tagSchema.entries, id: v.string(), trendable: v.boolean(), usable: v.boolean(), diff --git a/packages/pl-api/lib/entities/directory/statistics-period.ts b/packages/pl-api/lib/entities/directory/statistics-period.ts index e48264329..ea37a321d 100644 --- a/packages/pl-api/lib/entities/directory/statistics-period.ts +++ b/packages/pl-api/lib/entities/directory/statistics-period.ts @@ -1,7 +1,7 @@ import * as v from 'valibot'; const directoryStatisticsPeriodSchema = v.object({ - period: z.string().date(), + period: v.pipe(v.string(), v.isoDate()), server_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), active_user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), diff --git a/packages/pl-api/lib/entities/media-attachment.ts b/packages/pl-api/lib/entities/media-attachment.ts index 341378f8a..e66cf1fd6 100644 --- a/packages/pl-api/lib/entities/media-attachment.ts +++ b/packages/pl-api/lib/entities/media-attachment.ts @@ -51,7 +51,8 @@ const videoAttachmentSchema = v.object({ type: v.literal('video'), meta: v.fallback(v.object({ duration: v.fallback(v.optional(v.number()), undefined), - original: v.fallback(v.optional(imageMetaSchema.extend({ + original: v.fallback(v.optional(v.object({ + ...imageMetaSchema.entries, frame_rate: v.fallback(v.nullable(v.pipe(v.string(), v.regex(/\d+\/\d+$/))), null), duration: v.fallback(v.nullable(z.number().nonnegative()), null), })), undefined), diff --git a/packages/pl-api/lib/entities/notification.ts b/packages/pl-api/lib/entities/notification.ts index 128344dcf..2a687dc7a 100644 --- a/packages/pl-api/lib/entities/notification.ts +++ b/packages/pl-api/lib/entities/notification.ts @@ -20,54 +20,64 @@ const baseNotificationSchema = v.object({ is_seen: v.fallback(v.optional(v.boolean()), undefined), }); -const accountNotificationSchema = baseNotificationSchema.extend({ +const accountNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.picklist(['follow', 'follow_request', 'admin.sign_up', 'bite']), }); -const mentionNotificationSchema = baseNotificationSchema.extend({ +const mentionNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('mention'), subtype: v.fallback(v.nullable(v.picklist(['reply'])), null), status: statusSchema, }); -const statusNotificationSchema = baseNotificationSchema.extend({ +const statusNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.picklist(['status', 'reblog', 'favourite', 'poll', 'update', 'event_reminder']), status: statusSchema, }); -const reportNotificationSchema = baseNotificationSchema.extend({ +const reportNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('admin.report'), report: reportSchema, }); -const severedRelationshipNotificationSchema = baseNotificationSchema.extend({ +const severedRelationshipNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('severed_relationships'), relationship_severance_event: relationshipSeveranceEventSchema, }); -const moderationWarningNotificationSchema = baseNotificationSchema.extend({ +const moderationWarningNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('moderation_warning'), moderation_warning: accountWarningSchema, }); -const moveNotificationSchema = baseNotificationSchema.extend({ +const moveNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('move'), target: accountSchema, }); -const emojiReactionNotificationSchema = baseNotificationSchema.extend({ +const emojiReactionNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('emoji_reaction'), emoji: v.string(), emoji_url: v.fallback(v.nullable(v.string()), null), status: statusSchema, }); -const chatMentionNotificationSchema = baseNotificationSchema.extend({ +const chatMentionNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.literal('chat_mention'), chat_message: chatMessageSchema, }); -const eventParticipationRequestNotificationSchema = baseNotificationSchema.extend({ +const eventParticipationRequestNotificationSchema = v.object({ + ...baseNotificationSchema.entries, type: v.picklist(['participation_accepted', 'participation_request']), status: statusSchema, participation_message: v.fallback(v.nullable(v.string()), null), @@ -81,7 +91,7 @@ const notificationSchema: z.ZodType = z.preprocess((notification: type: notification.type === 'pleroma:report' ? 'admin.report' : notification.type?.replace(/^pleroma:/, ''), -}), z.discriminatedUnion('type', [ +}), v.variant('type', [ accountNotificationSchema, mentionNotificationSchema, statusNotificationSchema, diff --git a/packages/pl-api/lib/entities/poll.ts b/packages/pl-api/lib/entities/poll.ts index d95c7802c..3a76c1ef5 100644 --- a/packages/pl-api/lib/entities/poll.ts +++ b/packages/pl-api/lib/entities/poll.ts @@ -7,7 +7,7 @@ const pollOptionSchema = v.object({ title: v.fallback(v.string(), ''), votes_count: v.fallback(v.number(), 0), - title_map: v.record(v.string(), v.string()).nullable().catch(null), + title_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), }); /** @see {@link https://docs.joinmastodon.org/entities/Poll/} */ diff --git a/packages/pl-api/lib/entities/role.ts b/packages/pl-api/lib/entities/role.ts index 173c6ff89..1b1c2eab9 100644 --- a/packages/pl-api/lib/entities/role.ts +++ b/packages/pl-api/lib/entities/role.ts @@ -1,11 +1,11 @@ import * as v from 'valibot'; -const hexSchema = z.string().regex(/^#[a-f0-9]{6}$/i); +const hexSchema = v.pipe(v.string(), v.regex(/^#[a-f0-9]{6}$/i)); const roleSchema = v.object({ id: v.fallback(v.string(), ''), name: v.fallback(v.string(), ''), - color: hexSchema.catch(''), + color: v.fallback(hexSchema, ''), permissions: v.fallback(v.string(), ''), highlighted: v.fallback(v.boolean(), true), }); diff --git a/packages/pl-api/lib/entities/status-source.ts b/packages/pl-api/lib/entities/status-source.ts index 01516a200..7b521cfa9 100644 --- a/packages/pl-api/lib/entities/status-source.ts +++ b/packages/pl-api/lib/entities/status-source.ts @@ -8,11 +8,11 @@ const statusSourceSchema = v.object({ text: v.fallback(v.string(), ''), spoiler_text: v.fallback(v.string(), ''), - content_type: z.string().catch('text/plain'), + content_type: v.fallback(v.string(), 'text/plain'), location: v.fallback(v.nullable(locationSchema), null), - text_map: v.record(v.string(), v.string()).nullable().catch(null), - spoiler_text_map: v.record(v.string(), v.string()).nullable().catch(null), + text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), + spoiler_text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), }); type StatusSource = v.InferOutput; diff --git a/packages/pl-api/lib/entities/status.ts b/packages/pl-api/lib/entities/status.ts index a013c44be..c867c45d0 100644 --- a/packages/pl-api/lib/entities/status.ts +++ b/packages/pl-api/lib/entities/status.ts @@ -90,7 +90,7 @@ const baseStatusSchema = v.object({ bookmark_folder: v.fallback(v.nullable(v.string()), null), event: v.fallback(v.nullable(statusEventSchema), null), - translation: translationSchema.nullable().or(v.literal(false)).catch(null), + translation: v.fallback(v.union([v.nullable(translationSchema), v.literal(false)]), null), content_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), @@ -134,13 +134,15 @@ const preprocess = (status: any) => { return status; }; -const statusSchema: z.ZodType = z.preprocess(preprocess, baseStatusSchema.extend({ +const statusSchema: z.ZodType = z.preprocess(preprocess, v.object({ + ...baseStatusSchema.entries, reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), quote: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), })) as any; -const statusWithoutAccountSchema = z.preprocess(preprocess, baseStatusSchema.omit({ account: true }).extend({ +const statusWithoutAccountSchema = z.preprocess(preprocess, v.object({ + ...(v.omit(baseStatusSchema, ['account']).entries), account: v.fallback(v.nullable(accountSchema), null), reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), From 6633b28645c118edb2fde5d50137dc8b54b4fe7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 15 Oct 2024 00:07:53 +0200 Subject: [PATCH 15/28] pl-fe: Disable global objects used for debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/schemas/pleroma.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/pl-fe/src/schemas/pleroma.ts b/packages/pl-fe/src/schemas/pleroma.ts index 78a3d4b90..20f7a2a05 100644 --- a/packages/pl-fe/src/schemas/pleroma.ts +++ b/packages/pl-fe/src/schemas/pleroma.ts @@ -18,9 +18,6 @@ const mrfSimpleSchema = coerceObject(v.entriesFromList( v.fallback(v.array(v.string()), []), )); -(window as any).v = v; -(window as any).mrfSimpleSchema = mrfSimpleSchema; - type MRFSimple = v.InferOutput; export { mrfSimpleSchema, type MRFSimple }; From 2f7e149f758e46f6d49dbb56b46a06dee5c8c57d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 01:08:56 +0200 Subject: [PATCH 16/28] pl-api: Mostly finish migration to valibot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/lib/client.ts | 6 +- packages/pl-api/lib/directory-client.ts | 10 +- packages/pl-api/lib/entities/account.ts | 20 ++-- packages/pl-api/lib/entities/admin/account.ts | 98 ++++++++++--------- .../pl-api/lib/entities/admin/announcement.ts | 18 ++-- .../pl-api/lib/entities/admin/ip-block.ts | 2 +- packages/pl-api/lib/entities/admin/ip.ts | 2 +- packages/pl-api/lib/entities/admin/relay.ts | 14 ++- packages/pl-api/lib/entities/admin/report.ts | 60 ++++++------ .../lib/entities/announcement-reaction.ts | 2 +- packages/pl-api/lib/entities/announcement.ts | 9 +- .../pl-api/lib/entities/emoji-reaction.ts | 14 ++- packages/pl-api/lib/entities/filter.ts | 50 +++++----- packages/pl-api/lib/entities/group-member.ts | 2 +- .../pl-api/lib/entities/group-relationship.ts | 2 +- packages/pl-api/lib/entities/group.ts | 2 +- packages/pl-api/lib/entities/instance.ts | 69 +++++++------ packages/pl-api/lib/entities/location.ts | 2 +- packages/pl-api/lib/entities/marker.ts | 22 +++-- .../pl-api/lib/entities/media-attachment.ts | 54 +++++----- packages/pl-api/lib/entities/mention.ts | 25 ++--- packages/pl-api/lib/entities/notification.ts | 41 ++++---- packages/pl-api/lib/entities/oauth-token.ts | 20 ++-- packages/pl-api/lib/entities/poll.ts | 4 +- packages/pl-api/lib/entities/rule.ts | 12 ++- packages/pl-api/lib/entities/scrobble.ts | 30 +++--- packages/pl-api/lib/entities/status.ts | 12 +-- .../pl-api/lib/entities/streaming-event.ts | 54 +++++----- packages/pl-api/lib/entities/suggestion.ts | 50 +++++----- packages/pl-api/lib/entities/tag.ts | 2 +- packages/pl-api/lib/entities/translation.ts | 36 ++++--- packages/pl-api/lib/entities/trends-link.ts | 42 ++++---- packages/pl-api/lib/entities/utils.ts | 16 +-- .../src/api/hooks/accounts/useRelationship.ts | 4 +- .../api/hooks/groups/useDemoteGroupMember.ts | 4 +- .../api/hooks/groups/useGroupRelationship.ts | 4 +- .../api/hooks/groups/usePromoteGroupMember.ts | 4 +- .../pl-fe/src/entity-store/hooks/types.ts | 4 +- .../entity-store/hooks/useBatchedEntities.ts | 2 +- .../src/entity-store/hooks/useCreateEntity.ts | 2 +- .../src/entity-store/hooks/useEntities.ts | 4 +- .../pl-fe/src/entity-store/hooks/useEntity.ts | 2 +- .../src/entity-store/hooks/useEntityLookup.ts | 2 +- packages/pl-fe/src/schemas/utils.ts | 17 ++-- 44 files changed, 457 insertions(+), 394 deletions(-) diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts index ad955093b..22f9413d5 100644 --- a/packages/pl-api/lib/client.ts +++ b/packages/pl-api/lib/client.ts @@ -227,7 +227,7 @@ class PlApiClient { } } - #paginatedGet = async >(input: URL | RequestInfo, body: RequestBody, schema: T): Promise>> => { + #paginatedGet = async (input: URL | RequestInfo, body: RequestBody, schema: v.BaseSchema>): Promise> => { const getMore = (input: string | null) => input ? async () => { const response = await this.request(input); @@ -2441,7 +2441,7 @@ class PlApiClient { const enqueue = (fn: () => any) => ws.readyState === WebSocket.CONNECTING ? queue.push(fn) : fn(); ws.onmessage = (event) => { - const message = streamingEventSchema.parse(JSON.parse(event.data as string)); + const message = v.parse(streamingEventSchema, JSON.parse(event.data as string)); listeners.filter(({ listener, stream }) => (!stream || message.stream.includes(stream)) && listener(message)); }; @@ -2687,7 +2687,7 @@ class PlApiClient { response = await this.request('/api/v1/instance'); } - const instance = v.parse(instanceSchema.readonly(), response.json); + const instance = v.parse(v.pipe(instanceSchema, v.readonly()), response.json); this.#setInstance(instance); return instance; diff --git a/packages/pl-api/lib/directory-client.ts b/packages/pl-api/lib/directory-client.ts index 69ad428e1..2a41b061c 100644 --- a/packages/pl-api/lib/directory-client.ts +++ b/packages/pl-api/lib/directory-client.ts @@ -1,3 +1,5 @@ +import * as v from 'valibot'; + import { directoryCategorySchema, directoryLanguageSchema, directoryServerSchema, directoryStatisticsPeriodSchema } from './entities'; import { filteredArray } from './entities/utils'; import request from './request'; @@ -23,25 +25,25 @@ class PlApiDirectoryClient { async getStatistics() { const response = await this.request('/statistics'); - return filteredArray(directoryStatisticsPeriodSchema).parse(response.json); + return v.parse(filteredArray(directoryStatisticsPeriodSchema), response.json); } async getCategories(params?: Params) { const response = await this.request('/categories', { params }); - return filteredArray(directoryCategorySchema).parse(response.json); + return v.parse(filteredArray(directoryCategorySchema), response.json); } async getLanguages(params?: Params) { const response = await this.request('/categories', { params }); - return filteredArray(directoryLanguageSchema).parse(response.json); + return v.parse(filteredArray(directoryLanguageSchema), response.json); } async getServers(params?: Params) { const response = await this.request('/servers', { params }); - return filteredArray(directoryServerSchema).parse(response.json); + return v.parse(filteredArray(directoryServerSchema), response.json); } } diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index 91cd28da9..212bd3ba8 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -9,7 +9,7 @@ import { coerceObject, dateSchema, filteredArray } from './utils'; const filterBadges = (tags?: string[]) => tags?.filter(tag => tag.startsWith('badge:')).map(tag => v.parse(roleSchema, { id: tag, name: tag.replace(/^badge:/, '') })); -const preprocessAccount = (account: any) => { +const preprocessAccount = v.transform((account: any) => { if (!account?.acct) return null; const username = account.username || account.acct.split('@')[0]; @@ -59,7 +59,7 @@ const preprocessAccount = (account: any) => { ])), ...account.source } : undefined, }; -}; +}); const fieldSchema = v.object({ name: v.string(), @@ -128,11 +128,11 @@ const baseAccountSchema = v.object({ const accountWithMovedAccountSchema = v.object({ ...baseAccountSchema.entries, - moved: v.fallback(v.nullable(z.lazy((): typeof baseAccountSchema => accountWithMovedAccountSchema as any)), null), + moved: v.fallback(v.nullable(v.lazy((): typeof baseAccountSchema => accountWithMovedAccountSchema as any)), null), }); /** @see {@link https://docs.joinmastodon.org/entities/Account/} */ -const untypedAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema); +const untypedAccountSchema = v.pipe(v.any(), preprocessAccount, accountWithMovedAccountSchema); type WithMoved = { moved: Account | null; @@ -140,9 +140,9 @@ type WithMoved = { type Account = v.InferOutput & WithMoved; -const accountSchema: z.ZodType = untypedAccountSchema as any; +const accountSchema: v.BaseSchema> = untypedAccountSchema as any; -const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, v.object({ +const untypedCredentialAccountSchema = v.pipe(v.any(), preprocessAccount, v.object({ ...accountWithMovedAccountSchema.entries, source: v.fallback(v.nullable(v.object({ note: v.fallback(v.string(), ''), @@ -150,7 +150,7 @@ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, v.object( privacy: v.picklist(['public', 'unlisted', 'private', 'direct']), sensitive: v.fallback(v.boolean(), false), language: v.fallback(v.nullable(v.string()), null), - follow_requests_count: z.number().int().nonnegative().catch(0), + follow_requests_count: v.fallback(v.pipe(v.number(), v.integer(), v.minValue(0)), 0), show_role: v.fallback(v.nullable(v.optional(v.boolean())), undefined), no_rich_text: v.fallback(v.nullable(v.optional(v.boolean())), undefined), @@ -173,16 +173,16 @@ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, v.object( type CredentialAccount = v.InferOutput & WithMoved; -const credentialAccountSchema: z.ZodType = untypedCredentialAccountSchema as any; +const credentialAccountSchema: v.BaseSchema> = untypedCredentialAccountSchema as any; -const untypedMutedAccountSchema = z.preprocess(preprocessAccount, v.object({ +const untypedMutedAccountSchema = v.pipe(v.any(), preprocessAccount, v.object({ ...accountWithMovedAccountSchema.entries, mute_expires_at: v.fallback(v.nullable(dateSchema), null), })); type MutedAccount = v.InferOutput & WithMoved; -const mutedAccountSchema: z.ZodType = untypedMutedAccountSchema as any; +const mutedAccountSchema: v.BaseSchema> = untypedMutedAccountSchema as any; export { accountSchema, diff --git a/packages/pl-api/lib/entities/admin/account.ts b/packages/pl-api/lib/entities/admin/account.ts index 2df73feb2..065e33ca5 100644 --- a/packages/pl-api/lib/entities/admin/account.ts +++ b/packages/pl-api/lib/entities/admin/account.ts @@ -7,59 +7,63 @@ import { dateSchema, filteredArray } from '../utils'; import { adminIpSchema } from './ip'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_Account/} */ -const adminAccountSchema = z.preprocess((account: any) => { - if (!account.account) { +const adminAccountSchema = v.pipe( + v.any(), + v.transform((account: any) => { + if (!account.account) { /** * Convert Pleroma account schema * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminusers} */ - return { - id: account.id, - account: null, - username: account.nickname, - domain: account.nickname.split('@')[1] || null, - created_at: account.created_at, - email: account.email, - invite_request: account.registration_reason, - role: account.roles?.is_admin - ? v.parse(roleSchema, { name: 'Admin' }) - : account.roles?.moderator - ? v.parse(roleSchema, { name: 'Moderator ' }) : - null, - confirmed: account.is_confirmed, - approved: account.is_approved, - disabled: !account.is_active, + return { + id: account.id, + account: null, + username: account.nickname, + domain: account.nickname.split('@')[1] || null, + created_at: account.created_at, + email: account.email, + invite_request: account.registration_reason, + role: account.roles?.is_admin + ? v.parse(roleSchema, { name: 'Admin' }) + : account.roles?.moderator + ? v.parse(roleSchema, { name: 'Moderator ' }) : + null, + confirmed: account.is_confirmed, + approved: account.is_approved, + disabled: !account.is_active, - actor_type: account.actor_type, - display_name: account.display_name, - suggested: account.is_suggested, - }; - } - return account; -}, v.object({ - id: v.string(), - username: v.string(), - domain: v.fallback(v.nullable(v.string()), null), - created_at: dateSchema, - email: v.fallback(v.nullable(v.string()), null), - ip: v.fallback(v.nullable(v.pipe(v.string(), v.ip())), null), - ips: filteredArray(adminIpSchema), - locale: v.fallback(v.nullable(v.string()), null), - invite_request: v.fallback(v.nullable(v.string()), null), - role: v.fallback(v.nullable(roleSchema), null), - confirmed: v.fallback(v.boolean(), false), - approved: v.fallback(v.boolean(), false), - disabled: v.fallback(v.boolean(), false), - silenced: v.fallback(v.boolean(), false), - suspended: v.fallback(v.boolean(), false), - account: v.fallback(v.nullable(accountSchema), null), - created_by_application_id: v.fallback(v.optional(v.string()), undefined), - invited_by_account_id: v.fallback(v.optional(v.string()), undefined), + actor_type: account.actor_type, + display_name: account.display_name, + suggested: account.is_suggested, + }; + } + return account; + }), + v.object({ + id: v.string(), + username: v.string(), + domain: v.fallback(v.nullable(v.string()), null), + created_at: dateSchema, + email: v.fallback(v.nullable(v.string()), null), + ip: v.fallback(v.nullable(v.pipe(v.string(), v.ip())), null), + ips: filteredArray(adminIpSchema), + locale: v.fallback(v.nullable(v.string()), null), + invite_request: v.fallback(v.nullable(v.string()), null), + role: v.fallback(v.nullable(roleSchema), null), + confirmed: v.fallback(v.boolean(), false), + approved: v.fallback(v.boolean(), false), + disabled: v.fallback(v.boolean(), false), + silenced: v.fallback(v.boolean(), false), + suspended: v.fallback(v.boolean(), false), + account: v.fallback(v.nullable(accountSchema), null), + created_by_application_id: v.fallback(v.optional(v.string()), undefined), + invited_by_account_id: v.fallback(v.optional(v.string()), undefined), - actor_type: v.fallback(v.nullable(v.string()), null), - display_name: v.fallback(v.nullable(v.string()), null), - suggested: v.fallback(v.nullable(v.boolean()), null), -})); + actor_type: v.fallback(v.nullable(v.string()), null), + display_name: v.fallback(v.nullable(v.string()), null), + suggested: v.fallback(v.nullable(v.boolean()), null), + }), +); type AdminAccount = v.InferOutput; diff --git a/packages/pl-api/lib/entities/admin/announcement.ts b/packages/pl-api/lib/entities/admin/announcement.ts index 0f44336b2..5cef6c4a1 100644 --- a/packages/pl-api/lib/entities/admin/announcement.ts +++ b/packages/pl-api/lib/entities/admin/announcement.ts @@ -4,13 +4,17 @@ import * as v from 'valibot'; import { announcementSchema } from '../announcement'; /** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminannouncements} */ -const adminAnnouncementSchema = z.preprocess((announcement: any) => ({ - ...announcement, - ...pick(announcement.pleroma, 'raw_content'), -}), v.object({ - ...announcementSchema.entries, - raw_content: v.fallback(v.string(), ''), -})); +const adminAnnouncementSchema = v.pipe( + v.any(), + v.transform((announcement: any) => ({ + ...announcement, + ...pick(announcement.pleroma, 'raw_content'), + })), + v.object({ + ...announcementSchema.entries, + raw_content: v.fallback(v.string(), ''), + }), +); type AdminAnnouncement = v.InferOutput; diff --git a/packages/pl-api/lib/entities/admin/ip-block.ts b/packages/pl-api/lib/entities/admin/ip-block.ts index bf470a426..5347035d8 100644 --- a/packages/pl-api/lib/entities/admin/ip-block.ts +++ b/packages/pl-api/lib/entities/admin/ip-block.ts @@ -5,7 +5,7 @@ import { dateSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_IpBlock/} */ const adminIpBlockSchema = v.object({ id: v.string(), - ip: z.string().ip(), + ip: v.pipe(v.string(), v.ip()), severity: v.picklist(['sign_up_requires_approval', 'sign_up_block', 'no_access']), comment: v.fallback(v.string(), ''), created_at: dateSchema, diff --git a/packages/pl-api/lib/entities/admin/ip.ts b/packages/pl-api/lib/entities/admin/ip.ts index 83ebb5654..8d99befa4 100644 --- a/packages/pl-api/lib/entities/admin/ip.ts +++ b/packages/pl-api/lib/entities/admin/ip.ts @@ -4,7 +4,7 @@ import { dateSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_Ip/} */ const adminIpSchema = v.object({ - ip: z.string().ip(), + ip: v.pipe(v.string(), v.ip()), used_at: dateSchema, }); diff --git a/packages/pl-api/lib/entities/admin/relay.ts b/packages/pl-api/lib/entities/admin/relay.ts index ce19ea3ca..efe2df0a8 100644 --- a/packages/pl-api/lib/entities/admin/relay.ts +++ b/packages/pl-api/lib/entities/admin/relay.ts @@ -1,10 +1,14 @@ import * as v from 'valibot'; -const adminRelaySchema = z.preprocess((data: any) => ({ id: data.actor, ...data }), v.object({ - actor: v.fallback(v.string(), ''), - id: v.string(), - followed_back: v.fallback(v.boolean(), false), -})); +const adminRelaySchema = v.pipe( + v.any(), + v.transform((data: any) => ({ id: data.actor, ...data })), + v.object({ + actor: v.fallback(v.string(), ''), + id: v.string(), + followed_back: v.fallback(v.boolean(), false), + }), +); type AdminRelay = v.InferOutput diff --git a/packages/pl-api/lib/entities/admin/report.ts b/packages/pl-api/lib/entities/admin/report.ts index 4ae7c4f3c..0a577ecd2 100644 --- a/packages/pl-api/lib/entities/admin/report.ts +++ b/packages/pl-api/lib/entities/admin/report.ts @@ -8,38 +8,42 @@ import { dateSchema, filteredArray } from '../utils'; import { adminAccountSchema } from './account'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_Report/} */ -const adminReportSchema = z.preprocess((report: any) => { - if (report.actor) { +const adminReportSchema = v.pipe( + v.any(), + v.transform((report: any) => { + if (report.actor) { /** * Convert Pleroma report schema * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminreports} */ - return { - action_taken: report.state !== 'open', - comment: report.content, - updated_at: report.created_at, - account: report.actor, - target_account: report.account, - ...(pick(report, ['id', 'assigned_account', 'created_at', 'rules', 'statuses'])), - }; - } - return report; -}, v.object({ - id: v.string(), - action_taken: v.fallback(v.optional(v.boolean()), undefined), - action_taken_at: v.fallback(v.nullable(dateSchema), null), - category: v.fallback(v.optional(v.string()), undefined), - comment: v.fallback(v.optional(v.string()), undefined), - forwarded: v.fallback(v.optional(v.boolean()), undefined), - created_at: v.fallback(v.optional(dateSchema), undefined), - updated_at: v.fallback(v.optional(dateSchema), undefined), - account: adminAccountSchema, - target_account: adminAccountSchema, - assigned_account: v.fallback(v.nullable(adminAccountSchema), null), - action_taken_by_account: v.fallback(v.nullable(adminAccountSchema), null), - statuses: filteredArray(statusWithoutAccountSchema), - rules: filteredArray(ruleSchema), -})); + return { + action_taken: report.state !== 'open', + comment: report.content, + updated_at: report.created_at, + account: report.actor, + target_account: report.account, + ...(pick(report, ['id', 'assigned_account', 'created_at', 'rules', 'statuses'])), + }; + } + return report; + }), + v.object({ + id: v.string(), + action_taken: v.fallback(v.optional(v.boolean()), undefined), + action_taken_at: v.fallback(v.nullable(dateSchema), null), + category: v.fallback(v.optional(v.string()), undefined), + comment: v.fallback(v.optional(v.string()), undefined), + forwarded: v.fallback(v.optional(v.boolean()), undefined), + created_at: v.fallback(v.optional(dateSchema), undefined), + updated_at: v.fallback(v.optional(dateSchema), undefined), + account: adminAccountSchema, + target_account: adminAccountSchema, + assigned_account: v.fallback(v.nullable(adminAccountSchema), null), + action_taken_by_account: v.fallback(v.nullable(adminAccountSchema), null), + statuses: filteredArray(statusWithoutAccountSchema), + rules: filteredArray(ruleSchema), + }), +); type AdminReport = v.InferOutput; diff --git a/packages/pl-api/lib/entities/announcement-reaction.ts b/packages/pl-api/lib/entities/announcement-reaction.ts index 9fb1a0da7..fcb5881d9 100644 --- a/packages/pl-api/lib/entities/announcement-reaction.ts +++ b/packages/pl-api/lib/entities/announcement-reaction.ts @@ -3,7 +3,7 @@ import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/announcement/} */ const announcementReactionSchema = v.object({ name: v.fallback(v.string(), ''), - count: z.number().int().nonnegative().catch(0), + count: v.fallback(v.pipe(v.number(), v.integer(), v.minValue(0)), 0), me: v.fallback(v.boolean(), false), url: v.fallback(v.nullable(v.string()), null), static_url: v.fallback(v.nullable(v.string()), null), diff --git a/packages/pl-api/lib/entities/announcement.ts b/packages/pl-api/lib/entities/announcement.ts index 0be54d135..b62950d38 100644 --- a/packages/pl-api/lib/entities/announcement.ts +++ b/packages/pl-api/lib/entities/announcement.ts @@ -16,11 +16,12 @@ const announcementSchema = v.object({ read: v.fallback(v.boolean(), false), published_at: dateSchema, reactions: filteredArray(announcementReactionSchema), - statuses: z.preprocess( - (statuses: any) => Array.isArray(statuses) + statuses: v.pipe( + v.any(), + v.transform((statuses: any) => Array.isArray(statuses) ? Object.fromEntries(statuses.map((status: any) => [status.url, status.account?.acct]) || []) - : statuses, - v.record(v.string(), v.string(), v.string()), + : statuses), + v.record(v.string(), v.string()), ), mentions: filteredArray(mentionSchema), tags: filteredArray(tagSchema), diff --git a/packages/pl-api/lib/entities/emoji-reaction.ts b/packages/pl-api/lib/entities/emoji-reaction.ts index 0e46529fc..2fbccf22d 100644 --- a/packages/pl-api/lib/entities/emoji-reaction.ts +++ b/packages/pl-api/lib/entities/emoji-reaction.ts @@ -24,11 +24,15 @@ const customEmojiReactionSchema = v.object({ * Pleroma emoji reaction. * @see {@link https://docs.pleroma.social/backend/development/API/differences_in_mastoapi_responses/#statuses} */ -const emojiReactionSchema = z.preprocess((reaction: any) => reaction ? { - static_url: reaction.url, - account_ids: reaction.accounts?.map((account: any) => account?.id), - ...reaction, -} : null, v.union([baseEmojiReactionSchema, customEmojiReactionSchema]); +const emojiReactionSchema = v.pipe( + v.any(), + v.transform((reaction: any) => reaction ? { + static_url: reaction.url, + account_ids: reaction.accounts?.map((account: any) => account?.id), + ...reaction, + } : null), + v.union([baseEmojiReactionSchema, customEmojiReactionSchema]), +); type EmojiReaction = v.InferOutput; diff --git a/packages/pl-api/lib/entities/filter.ts b/packages/pl-api/lib/entities/filter.ts index 5301800f5..cbc34e06b 100644 --- a/packages/pl-api/lib/entities/filter.ts +++ b/packages/pl-api/lib/entities/filter.ts @@ -16,29 +16,33 @@ const filterStatusSchema = v.object({ }); /** @see {@link https://docs.joinmastodon.org/entities/Filter/} */ -const filterSchema = z.preprocess((filter: any) => { - if (filter.phrase) { - return { - ...filter, - title: filter.phrase, - keywords: [{ - id: '1', - keyword: filter.phrase, - whole_word: filter.whole_word, - }], - filter_action: filter.irreversible ? 'hide' : 'warn', - }; - } - return filter; -}, v.object({ - id: v.string(), - title: v.string(), - context: v.array(v.picklist(['home', 'notifications', 'public', 'thread', 'account'])), - expires_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), - filter_action: v.fallback(v.picklist(['warn', 'hide']), 'warn'), - keywords: filteredArray(filterKeywordSchema), - statuses: filteredArray(filterStatusSchema), -})); +const filterSchema = v.pipe( + v.any(), + v.transform((filter: any) => { + if (filter.phrase) { + return { + ...filter, + title: filter.phrase, + keywords: [{ + id: '1', + keyword: filter.phrase, + whole_word: filter.whole_word, + }], + filter_action: filter.irreversible ? 'hide' : 'warn', + }; + } + return filter; + }), + v.object({ + id: v.string(), + title: v.string(), + context: v.array(v.picklist(['home', 'notifications', 'public', 'thread', 'account'])), + expires_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), + filter_action: v.fallback(v.picklist(['warn', 'hide']), 'warn'), + keywords: filteredArray(filterKeywordSchema), + statuses: filteredArray(filterStatusSchema), + }), +); type Filter = v.InferOutput; diff --git a/packages/pl-api/lib/entities/group-member.ts b/packages/pl-api/lib/entities/group-member.ts index 4eb249ea4..7f4f80f26 100644 --- a/packages/pl-api/lib/entities/group-member.ts +++ b/packages/pl-api/lib/entities/group-member.ts @@ -13,7 +13,7 @@ type GroupRole =`${GroupRoles}`; const groupMemberSchema = v.object({ id: v.string(), account: accountSchema, - role: z.nativeEnum(GroupRoles), + role: v.enum(GroupRoles), }); type GroupMember = v.InferOutput; diff --git a/packages/pl-api/lib/entities/group-relationship.ts b/packages/pl-api/lib/entities/group-relationship.ts index 79489787d..96c2a0436 100644 --- a/packages/pl-api/lib/entities/group-relationship.ts +++ b/packages/pl-api/lib/entities/group-relationship.ts @@ -5,7 +5,7 @@ import { GroupRoles } from './group-member'; const groupRelationshipSchema = v.object({ id: v.string(), member: v.fallback(v.boolean(), false), - role: z.nativeEnum(GroupRoles).catch(GroupRoles.USER), + role: v.fallback(v.enum(GroupRoles), GroupRoles.USER), requested: v.fallback(v.boolean(), false), }); diff --git a/packages/pl-api/lib/entities/group.ts b/packages/pl-api/lib/entities/group.ts index 1e0884696..6f811bb5e 100644 --- a/packages/pl-api/lib/entities/group.ts +++ b/packages/pl-api/lib/entities/group.ts @@ -18,7 +18,7 @@ const groupSchema = v.object({ membership_required: v.fallback(v.boolean(), false), members_count: v.fallback(v.number(), 0), owner: v.fallback(v.nullable(v.object({ id: v.string() })), null), - note: z.string().transform(note => note === '

' ? '' : note).catch(''), + note: v.fallback(v.pipe(v.string(), v.transform(note => note === '

' ? '' : note)), ''), relationship: v.fallback(v.nullable(groupRelationshipSchema), null), // Dummy field to be overwritten later statuses_visibility: v.fallback(v.string(), 'public'), uri: v.fallback(v.string(), ''), diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts index d289ec4d8..6a1413781 100644 --- a/packages/pl-api/lib/entities/instance.ts +++ b/packages/pl-api/lib/entities/instance.ts @@ -184,9 +184,9 @@ const pleromaSchema = coerceObject({ }), }), fields_limits: coerceObject({ - max_fields: z.number().nonnegative().catch(4), - name_length: z.number().nonnegative().catch(255), - value_length: z.number().nonnegative().catch(2047), + max_fields: v.fallback(v.pipe(v.number(), v.integer(), v.minValue(0)), 4), + name_length: v.fallback(v.pipe(v.number(), v.integer(), v.minValue(0)), 255), + value_length: v.fallback(v.pipe(v.number(), v.integer(), v.minValue(0)), 2047), }), markup: coerceObject({ allow_headings: v.fallback(v.boolean(), false), @@ -293,43 +293,40 @@ const instanceV1Schema = coerceObject({ }); /** @see {@link https://docs.joinmastodon.org/entities/Instance/} */ -const instanceSchema = z.preprocess((data: any) => { +const instanceSchema = v.pipe( + v.any(), + v.transform((data: any) => { // Detect GoToSocial - if (typeof data.configuration?.accounts?.allow_custom_css === 'boolean') { - data.version = `0.0.0 (compatible; GoToSocial ${data.version})`; - } + if (typeof data.configuration?.accounts?.allow_custom_css === 'boolean') { + data.version = `0.0.0 (compatible; GoToSocial ${data.version})`; + } - const apiVersions = getApiVersions(data); + const apiVersions = getApiVersions(data); - if (data.domain) return { account_domain: data.domain, ...data, api_versions: apiVersions }; + if (data.domain) return { account_domain: data.domain, ...data, api_versions: apiVersions }; - return instanceV1ToV2({ ...data, api_versions: apiVersions }); -}, coerceObject({ - account_domain: v.fallback(v.string(), ''), - api_versions: v.fallback(v.record(v.string(), v.number()), {}), - configuration: configurationSchema, - contact: contactSchema, - description: v.fallback(v.string(), ''), - domain: v.fallback(v.string(), ''), - feature_quote: v.fallback(v.boolean(), false), - fedibird_capabilities: v.fallback(v.array(v.string()), []), - languages: v.fallback(v.array(v.string()), []), - pleroma: pleromaSchema, - registrations: registrations, - rules: filteredArray(ruleSchema), - stats: statsSchema, - thumbnail: thumbnailSchema, - title: v.fallback(v.string(), ''), - usage: usageSchema, - version: v.fallback(v.string(), '0.0.0'), -}).transform((instance) => { - const version = fixVersion(instance.version); - - return { - ...instance, - version, - }; -})); + return instanceV1ToV2({ ...data, api_versions: apiVersions }); + }), + coerceObject({ + account_domain: v.fallback(v.string(), ''), + api_versions: v.fallback(v.record(v.string(), v.number()), {}), + configuration: configurationSchema, + contact: contactSchema, + description: v.fallback(v.string(), ''), + domain: v.fallback(v.string(), ''), + feature_quote: v.fallback(v.boolean(), false), + fedibird_capabilities: v.fallback(v.array(v.string()), []), + languages: v.fallback(v.array(v.string()), []), + pleroma: pleromaSchema, + registrations: registrations, + rules: filteredArray(ruleSchema), + stats: statsSchema, + thumbnail: thumbnailSchema, + title: v.fallback(v.string(), ''), + usage: usageSchema, + version: v.pipe(v.fallback(v.string(), '0.0.0'), v.transform(fixVersion)), + }), +); type Instance = v.InferOutput; diff --git a/packages/pl-api/lib/entities/location.ts b/packages/pl-api/lib/entities/location.ts index 37015600d..ff723fa95 100644 --- a/packages/pl-api/lib/entities/location.ts +++ b/packages/pl-api/lib/entities/location.ts @@ -13,7 +13,7 @@ const locationSchema = v.object({ type: v.fallback(v.string(), ''), timezone: v.fallback(v.string(), ''), geom: v.fallback(v.nullable(v.object({ - coordinates: v.fallback(v.nullable(z.tuple([v.number(), v.number()])), null), + coordinates: v.fallback(v.nullable(v.tuple([v.number(), v.number()])), null), srid: v.fallback(v.string(), ''), })), null), }); diff --git a/packages/pl-api/lib/entities/marker.ts b/packages/pl-api/lib/entities/marker.ts index 9576a9803..c8749a6dd 100644 --- a/packages/pl-api/lib/entities/marker.ts +++ b/packages/pl-api/lib/entities/marker.ts @@ -2,15 +2,19 @@ import * as v from 'valibot'; import { dateSchema } from './utils'; -const markerSchema = z.preprocess((marker: any) => marker ? ({ - unread_count: marker.pleroma?.unread_count, - ...marker, -}) : null, v.object({ - last_read_id: v.string(), - version: v.pipe(v.number(), v.integer()), - updated_at: dateSchema, - unread_count: v.fallback(v.optional(v.pipe(v.number(), v.integer())), undefined), -})); +const markerSchema = v.pipe( + v.any(), + v.transform((marker: any) => marker ? ({ + unread_count: marker.pleroma?.unread_count, + ...marker, + }) : null), + v.object({ + last_read_id: v.string(), + version: v.pipe(v.number(), v.integer()), + updated_at: dateSchema, + unread_count: v.fallback(v.optional(v.pipe(v.number(), v.integer())), undefined), + }), +); /** @see {@link https://docs.joinmastodon.org/entities/Marker/} */ type Marker = v.InferOutput; diff --git a/packages/pl-api/lib/entities/media-attachment.ts b/packages/pl-api/lib/entities/media-attachment.ts index e66cf1fd6..47e9ee8f9 100644 --- a/packages/pl-api/lib/entities/media-attachment.ts +++ b/packages/pl-api/lib/entities/media-attachment.ts @@ -3,16 +3,10 @@ import * as v from 'valibot'; import { mimeSchema } from './utils'; -const blurhashSchema = z.string().superRefine((value, ctx) => { - const r = isBlurhashValid(value); - - if (!r.result) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: r.errorReason, - }); - } -}); +const blurhashSchema = v.pipe(v.string(), v.check( + (value) => isBlurhashValid(value).result, + 'invalid blurhash', // .errorReason +)); const baseAttachmentSchema = v.object({ id: v.string(), @@ -40,8 +34,8 @@ const imageAttachmentSchema = v.object({ original: v.fallback(v.optional(imageMetaSchema), undefined), small: v.fallback(v.optional(imageMetaSchema), undefined), focus: v.fallback(v.optional(v.object({ - x: z.number().min(-1).max(1), - y: z.number().min(-1).max(1), + x: v.pipe(v.number(), v.minValue(-1), v.maxValue(1)), + y: v.pipe(v.number(), v.minValue(-1), v.maxValue(1)), })), undefined), }), {}), }); @@ -54,7 +48,7 @@ const videoAttachmentSchema = v.object({ original: v.fallback(v.optional(v.object({ ...imageMetaSchema.entries, frame_rate: v.fallback(v.nullable(v.pipe(v.string(), v.regex(/\d+\/\d+$/))), null), - duration: v.fallback(v.nullable(z.number().nonnegative()), null), + duration: v.fallback(v.nullable(v.pipe(v.number(), v.minValue(0))), null), })), undefined), small: v.fallback(v.optional(imageMetaSchema), undefined), // WIP: add rest @@ -83,7 +77,7 @@ const audioAttachmentSchema = v.object({ })), undefined), original: v.fallback(v.optional(v.object({ duration: v.fallback(v.optional(v.number()), undefined), - bitrate: z.number().nonnegative().optional().catch(undefined), + bitrate: v.fallback(v.optional(v.pipe(v.number(), v.minValue(0))), undefined), })), undefined), }), {}), }); @@ -94,21 +88,25 @@ const unknownAttachmentSchema = v.object({ }); /** @see {@link https://docs.joinmastodon.org/entities/MediaAttachment} */ -const mediaAttachmentSchema = z.preprocess((data: any) => { - if (!data) return null; +const mediaAttachmentSchema = v.pipe( + v.any(), + v.transform((data: any) => { + if (!data) return null; - return { - mime_type: data.pleroma?.mime_type, - preview_url: data.url, - ...data, - }; -}, v.variant('type', [ - imageAttachmentSchema, - videoAttachmentSchema, - gifvAttachmentSchema, - audioAttachmentSchema, - unknownAttachmentSchema, -])); + return { + mime_type: data.pleroma?.mime_type, + preview_url: data.url, + ...data, + }; + }), + v.variant('type', [ + imageAttachmentSchema, + videoAttachmentSchema, + gifvAttachmentSchema, + audioAttachmentSchema, + unknownAttachmentSchema, + ]), +); type MediaAttachment = v.InferOutput; diff --git a/packages/pl-api/lib/entities/mention.ts b/packages/pl-api/lib/entities/mention.ts index 19cb4132b..43b15c25a 100644 --- a/packages/pl-api/lib/entities/mention.ts +++ b/packages/pl-api/lib/entities/mention.ts @@ -1,18 +1,21 @@ import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/Status/#Mention} */ -const mentionSchema = v.object({ - id: v.string(), - username: v.fallback(v.string(), ''), - url: v.fallback(v.pipe(v.string(), v.url()), ''), - acct: v.string(), -}).transform((mention) => { - if (!mention.username) { - mention.username = mention.acct.split('@')[0]; - } +const mentionSchema = v.pipe( + v.object({ + id: v.string(), + username: v.fallback(v.string(), ''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), + acct: v.string(), + }), + v.transform((mention) => { + if (!mention.username) { + mention.username = mention.acct.split('@')[0]; + } - return mention; -}); + return mention; + }), +); type Mention = v.InferOutput; diff --git a/packages/pl-api/lib/entities/notification.ts b/packages/pl-api/lib/entities/notification.ts index 2a687dc7a..03916463c 100644 --- a/packages/pl-api/lib/entities/notification.ts +++ b/packages/pl-api/lib/entities/notification.ts @@ -84,25 +84,28 @@ const eventParticipationRequestNotificationSchema = v.object({ }); /** @see {@link https://docs.joinmastodon.org/entities/Notification/} */ -const notificationSchema: z.ZodType = z.preprocess((notification: any) => ({ - group_key: `ungrouped-${notification.id}`, - ...pick(notification.pleroma || {}, ['is_muted', 'is_seen']), - ...notification, - type: notification.type === 'pleroma:report' - ? 'admin.report' - : notification.type?.replace(/^pleroma:/, ''), -}), v.variant('type', [ - accountNotificationSchema, - mentionNotificationSchema, - statusNotificationSchema, - reportNotificationSchema, - severedRelationshipNotificationSchema, - moderationWarningNotificationSchema, - moveNotificationSchema, - emojiReactionNotificationSchema, - chatMentionNotificationSchema, - eventParticipationRequestNotificationSchema, -])) as any; +const notificationSchema: v.BaseSchema> = v.pipe( + v.any(), + v.transform((notification: any) => ({ + group_key: `ungrouped-${notification.id}`, + ...pick(notification.pleroma || {}, ['is_muted', 'is_seen']), + ...notification, + type: notification.type === 'pleroma:report' + ? 'admin.report' + : notification.type?.replace(/^pleroma:/, ''), + })), + v.variant('type', [ + accountNotificationSchema, + mentionNotificationSchema, + statusNotificationSchema, + reportNotificationSchema, + severedRelationshipNotificationSchema, + moderationWarningNotificationSchema, + moveNotificationSchema, + emojiReactionNotificationSchema, + chatMentionNotificationSchema, + eventParticipationRequestNotificationSchema, + ])) as any; type Notification = v.InferOutput< | typeof accountNotificationSchema diff --git a/packages/pl-api/lib/entities/oauth-token.ts b/packages/pl-api/lib/entities/oauth-token.ts index cd97b9aac..1370fea50 100644 --- a/packages/pl-api/lib/entities/oauth-token.ts +++ b/packages/pl-api/lib/entities/oauth-token.ts @@ -1,14 +1,18 @@ import * as v from 'valibot'; /** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apioauth_tokens} */ -const oauthTokenSchema = z.preprocess((token: any) => ({ - ...token, - valid_until: token?.valid_until?.padEnd(27, 'Z'), -}), v.object({ - app_name: v.string(), - id: v.number(), - valid_until: z.string().datetime({ offset: true }), -})); +const oauthTokenSchema = v.pipe( + v.any(), + v.transform((token: any) => ({ + ...token, + valid_until: token?.valid_until?.padEnd(27, 'Z'), + })), + v.object({ + app_name: v.string(), + id: v.number(), + valid_until: z.string().datetime({ offset: true }), + }), +); type OauthToken = v.InferOutput; diff --git a/packages/pl-api/lib/entities/poll.ts b/packages/pl-api/lib/entities/poll.ts index 3a76c1ef5..8ee66237f 100644 --- a/packages/pl-api/lib/entities/poll.ts +++ b/packages/pl-api/lib/entities/poll.ts @@ -17,10 +17,10 @@ const pollSchema = v.object({ expires_at: v.fallback(v.nullable(z.string().datetime()), null), id: v.string(), multiple: v.fallback(v.boolean(), false), - options: v.array(pollOptionSchema).min(2), + options: v.pipe(v.array(pollOptionSchema), v.minLength(2)), voters_count: v.fallback(v.number(), 0), votes_count: v.fallback(v.number(), 0), - own_votes: v.fallback(v.nullable(v.array(v.number())).nonempty(), null), + own_votes: v.fallback(v.nullable(v.pipe(v.array(v.number()), v.minLength(1))), null), voted: v.fallback(v.boolean(), false), non_anonymous: v.fallback(v.boolean(), false), diff --git a/packages/pl-api/lib/entities/rule.ts b/packages/pl-api/lib/entities/rule.ts index 998a160a5..a94f2d117 100644 --- a/packages/pl-api/lib/entities/rule.ts +++ b/packages/pl-api/lib/entities/rule.ts @@ -7,10 +7,14 @@ const baseRuleSchema = v.object({ }); /** @see {@link https://docs.joinmastodon.org/entities/Rule/} */ -const ruleSchema = z.preprocess((data: any) => ({ - ...data, - hint: data.hint || data.subtext, -}), baseRuleSchema); +const ruleSchema = v.pipe( + v.any(), + v.transform((data: any) => ({ + ...data, + hint: data.hint || data.subtext, + })), + baseRuleSchema, +); type Rule = v.InferOutput; diff --git a/packages/pl-api/lib/entities/scrobble.ts b/packages/pl-api/lib/entities/scrobble.ts index a2766e704..fb4d6c9ea 100644 --- a/packages/pl-api/lib/entities/scrobble.ts +++ b/packages/pl-api/lib/entities/scrobble.ts @@ -2,19 +2,23 @@ import * as v from 'valibot'; import { accountSchema } from './account'; -const scrobbleSchema = z.preprocess((scrobble: any) => scrobble ? { - external_link: scrobble.externalLink, - ...scrobble, -} : null, v.object({ - id: v.pipe(v.unknown(), v.transform(String)), - account: accountSchema, - created_at: z.string().datetime({ offset: true }), - title: v.string(), - artist: v.fallback(v.string(), ''), - album: v.fallback(v.string(), ''), - external_link: v.fallback(v.nullable(v.string()), null), - length: v.fallback(v.nullable(v.number()), null), -})); +const scrobbleSchema = v.pipe( + v.any(), + v.transform((scrobble: any) => scrobble ? { + external_link: scrobble.externalLink, + ...scrobble, + } : null), + v.object({ + id: v.pipe(v.unknown(), v.transform(String)), + account: accountSchema, + created_at: z.string().datetime({ offset: true }), + title: v.string(), + artist: v.fallback(v.string(), ''), + album: v.fallback(v.string(), ''), + external_link: v.fallback(v.nullable(v.string()), null), + length: v.fallback(v.nullable(v.number()), null), + }), +); type Scrobble = v.InferOutput; diff --git a/packages/pl-api/lib/entities/status.ts b/packages/pl-api/lib/entities/status.ts index c867c45d0..acd6d3140 100644 --- a/packages/pl-api/lib/entities/status.ts +++ b/packages/pl-api/lib/entities/status.ts @@ -134,19 +134,19 @@ const preprocess = (status: any) => { return status; }; -const statusSchema: z.ZodType = z.preprocess(preprocess, v.object({ +const statusSchema: v.BaseSchema> = v.pipe(v.any(), v.transform(preprocess), v.object({ ...baseStatusSchema.entries, - reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), + reblog: v.fallback(v.nullable(v.lazy(() => statusSchema)), null), - quote: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), + quote: v.fallback(v.nullable(v.lazy(() => statusSchema)), null), })) as any; -const statusWithoutAccountSchema = z.preprocess(preprocess, v.object({ +const statusWithoutAccountSchema = v.pipe(v.any(), v.transform(preprocess), v.object({ ...(v.omit(baseStatusSchema, ['account']).entries), account: v.fallback(v.nullable(accountSchema), null), - reblog: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), + reblog: v.fallback(v.nullable(v.lazy(() => statusSchema)), null), - quote: v.fallback(v.nullable(z.lazy(() => statusSchema)), null), + quote: v.fallback(v.nullable(v.lazy(() => statusSchema)), null), })); type Status = v.InferOutput & { diff --git a/packages/pl-api/lib/entities/streaming-event.ts b/packages/pl-api/lib/entities/streaming-event.ts index 45a9f6531..d41e6586f 100644 --- a/packages/pl-api/lib/entities/streaming-event.ts +++ b/packages/pl-api/lib/entities/streaming-event.ts @@ -31,7 +31,7 @@ const baseStreamingEventSchema = v.object({ const statusStreamingEventSchema = v.object({ ...baseStreamingEventSchema.entries, event: v.picklist(['update', 'status.update']), - payload: z.preprocess((payload: any) => JSON.parse(payload), statusSchema), + payload: v.pipe(v.any(), v.transform((payload: any) => JSON.parse(payload)), statusSchema), }); const stringStreamingEventSchema = v.object({ @@ -43,7 +43,7 @@ const stringStreamingEventSchema = v.object({ const notificationStreamingEventSchema = v.object({ ...baseStreamingEventSchema.entries, event: v.literal('notification'), - payload: z.preprocess((payload: any) => JSON.parse(payload), notificationSchema), + payload: v.pipe(v.any(), v.transform((payload: any) => JSON.parse(payload)), notificationSchema), }); const emptyStreamingEventSchema = v.object({ @@ -54,37 +54,37 @@ const emptyStreamingEventSchema = v.object({ const conversationStreamingEventSchema = v.object({ ...baseStreamingEventSchema.entries, event: v.literal('conversation'), - payload: z.preprocess((payload: any) => JSON.parse(payload), conversationSchema), + payload: v.pipe(v.any(), v.transform((payload: any) => JSON.parse(payload)), conversationSchema), }); const announcementStreamingEventSchema = v.object({ ...baseStreamingEventSchema.entries, event: v.literal('announcement'), - payload: z.preprocess((payload: any) => JSON.parse(payload), announcementSchema), + payload: v.pipe(v.any(), v.transform((payload: any) => JSON.parse(payload)), announcementSchema), }); const announcementReactionStreamingEventSchema = v.object({ ...baseStreamingEventSchema.entries, event: v.literal('announcement.reaction'), - payload: z.preprocess((payload: any) => JSON.parse(payload), announcementReactionSchema), + payload: v.pipe(v.any(), v.transform((payload: any) => JSON.parse(payload)), announcementReactionSchema), }); const chatUpdateStreamingEventSchema = v.object({ ...baseStreamingEventSchema.entries, event: v.literal('chat_update'), - payload: z.preprocess((payload: any) => JSON.parse(payload), chatSchema), + payload: v.pipe(v.any(), v.transform((payload: any) => JSON.parse(payload)), chatSchema), }); const followRelationshipsUpdateStreamingEventSchema = v.object({ ...baseStreamingEventSchema.entries, event: v.literal('follow_relationships_update'), - payload: z.preprocess((payload: any) => JSON.parse(payload), followRelationshipUpdateSchema), + payload: v.pipe(v.any(), v.transform((payload: any) => JSON.parse(payload)), followRelationshipUpdateSchema), }); const respondStreamingEventSchema = v.object({ ...baseStreamingEventSchema.entries, event: v.literal('respond'), - payload: z.preprocess((payload: any) => JSON.parse(payload), v.object({ + payload: v.pipe(v.any(), v.transform((payload: any) => JSON.parse(payload)), v.object({ type: v.string(), result: v.picklist(['success', 'ignored', 'error']), })), @@ -93,26 +93,30 @@ const respondStreamingEventSchema = v.object({ const markerStreamingEventSchema = v.object({ ...baseStreamingEventSchema.entries, event: v.literal('marker'), - payload: z.preprocess((payload: any) => JSON.parse(payload), markersSchema), + payload: v.pipe(v.any(), v.transform((payload: any) => JSON.parse(payload)), markersSchema), }); /** @see {@link https://docs.joinmastodon.org/methods/streaming/#events} */ -const streamingEventSchema: z.ZodType = z.preprocess((event: any) => ({ - ...event, - event: event.event?.replace(/^pleroma:/, ''), -}), v.variant('event', [ - statusStreamingEventSchema, - stringStreamingEventSchema, - notificationStreamingEventSchema, - emptyStreamingEventSchema, - conversationStreamingEventSchema, - announcementStreamingEventSchema, - announcementReactionStreamingEventSchema, - chatUpdateStreamingEventSchema, - followRelationshipsUpdateStreamingEventSchema, - respondStreamingEventSchema, - markerStreamingEventSchema, -])) as any; +const streamingEventSchema: v.BaseSchema> = v.pipe( + v.any(), + v.transform((event: any) => ({ + ...event, + event: event.event?.replace(/^pleroma:/, ''), + })), + v.variant('event', [ + statusStreamingEventSchema, + stringStreamingEventSchema, + notificationStreamingEventSchema, + emptyStreamingEventSchema, + conversationStreamingEventSchema, + announcementStreamingEventSchema, + announcementReactionStreamingEventSchema, + chatUpdateStreamingEventSchema, + followRelationshipsUpdateStreamingEventSchema, + respondStreamingEventSchema, + markerStreamingEventSchema, + ]), +) as any; type StreamingEvent = v.InferOutput< | typeof statusStreamingEventSchema diff --git a/packages/pl-api/lib/entities/suggestion.ts b/packages/pl-api/lib/entities/suggestion.ts index c45513455..9b9f12168 100644 --- a/packages/pl-api/lib/entities/suggestion.ts +++ b/packages/pl-api/lib/entities/suggestion.ts @@ -3,37 +3,41 @@ import * as v from 'valibot'; import { accountSchema } from './account'; /** @see {@link https://docs.joinmastodon.org/entities/Suggestion} */ -const suggestionSchema = z.preprocess((suggestion: any) => { +const suggestionSchema = v.pipe( + v.any(), + v.transform((suggestion: any) => { /** * Support `/api/v1/suggestions` * @see {@link https://docs.joinmastodon.org/methods/suggestions/#v1} */ - if (!suggestion) return null; + if (!suggestion) return null; - if (suggestion?.acct) return { - source: 'staff', - sources: ['featured'], - account: suggestion, - }; + if (suggestion?.acct) return { + source: 'staff', + sources: ['featured'], + account: suggestion, + }; - if (!suggestion.sources) { - suggestion.sources = []; - switch (suggestion.source) { - case 'staff': - suggestion.sources.push('staff'); - break; - case 'global': - suggestion.sources.push('most_interactions'); - break; + if (!suggestion.sources) { + suggestion.sources = []; + switch (suggestion.source) { + case 'staff': + suggestion.sources.push('staff'); + break; + case 'global': + suggestion.sources.push('most_interactions'); + break; + } } - } - return suggestion; -}, v.object({ - source: v.fallback(v.nullable(v.string()), null), - sources: v.fallback(v.array(v.string()), []), - account: accountSchema, -})); + return suggestion; + }), + v.object({ + source: v.fallback(v.nullable(v.string()), null), + sources: v.fallback(v.array(v.string()), []), + account: accountSchema, + }), +); type Suggestion = v.InferOutput; diff --git a/packages/pl-api/lib/entities/tag.ts b/packages/pl-api/lib/entities/tag.ts index 60fe97ce7..c1518bbde 100644 --- a/packages/pl-api/lib/entities/tag.ts +++ b/packages/pl-api/lib/entities/tag.ts @@ -8,7 +8,7 @@ const historySchema = v.object({ /** @see {@link https://docs.joinmastodon.org/entities/tag} */ const tagSchema = v.object({ - name: z.string().min(1), + name: v.pipe(v.string(), v.minLength(1)), url: v.fallback(v.pipe(v.string(), v.url()), ''), history: v.fallback(v.nullable(historySchema), null), following: v.fallback(v.optional(v.boolean()), undefined), diff --git a/packages/pl-api/lib/entities/translation.ts b/packages/pl-api/lib/entities/translation.ts index 40eeee6a0..07ffa270d 100644 --- a/packages/pl-api/lib/entities/translation.ts +++ b/packages/pl-api/lib/entities/translation.ts @@ -15,27 +15,31 @@ const translationMediaAttachment = v.object({ }); /** @see {@link https://docs.joinmastodon.org/entities/Translation/} */ -const translationSchema = z.preprocess((translation: any) => { +const translationSchema = v.pipe( + v.any(), + v.transform((translation: any) => { /** * handle Akkoma * @see {@link https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/lib/pleroma/web/mastodon_api/controllers/status_controller.ex#L504} */ - if (translation?.text) return { - content: translation.text, - detected_source_language: translation.detected_language, - provider: '', - }; + if (translation?.text) return { + content: translation.text, + detected_source_language: translation.detected_language, + provider: '', + }; - return translation; -}, v.object({ - id: v.fallback(v.nullable(v.string()), null), - content: v.fallback(v.string(), ''), - spoiler_text: v.fallback(v.string(), ''), - poll: v.fallback(v.optional(translationPollSchema), undefined), - media_attachments: filteredArray(translationMediaAttachment), - detected_source_language: v.string(), - provider: v.string(), -})); + return translation; + }), + v.object({ + id: v.fallback(v.nullable(v.string()), null), + content: v.fallback(v.string(), ''), + spoiler_text: v.fallback(v.string(), ''), + poll: v.fallback(v.optional(translationPollSchema), undefined), + media_attachments: filteredArray(translationMediaAttachment), + detected_source_language: v.string(), + provider: v.string(), + }), +); type Translation = v.InferOutput; diff --git a/packages/pl-api/lib/entities/trends-link.ts b/packages/pl-api/lib/entities/trends-link.ts index f43e73f0c..0ed03dbd2 100644 --- a/packages/pl-api/lib/entities/trends-link.ts +++ b/packages/pl-api/lib/entities/trends-link.ts @@ -4,25 +4,29 @@ import { blurhashSchema } from './media-attachment'; import { historySchema } from './tag'; /** @see {@link https://docs.joinmastodon.org/entities/PreviewCard/#trends-link} */ -const trendsLinkSchema = z.preprocess((link: any) => ({ ...link, id: link.url }), v.object({ - id: v.fallback(v.string(), ''), - url: v.fallback(v.pipe(v.string(), v.url()), ''), - title: v.fallback(v.string(), ''), - description: v.fallback(v.string(), ''), - type: v.fallback(v.picklist(['link', 'photo', 'video', 'rich']), 'link'), - author_name: v.fallback(v.string(), ''), - author_url: v.fallback(v.string(), ''), - provider_name: v.fallback(v.string(), ''), - provider_url: v.fallback(v.string(), ''), - html: v.fallback(v.string(), ''), - width: v.fallback(v.nullable(v.number()), null), - height: v.fallback(v.nullable(v.number()), null), - image: v.fallback(v.nullable(v.string()), null), - image_description: v.fallback(v.nullable(v.string()), null), - embed_url: v.fallback(v.string(), ''), - blurhash: v.fallback(v.nullable(blurhashSchema), null), - history: v.fallback(v.nullable(historySchema), null), -})); +const trendsLinkSchema = v.pipe( + v.any(), + v.transform((link: any) => ({ ...link, id: link.url })), + v.object({ + id: v.fallback(v.string(), ''), + url: v.fallback(v.pipe(v.string(), v.url()), ''), + title: v.fallback(v.string(), ''), + description: v.fallback(v.string(), ''), + type: v.fallback(v.picklist(['link', 'photo', 'video', 'rich']), 'link'), + author_name: v.fallback(v.string(), ''), + author_url: v.fallback(v.string(), ''), + provider_name: v.fallback(v.string(), ''), + provider_url: v.fallback(v.string(), ''), + html: v.fallback(v.string(), ''), + width: v.fallback(v.nullable(v.number()), null), + height: v.fallback(v.nullable(v.number()), null), + image: v.fallback(v.nullable(v.string()), null), + image_description: v.fallback(v.nullable(v.string()), null), + embed_url: v.fallback(v.string(), ''), + blurhash: v.fallback(v.nullable(blurhashSchema), null), + history: v.fallback(v.nullable(historySchema), null), + }), +); type TrendsLink = v.InferOutput; diff --git a/packages/pl-api/lib/entities/utils.ts b/packages/pl-api/lib/entities/utils.ts index 4bbadbc5c..eaee8fe94 100644 --- a/packages/pl-api/lib/entities/utils.ts +++ b/packages/pl-api/lib/entities/utils.ts @@ -4,14 +4,16 @@ import * as v from 'valibot'; const dateSchema = z.string().datetime({ offset: true }).catch(new Date().toUTCString()); /** Validates individual items in an array, dropping any that aren't valid. */ -const filteredArray = (schema: T) => - z.any().array().catch([]) - .transform((arr) => ( +const filteredArray = (schema: v.BaseSchema>) => + v.pipe( + v.fallback(v.array(v.any()), []), + v.transform((arr) => ( arr.map((item) => { - const parsed = schema.safeParse(item); - return parsed.success ? parsed.data : undefined; - }).filter((item): item is v.InferOutput => Boolean(item)) - )); + const parsed = v.safeParse(schema, item); + return parsed.success ? parsed.output : undefined; + }).filter((item): item is T => Boolean(item)) + )), + ); /** Validates the string as an emoji. */ const emojiSchema = v.pipe(v.string(), v.emoji()); diff --git a/packages/pl-fe/src/api/hooks/accounts/useRelationship.ts b/packages/pl-fe/src/api/hooks/accounts/useRelationship.ts index d47f3e68e..5f3749014 100644 --- a/packages/pl-fe/src/api/hooks/accounts/useRelationship.ts +++ b/packages/pl-fe/src/api/hooks/accounts/useRelationship.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { Entities } from 'pl-fe/entity-store/entities'; import { useEntity } from 'pl-fe/entity-store/hooks'; @@ -19,7 +19,7 @@ const useRelationship = (accountId: string | undefined, opts: UseRelationshipOpt () => client.accounts.getRelationships([accountId!]), { enabled: enabled && !!accountId, - schema: z.any().transform(arr => arr[0]), + schema: v.pipe(v.any(), v.transform(arr => arr[0])), }, ); diff --git a/packages/pl-fe/src/api/hooks/groups/useDemoteGroupMember.ts b/packages/pl-fe/src/api/hooks/groups/useDemoteGroupMember.ts index 1a1e4d81f..7d51d6238 100644 --- a/packages/pl-fe/src/api/hooks/groups/useDemoteGroupMember.ts +++ b/packages/pl-fe/src/api/hooks/groups/useDemoteGroupMember.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { Entities } from 'pl-fe/entity-store/entities'; import { useCreateEntity } from 'pl-fe/entity-store/hooks'; @@ -13,7 +13,7 @@ const useDemoteGroupMember = (group: Pick, groupMember: Pick client.experimental.groups.demoteGroupUsers(group.id, account_ids, role), - { schema: z.any().transform((arr) => arr[0]), transform: normalizeGroupMember }, + { schema: v.pipe(v.any(), v.transform(arr => arr[0])), transform: normalizeGroupMember }, ); return createEntity; diff --git a/packages/pl-fe/src/api/hooks/groups/useGroupRelationship.ts b/packages/pl-fe/src/api/hooks/groups/useGroupRelationship.ts index 1241f1d5d..9f77f5af4 100644 --- a/packages/pl-fe/src/api/hooks/groups/useGroupRelationship.ts +++ b/packages/pl-fe/src/api/hooks/groups/useGroupRelationship.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { Entities } from 'pl-fe/entity-store/entities'; import { useEntity } from 'pl-fe/entity-store/hooks'; @@ -14,7 +14,7 @@ const useGroupRelationship = (groupId: string | undefined) => { () => client.experimental.groups.getGroupRelationships([groupId!]), { enabled: !!groupId, - schema: z.any().transform(arr => arr[0]), + schema: v.pipe(v.any(), v.transform(arr => arr[0])), }, ); diff --git a/packages/pl-fe/src/api/hooks/groups/usePromoteGroupMember.ts b/packages/pl-fe/src/api/hooks/groups/usePromoteGroupMember.ts index 641d77922..5824e3a46 100644 --- a/packages/pl-fe/src/api/hooks/groups/usePromoteGroupMember.ts +++ b/packages/pl-fe/src/api/hooks/groups/usePromoteGroupMember.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { Entities } from 'pl-fe/entity-store/entities'; import { useCreateEntity } from 'pl-fe/entity-store/hooks'; @@ -13,7 +13,7 @@ const usePromoteGroupMember = (group: Pick, groupMember: Pick client.experimental.groups.promoteGroupUsers(group.id, account_ids, role), - { schema: z.any().transform((arr) => arr[0]), transform: normalizeGroupMember }, + { schema: v.pipe(v.any(), v.transform(arr => arr[0])), transform: normalizeGroupMember }, ); return createEntity; diff --git a/packages/pl-fe/src/entity-store/hooks/types.ts b/packages/pl-fe/src/entity-store/hooks/types.ts index 95a4e0baf..64b1e9e8f 100644 --- a/packages/pl-fe/src/entity-store/hooks/types.ts +++ b/packages/pl-fe/src/entity-store/hooks/types.ts @@ -1,7 +1,7 @@ import type { Entity } from '../types'; -import type z from 'zod'; +import type { BaseSchema, BaseIssue } from 'valibot'; -type EntitySchema = z.ZodType; +type EntitySchema = BaseSchema>; /** * Tells us where to find/store the entity in the cache. diff --git a/packages/pl-fe/src/entity-store/hooks/useBatchedEntities.ts b/packages/pl-fe/src/entity-store/hooks/useBatchedEntities.ts index 968ed38b3..746452753 100644 --- a/packages/pl-fe/src/entity-store/hooks/useBatchedEntities.ts +++ b/packages/pl-fe/src/entity-store/hooks/useBatchedEntities.ts @@ -54,7 +54,7 @@ const useBatchedEntities = ( dispatch(entitiesFetchRequest(entityType, listKey)); try { const response = await entityFn(filteredIds); - const entities = filteredArray(schema).parse(response); + const entities = v.parse(filteredArray(schema), response); dispatch(entitiesFetchSuccess(entities, entityType, listKey, 'end', { next: null, prev: null, diff --git a/packages/pl-fe/src/entity-store/hooks/useCreateEntity.ts b/packages/pl-fe/src/entity-store/hooks/useCreateEntity.ts index 9ed369471..68ede31e7 100644 --- a/packages/pl-fe/src/entity-store/hooks/useCreateEntity.ts +++ b/packages/pl-fe/src/entity-store/hooks/useCreateEntity.ts @@ -32,7 +32,7 @@ const useCreateEntity = => { const result = await setPromise(entityFn(data)); const schema = opts.schema || z.custom(); - let entity: TEntity | TTransformedEntity = schema.parse(result); + let entity: TEntity | TTransformedEntity = v.parse(schema, result); if (opts.transform) entity = opts.transform(entity); // TODO: optimistic updating diff --git a/packages/pl-fe/src/entity-store/hooks/useEntities.ts b/packages/pl-fe/src/entity-store/hooks/useEntities.ts index 071b87063..30629a0da 100644 --- a/packages/pl-fe/src/entity-store/hooks/useEntities.ts +++ b/packages/pl-fe/src/entity-store/hooks/useEntities.ts @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import z from 'zod'; +import * as v from 'valibot'; import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch'; import { useAppSelector } from 'pl-fe/hooks/useAppSelector'; @@ -64,7 +64,7 @@ const useEntities = { try { const response = await setPromise(entityFn()); - let entity: TEntity | TTransformedEntity = schema.parse(response); + let entity: TEntity | TTransformedEntity = v.parse(schema, response); if (opts.transform) entity = opts.transform(entity); dispatch(importEntities([entity], entityType)); } catch (e) { diff --git a/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts b/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts index bf598d8b8..683077c97 100644 --- a/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts +++ b/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts @@ -36,7 +36,7 @@ const useEntityLookup = { try { const response = await setPromise(entityFn()); - const entity = schema.parse(response); + const entity = v.parse(schema, response); const transformedEntity = opts.transform ? opts.transform(entity) : entity; setFetchedEntity(transformedEntity as TTransformedEntity); dispatch(importEntities([transformedEntity], entityType)); diff --git a/packages/pl-fe/src/schemas/utils.ts b/packages/pl-fe/src/schemas/utils.ts index 775a349c6..ed75567ee 100644 --- a/packages/pl-fe/src/schemas/utils.ts +++ b/packages/pl-fe/src/schemas/utils.ts @@ -1,17 +1,18 @@ import * as v from 'valibot'; -import z from 'zod'; import type { CustomEmoji } from 'pl-api'; /** Validates individual items in an array, dropping any that aren't valid. */ -const filteredArray = (schema: T) => - z.any().array().catch([]) - .transform((arr) => ( +const filteredArray = (schema: v.BaseSchema>) => + v.pipe( + v.fallback(v.array(v.any()), []), + v.transform((arr) => ( arr.map((item) => { - const parsed = schema.safeParse(item); - return parsed.success ? parsed.data : undefined; - }).filter((item): item is z.infer => Boolean(item)) - )); + const parsed = v.safeParse(schema, item); + return parsed.success ? parsed.output : undefined; + }).filter((item): item is T => Boolean(item)) + )), + ); /** Map a list of CustomEmoji to their shortcodes. */ const makeCustomEmojiMap = (customEmojis: CustomEmoji[]) => From a521c9044d7cc1a11d8e591273479c27e7a1e448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 17:03:27 +0200 Subject: [PATCH 17/28] make pl-api compile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/lib/entities/account-warning.ts | 4 ++-- packages/pl-api/lib/entities/account.ts | 8 ++++---- packages/pl-api/lib/entities/admin/account.ts | 4 ++-- packages/pl-api/lib/entities/admin/cohort.ts | 6 ++++-- packages/pl-api/lib/entities/admin/domain-allow.ts | 4 ++-- packages/pl-api/lib/entities/admin/domain-block.ts | 4 ++-- packages/pl-api/lib/entities/admin/domain.ts | 4 +++- .../lib/entities/admin/email-domain-block.ts | 4 ++-- packages/pl-api/lib/entities/admin/ip-block.ts | 6 +++--- packages/pl-api/lib/entities/admin/ip.ts | 4 ++-- packages/pl-api/lib/entities/admin/measure.ts | 4 +++- packages/pl-api/lib/entities/admin/report.ts | 8 ++++---- packages/pl-api/lib/entities/announcement.ts | 10 +++++----- packages/pl-api/lib/entities/backup.ts | 4 ++-- packages/pl-api/lib/entities/chat-message.ts | 4 ++-- packages/pl-api/lib/entities/chat.ts | 4 ++-- .../pl-api/lib/entities/extended-description.ts | 4 ++-- packages/pl-api/lib/entities/filter.ts | 4 ++-- packages/pl-api/lib/entities/group.ts | 4 ++-- .../pl-api/lib/entities/interaction-request.ts | 7 ++++--- packages/pl-api/lib/entities/marker.ts | 4 ++-- .../pl-api/lib/entities/notification-request.ts | 6 +++--- packages/pl-api/lib/entities/notification.ts | 4 ++-- packages/pl-api/lib/entities/oauth-token.ts | 4 +++- packages/pl-api/lib/entities/poll.ts | 4 ++-- .../lib/entities/relationship-severance-event.ts | 4 ++-- packages/pl-api/lib/entities/report.ts | 6 +++--- packages/pl-api/lib/entities/scheduled-status.ts | 6 +++--- packages/pl-api/lib/entities/scrobble.ts | 3 ++- packages/pl-api/lib/entities/status-edit.ts | 4 ++-- packages/pl-api/lib/entities/status.ts | 14 +++++++------- packages/pl-api/lib/entities/utils.ts | 10 ++++++++-- 32 files changed, 93 insertions(+), 77 deletions(-) diff --git a/packages/pl-api/lib/entities/account-warning.ts b/packages/pl-api/lib/entities/account-warning.ts index 1065dbc38..57befd64f 100644 --- a/packages/pl-api/lib/entities/account-warning.ts +++ b/packages/pl-api/lib/entities/account-warning.ts @@ -1,7 +1,7 @@ import * as v from 'valibot'; import { accountSchema } from './account'; -import { dateSchema } from './utils'; +import { datetimeSchema } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/Appeal/} */ const appealSchema = v.object({ @@ -17,7 +17,7 @@ const accountWarningSchema = v.object({ status_ids: v.fallback(v.array(v.string()), []), target_account: accountSchema, appeal: v.fallback(v.nullable(appealSchema), null), - created_at: dateSchema, + created_at: v.fallback(datetimeSchema, new Date().toISOString()), }); type AccountWarning = v.InferOutput; diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index 212bd3ba8..420047317 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -4,7 +4,7 @@ import * as v from 'valibot'; import { customEmojiSchema } from './custom-emoji'; import { relationshipSchema } from './relationship'; import { roleSchema } from './role'; -import { coerceObject, dateSchema, filteredArray } from './utils'; +import { coerceObject, datetimeSchema, filteredArray } from './utils'; const filterBadges = (tags?: string[]) => tags?.filter(tag => tag.startsWith('badge:')).map(tag => v.parse(roleSchema, { id: tag, name: tag.replace(/^badge:/, '') })); @@ -64,7 +64,7 @@ const preprocessAccount = v.transform((account: any) => { const fieldSchema = v.object({ name: v.string(), value: v.string(), - verified_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), + verified_at: v.fallback(v.nullable(datetimeSchema), null), }); const baseAccountSchema = v.object({ @@ -87,7 +87,7 @@ const baseAccountSchema = v.object({ noindex: v.fallback(v.nullable(v.boolean()), null), suspended: v.fallback(v.optional(v.boolean()), undefined), limited: v.fallback(v.optional(v.boolean()), undefined), - created_at: z.string().datetime().catch(new Date().toUTCString()), + created_at: v.fallback(datetimeSchema, new Date().toUTCString()), last_status_at: v.fallback(v.nullable(v.pipe(v.string(), v.isoDate())), null), statuses_count: v.fallback(v.number(), 0), followers_count: v.fallback(v.number(), 0), @@ -177,7 +177,7 @@ const credentialAccountSchema: v.BaseSchema & WithMoved; diff --git a/packages/pl-api/lib/entities/admin/account.ts b/packages/pl-api/lib/entities/admin/account.ts index 065e33ca5..58c713bf5 100644 --- a/packages/pl-api/lib/entities/admin/account.ts +++ b/packages/pl-api/lib/entities/admin/account.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; import { accountSchema } from '../account'; import { roleSchema } from '../role'; -import { dateSchema, filteredArray } from '../utils'; +import { datetimeSchema, filteredArray } from '../utils'; import { adminIpSchema } from './ip'; @@ -43,7 +43,7 @@ const adminAccountSchema = v.pipe( id: v.string(), username: v.string(), domain: v.fallback(v.nullable(v.string()), null), - created_at: dateSchema, + created_at: v.fallback(datetimeSchema, new Date().toISOString()), email: v.fallback(v.nullable(v.string()), null), ip: v.fallback(v.nullable(v.pipe(v.string(), v.ip())), null), ips: filteredArray(adminIpSchema), diff --git a/packages/pl-api/lib/entities/admin/cohort.ts b/packages/pl-api/lib/entities/admin/cohort.ts index 5275ee50a..c536f107b 100644 --- a/packages/pl-api/lib/entities/admin/cohort.ts +++ b/packages/pl-api/lib/entities/admin/cohort.ts @@ -1,11 +1,13 @@ import * as v from 'valibot'; +import { datetimeSchema } from '../utils'; + /** @see {@link https://docs.joinmastodon.org/entities/Admin_Cohort/} */ const adminCohortSchema = v.object({ - period: z.string().datetime({ offset: true }), + period: datetimeSchema, frequency: v.picklist(['day', 'month']), data: v.array(v.object({ - date: z.string().datetime({ offset: true }), + date: datetimeSchema, rate: v.number(), value: v.pipe(v.number(), v.integer()), })), diff --git a/packages/pl-api/lib/entities/admin/domain-allow.ts b/packages/pl-api/lib/entities/admin/domain-allow.ts index be6c73078..5024d2b12 100644 --- a/packages/pl-api/lib/entities/admin/domain-allow.ts +++ b/packages/pl-api/lib/entities/admin/domain-allow.ts @@ -1,12 +1,12 @@ import * as v from 'valibot'; -import { dateSchema } from '../utils'; +import { datetimeSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_DomainAllow/} */ const adminDomainAllowSchema = v.object({ id: v.string(), domain: v.string(), - created_at: dateSchema, + created_at: datetimeSchema, }); type AdminDomainAllow = v.InferOutput; diff --git a/packages/pl-api/lib/entities/admin/domain-block.ts b/packages/pl-api/lib/entities/admin/domain-block.ts index d87f18338..0dca14241 100644 --- a/packages/pl-api/lib/entities/admin/domain-block.ts +++ b/packages/pl-api/lib/entities/admin/domain-block.ts @@ -1,13 +1,13 @@ import * as v from 'valibot'; -import { dateSchema } from '../utils'; +import { datetimeSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_DomainBlock/} */ const adminDomainBlockSchema = v.object({ id: v.string(), domain: v.string(), digest: v.string(), - created_at: dateSchema, + created_at: datetimeSchema, severity: v.picklist(['silence', 'suspend', 'noop']), reject_media: v.boolean(), reject_reports: v.boolean(), diff --git a/packages/pl-api/lib/entities/admin/domain.ts b/packages/pl-api/lib/entities/admin/domain.ts index 2eb5e6a49..f0b566670 100644 --- a/packages/pl-api/lib/entities/admin/domain.ts +++ b/packages/pl-api/lib/entities/admin/domain.ts @@ -1,11 +1,13 @@ import * as v from 'valibot'; +import { datetimeSchema } from '../utils'; + const adminDomainSchema = v.object({ domain: v.fallback(v.string(), ''), id: v.pipe(v.unknown(), v.transform(String)), public: v.fallback(v.boolean(), false), resolves: v.fallback(v.boolean(), false), - last_checked_at: z.string().datetime().catch(''), + last_checked_at: v.fallback(v.nullable(datetimeSchema), null), }); type AdminDomain = v.InferOutput diff --git a/packages/pl-api/lib/entities/admin/email-domain-block.ts b/packages/pl-api/lib/entities/admin/email-domain-block.ts index 5c5e8ecd6..724f44bb0 100644 --- a/packages/pl-api/lib/entities/admin/email-domain-block.ts +++ b/packages/pl-api/lib/entities/admin/email-domain-block.ts @@ -1,12 +1,12 @@ import * as v from 'valibot'; -import { dateSchema } from '../utils'; +import { datetimeSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_EmailDomainBlock/} */ const adminEmailDomainBlockSchema = v.object({ id: v.string(), domain: v.string(), - created_at: dateSchema, + created_at: datetimeSchema, history: v.array(v.object({ day: v.pipe(v.unknown(), v.transform(String)), accounts: v.pipe(v.unknown(), v.transform(String)), diff --git a/packages/pl-api/lib/entities/admin/ip-block.ts b/packages/pl-api/lib/entities/admin/ip-block.ts index 5347035d8..1a3a62108 100644 --- a/packages/pl-api/lib/entities/admin/ip-block.ts +++ b/packages/pl-api/lib/entities/admin/ip-block.ts @@ -1,6 +1,6 @@ import * as v from 'valibot'; -import { dateSchema } from '../utils'; +import { datetimeSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_IpBlock/} */ const adminIpBlockSchema = v.object({ @@ -8,8 +8,8 @@ const adminIpBlockSchema = v.object({ ip: v.pipe(v.string(), v.ip()), severity: v.picklist(['sign_up_requires_approval', 'sign_up_block', 'no_access']), comment: v.fallback(v.string(), ''), - created_at: dateSchema, - expires_at: z.string().datetime({ offset: true }), + created_at: datetimeSchema, + expires_at: v.fallback(v.nullable(datetimeSchema), null), }); type AdminIpBlock = v.InferOutput; diff --git a/packages/pl-api/lib/entities/admin/ip.ts b/packages/pl-api/lib/entities/admin/ip.ts index 8d99befa4..f1adce518 100644 --- a/packages/pl-api/lib/entities/admin/ip.ts +++ b/packages/pl-api/lib/entities/admin/ip.ts @@ -1,11 +1,11 @@ import * as v from 'valibot'; -import { dateSchema } from '../utils'; +import { datetimeSchema } from '../utils'; /** @see {@link https://docs.joinmastodon.org/entities/Admin_Ip/} */ const adminIpSchema = v.object({ ip: v.pipe(v.string(), v.ip()), - used_at: dateSchema, + used_at: datetimeSchema, }); type AdminIp = v.InferOutput; diff --git a/packages/pl-api/lib/entities/admin/measure.ts b/packages/pl-api/lib/entities/admin/measure.ts index 87ccc44f2..fed8a988f 100644 --- a/packages/pl-api/lib/entities/admin/measure.ts +++ b/packages/pl-api/lib/entities/admin/measure.ts @@ -1,5 +1,7 @@ import * as v from 'valibot'; +import { datetimeSchema } from '../utils'; + /** @see {@link https://docs.joinmastodon.org/entities/Admin_Measure/} */ const adminMeasureSchema = v.object({ key: v.string(), @@ -8,7 +10,7 @@ const adminMeasureSchema = v.object({ human_value: v.fallback(v.optional(v.string()), undefined), previous_total: v.fallback(v.optional(v.pipe(v.unknown(), v.transform(String))), undefined), data: v.array(v.object({ - date: z.string().datetime({ offset: true }), + date: datetimeSchema, value: v.pipe(v.unknown(), v.transform(String)), })), }); diff --git a/packages/pl-api/lib/entities/admin/report.ts b/packages/pl-api/lib/entities/admin/report.ts index 0a577ecd2..c3386aec1 100644 --- a/packages/pl-api/lib/entities/admin/report.ts +++ b/packages/pl-api/lib/entities/admin/report.ts @@ -3,7 +3,7 @@ import * as v from 'valibot'; import { ruleSchema } from '../rule'; import { statusWithoutAccountSchema } from '../status'; -import { dateSchema, filteredArray } from '../utils'; +import { datetimeSchema, filteredArray } from '../utils'; import { adminAccountSchema } from './account'; @@ -30,12 +30,12 @@ const adminReportSchema = v.pipe( v.object({ id: v.string(), action_taken: v.fallback(v.optional(v.boolean()), undefined), - action_taken_at: v.fallback(v.nullable(dateSchema), null), + action_taken_at: v.fallback(v.nullable(datetimeSchema), null), category: v.fallback(v.optional(v.string()), undefined), comment: v.fallback(v.optional(v.string()), undefined), forwarded: v.fallback(v.optional(v.boolean()), undefined), - created_at: v.fallback(v.optional(dateSchema), undefined), - updated_at: v.fallback(v.optional(dateSchema), undefined), + created_at: v.fallback(v.optional(datetimeSchema), undefined), + updated_at: v.fallback(v.optional(datetimeSchema), undefined), account: adminAccountSchema, target_account: adminAccountSchema, assigned_account: v.fallback(v.nullable(adminAccountSchema), null), diff --git a/packages/pl-api/lib/entities/announcement.ts b/packages/pl-api/lib/entities/announcement.ts index b62950d38..5830f3de3 100644 --- a/packages/pl-api/lib/entities/announcement.ts +++ b/packages/pl-api/lib/entities/announcement.ts @@ -4,17 +4,17 @@ import { announcementReactionSchema } from './announcement-reaction'; import { customEmojiSchema } from './custom-emoji'; import { mentionSchema } from './mention'; import { tagSchema } from './tag'; -import { dateSchema, filteredArray } from './utils'; +import { datetimeSchema, filteredArray } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/announcement/} */ const announcementSchema = v.object({ id: v.string(), content: v.fallback(v.string(), ''), - starts_at: v.fallback(v.nullable(z.string().datetime()), null), - ends_at: v.fallback(v.nullable(z.string().datetime()), null), + starts_at: v.fallback(v.nullable(datetimeSchema), null), + ends_at: v.fallback(v.nullable(datetimeSchema), null), all_day: v.fallback(v.boolean(), false), read: v.fallback(v.boolean(), false), - published_at: dateSchema, + published_at: v.fallback(datetimeSchema, new Date().toISOString()), reactions: filteredArray(announcementReactionSchema), statuses: v.pipe( v.any(), @@ -26,7 +26,7 @@ const announcementSchema = v.object({ mentions: filteredArray(mentionSchema), tags: filteredArray(tagSchema), emojis: filteredArray(customEmojiSchema), - updated_at: dateSchema, + updated_at: v.fallback(datetimeSchema, new Date().toISOString()), }); type Announcement = v.InferOutput; diff --git a/packages/pl-api/lib/entities/backup.ts b/packages/pl-api/lib/entities/backup.ts index ddc10ee0f..fa5008f37 100644 --- a/packages/pl-api/lib/entities/backup.ts +++ b/packages/pl-api/lib/entities/backup.ts @@ -1,13 +1,13 @@ import * as v from 'valibot'; -import { dateSchema, mimeSchema } from './utils'; +import { datetimeSchema, mimeSchema } from './utils'; /** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromabackups} */ const backupSchema = v.object({ id: v.pipe(v.unknown(), v.transform(String)), contentType: mimeSchema, file_size: v.fallback(v.number(), 0), - inserted_at: dateSchema, + inserted_at: datetimeSchema, processed: v.fallback(v.boolean(), false), url: v.fallback(v.string(), ''), }); diff --git a/packages/pl-api/lib/entities/chat-message.ts b/packages/pl-api/lib/entities/chat-message.ts index 387e3bc8e..59ca3d39d 100644 --- a/packages/pl-api/lib/entities/chat-message.ts +++ b/packages/pl-api/lib/entities/chat-message.ts @@ -3,7 +3,7 @@ import * as v from 'valibot'; import { customEmojiSchema } from './custom-emoji'; import { mediaAttachmentSchema } from './media-attachment'; import { previewCardSchema } from './preview-card'; -import { dateSchema, filteredArray } from './utils'; +import { datetimeSchema, filteredArray } from './utils'; /** @see {@link https://docs.pleroma.social/backend/development/API/chats/#getting-the-messages-for-a-chat} */ const chatMessageSchema = v.object({ @@ -11,7 +11,7 @@ const chatMessageSchema = v.object({ content: v.fallback(v.string(), ''), chat_id: v.string(), account_id: v.string(), - created_at: dateSchema, + created_at: datetimeSchema, emojis: filteredArray(customEmojiSchema), attachment: v.fallback(v.nullable(mediaAttachmentSchema), null), unread: v.boolean(), diff --git a/packages/pl-api/lib/entities/chat.ts b/packages/pl-api/lib/entities/chat.ts index cddad9af7..a7597d82b 100644 --- a/packages/pl-api/lib/entities/chat.ts +++ b/packages/pl-api/lib/entities/chat.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; import { accountSchema } from './account'; import { chatMessageSchema } from './chat-message'; -import { dateSchema } from './utils'; +import { datetimeSchema } from './utils'; /** @see {@link https://docs.pleroma.social/backend/development/API/chats/#getting-a-list-of-chats} */ const chatSchema = v.object({ @@ -10,7 +10,7 @@ const chatSchema = v.object({ account: accountSchema, unread: v.pipe(v.number(), v.integer()), last_message: v.fallback(v.nullable(chatMessageSchema), null), - created_at: dateSchema, + updated_at: datetimeSchema, }); type Chat = v.InferOutput; diff --git a/packages/pl-api/lib/entities/extended-description.ts b/packages/pl-api/lib/entities/extended-description.ts index be7fb5ab6..934f7a413 100644 --- a/packages/pl-api/lib/entities/extended-description.ts +++ b/packages/pl-api/lib/entities/extended-description.ts @@ -1,10 +1,10 @@ import * as v from 'valibot'; -import { dateSchema } from './utils'; +import { datetimeSchema } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/ExtendedDescription} */ const extendedDescriptionSchema = v.object({ - updated_at: dateSchema, + updated_at: datetimeSchema, content: v.string(), }); diff --git a/packages/pl-api/lib/entities/filter.ts b/packages/pl-api/lib/entities/filter.ts index cbc34e06b..07c898f2b 100644 --- a/packages/pl-api/lib/entities/filter.ts +++ b/packages/pl-api/lib/entities/filter.ts @@ -1,6 +1,6 @@ import * as v from 'valibot'; -import { filteredArray } from './utils'; +import { datetimeSchema, filteredArray } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/FilterKeyword/} */ const filterKeywordSchema = v.object({ @@ -37,7 +37,7 @@ const filterSchema = v.pipe( id: v.string(), title: v.string(), context: v.array(v.picklist(['home', 'notifications', 'public', 'thread', 'account'])), - expires_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), + expires_at: v.fallback(v.nullable(datetimeSchema), null), filter_action: v.fallback(v.picklist(['warn', 'hide']), 'warn'), keywords: filteredArray(filterKeywordSchema), statuses: filteredArray(filterStatusSchema), diff --git a/packages/pl-api/lib/entities/group.ts b/packages/pl-api/lib/entities/group.ts index 6f811bb5e..47b413e0e 100644 --- a/packages/pl-api/lib/entities/group.ts +++ b/packages/pl-api/lib/entities/group.ts @@ -2,12 +2,12 @@ import * as v from 'valibot'; import { customEmojiSchema } from './custom-emoji'; import { groupRelationshipSchema } from './group-relationship'; -import { filteredArray } from './utils'; +import { datetimeSchema, filteredArray } from './utils'; const groupSchema = v.object({ avatar: v.fallback(v.string(), ''), avatar_static: v.fallback(v.string(), ''), - created_at: z.string().datetime().catch(new Date().toUTCString()), + created_at: v.fallback(datetimeSchema, new Date().toISOString()), display_name: v.fallback(v.string(), ''), domain: v.fallback(v.string(), ''), emojis: filteredArray(customEmojiSchema), diff --git a/packages/pl-api/lib/entities/interaction-request.ts b/packages/pl-api/lib/entities/interaction-request.ts index b75288846..b917292ea 100644 --- a/packages/pl-api/lib/entities/interaction-request.ts +++ b/packages/pl-api/lib/entities/interaction-request.ts @@ -2,14 +2,15 @@ import * as v from 'valibot'; import { accountSchema } from './account'; import { statusSchema } from './status'; +import { datetimeSchema } from './utils'; /** @see {@link https://docs.gotosocial.org/en/latest/api/swagger.yaml#/definitions/interactionRequest} */ const interactionRequestSchema = v.object({ - accepted_at: v.fallback(v.nullable(z.string().datetime()), null), + accepted_at: v.fallback(v.nullable(datetimeSchema), null), account: accountSchema, - created_at: z.string().datetime(), + created_at: datetimeSchema, id: v.string(), - rejected_at: v.fallback(v.nullable(z.string().datetime()), null), + rejected_at: v.fallback(v.nullable(datetimeSchema), null), reply: v.fallback(v.nullable(statusSchema), null), status: v.fallback(v.nullable(statusSchema), null), type: v.picklist(['favourite', 'reply', 'reblog']), diff --git a/packages/pl-api/lib/entities/marker.ts b/packages/pl-api/lib/entities/marker.ts index c8749a6dd..dd7a9483f 100644 --- a/packages/pl-api/lib/entities/marker.ts +++ b/packages/pl-api/lib/entities/marker.ts @@ -1,6 +1,6 @@ import * as v from 'valibot'; -import { dateSchema } from './utils'; +import { datetimeSchema } from './utils'; const markerSchema = v.pipe( v.any(), @@ -11,7 +11,7 @@ const markerSchema = v.pipe( v.object({ last_read_id: v.string(), version: v.pipe(v.number(), v.integer()), - updated_at: dateSchema, + updated_at: datetimeSchema, unread_count: v.fallback(v.optional(v.pipe(v.number(), v.integer())), undefined), }), ); diff --git a/packages/pl-api/lib/entities/notification-request.ts b/packages/pl-api/lib/entities/notification-request.ts index a03ace66e..d1d24a91e 100644 --- a/packages/pl-api/lib/entities/notification-request.ts +++ b/packages/pl-api/lib/entities/notification-request.ts @@ -1,14 +1,14 @@ import * as v from 'valibot'; -import { dateSchema } from './utils'; +import { datetimeSchema } from './utils'; import { accountSchema, statusSchema } from '.'; /** @see {@link https://docs.joinmastodon.org/entities/NotificationRequest} */ const notificationRequestSchema = v.object({ id: v.string(), - created_at: dateSchema, - updated_at: dateSchema, + created_at: datetimeSchema, + updated_at: datetimeSchema, account: accountSchema, notifications_count: v.pipe(v.unknown(), v.transform(String)), last_status: v.fallback(v.optional(statusSchema), undefined), diff --git a/packages/pl-api/lib/entities/notification.ts b/packages/pl-api/lib/entities/notification.ts index 03916463c..a22345711 100644 --- a/packages/pl-api/lib/entities/notification.ts +++ b/packages/pl-api/lib/entities/notification.ts @@ -7,11 +7,11 @@ import { chatMessageSchema } from './chat-message'; import { relationshipSeveranceEventSchema } from './relationship-severance-event'; import { reportSchema } from './report'; import { statusSchema } from './status'; -import { dateSchema } from './utils'; +import { datetimeSchema } from './utils'; const baseNotificationSchema = v.object({ account: accountSchema, - created_at: dateSchema, + created_at: v.fallback(datetimeSchema, new Date().toISOString()), id: v.string(), group_key: v.string(), type: v.string(), diff --git a/packages/pl-api/lib/entities/oauth-token.ts b/packages/pl-api/lib/entities/oauth-token.ts index 1370fea50..f041ade19 100644 --- a/packages/pl-api/lib/entities/oauth-token.ts +++ b/packages/pl-api/lib/entities/oauth-token.ts @@ -1,5 +1,7 @@ import * as v from 'valibot'; +import { datetimeSchema } from './utils'; + /** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#get-apioauth_tokens} */ const oauthTokenSchema = v.pipe( v.any(), @@ -10,7 +12,7 @@ const oauthTokenSchema = v.pipe( v.object({ app_name: v.string(), id: v.number(), - valid_until: z.string().datetime({ offset: true }), + valid_until: datetimeSchema, }), ); diff --git a/packages/pl-api/lib/entities/poll.ts b/packages/pl-api/lib/entities/poll.ts index 8ee66237f..24ebe6f9c 100644 --- a/packages/pl-api/lib/entities/poll.ts +++ b/packages/pl-api/lib/entities/poll.ts @@ -1,7 +1,7 @@ import * as v from 'valibot'; import { customEmojiSchema } from './custom-emoji'; -import { filteredArray } from './utils'; +import { datetimeSchema, filteredArray } from './utils'; const pollOptionSchema = v.object({ title: v.fallback(v.string(), ''), @@ -14,7 +14,7 @@ const pollOptionSchema = v.object({ const pollSchema = v.object({ emojis: filteredArray(customEmojiSchema), expired: v.fallback(v.boolean(), false), - expires_at: v.fallback(v.nullable(z.string().datetime()), null), + expires_at: v.fallback(v.nullable(datetimeSchema), null), id: v.string(), multiple: v.fallback(v.boolean(), false), options: v.pipe(v.array(pollOptionSchema), v.minLength(2)), diff --git a/packages/pl-api/lib/entities/relationship-severance-event.ts b/packages/pl-api/lib/entities/relationship-severance-event.ts index 6ac0430ef..df76cfd0d 100644 --- a/packages/pl-api/lib/entities/relationship-severance-event.ts +++ b/packages/pl-api/lib/entities/relationship-severance-event.ts @@ -1,6 +1,6 @@ import * as v from 'valibot'; -import { dateSchema } from './utils'; +import { datetimeSchema } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/RelationshipSeveranceEvent/} */ const relationshipSeveranceEventSchema = v.object({ @@ -8,7 +8,7 @@ const relationshipSeveranceEventSchema = v.object({ type: v.picklist(['domain_block', 'user_domain_block', 'account_suspension']), purged: v.string(), relationships_count: v.fallback(v.optional(v.number()), undefined), - created_at: dateSchema, + created_at: datetimeSchema, }); type RelationshipSeveranceEvent = v.InferOutput; diff --git a/packages/pl-api/lib/entities/report.ts b/packages/pl-api/lib/entities/report.ts index f9ab65d9f..83970f0dc 100644 --- a/packages/pl-api/lib/entities/report.ts +++ b/packages/pl-api/lib/entities/report.ts @@ -1,17 +1,17 @@ import * as v from 'valibot'; import { accountSchema } from './account'; -import { dateSchema } from './utils'; +import { datetimeSchema } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/Report/} */ const reportSchema = v.object({ id: v.string(), action_taken: v.fallback(v.optional(v.boolean()), undefined), - action_taken_at: v.fallback(v.nullable(dateSchema), null), + action_taken_at: v.fallback(v.nullable(datetimeSchema), null), category: v.fallback(v.optional(v.string()), undefined), comment: v.fallback(v.optional(v.string()), undefined), forwarded: v.fallback(v.optional(v.boolean()), undefined), - created_at: v.fallback(v.optional(dateSchema), undefined), + created_at: v.fallback(v.optional(datetimeSchema), undefined), status_ids: v.fallback(v.nullable(v.string()), null), rule_ids: v.fallback(v.nullable(v.string()), null), target_account: v.fallback(v.nullable(accountSchema), null), diff --git a/packages/pl-api/lib/entities/scheduled-status.ts b/packages/pl-api/lib/entities/scheduled-status.ts index c844c2fc4..347cc0946 100644 --- a/packages/pl-api/lib/entities/scheduled-status.ts +++ b/packages/pl-api/lib/entities/scheduled-status.ts @@ -1,12 +1,12 @@ import * as v from 'valibot'; import { mediaAttachmentSchema } from './media-attachment'; -import { filteredArray } from './utils'; +import { datetimeSchema, filteredArray } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/ScheduledStatus/} */ const scheduledStatusSchema = v.object({ id: v.string(), - scheduled_at: z.string().datetime({ offset: true }), + scheduled_at: datetimeSchema, params: v.object({ text: v.fallback(v.nullable(v.string()), null), poll: v.fallback(v.nullable(v.object({ @@ -22,7 +22,7 @@ const scheduledStatusSchema = v.object({ in_reply_to_id: v.fallback(v.nullable(v.string()), null), language: v.fallback(v.nullable(v.string()), null), application_id: v.fallback(v.nullable(v.pipe(v.number(), v.integer())), null), - scheduled_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), + scheduled_at: v.fallback(v.nullable(datetimeSchema), null), idempotency: v.fallback(v.nullable(v.string()), null), with_rate_limit: v.fallback(v.boolean(), false), diff --git a/packages/pl-api/lib/entities/scrobble.ts b/packages/pl-api/lib/entities/scrobble.ts index fb4d6c9ea..e27d2d607 100644 --- a/packages/pl-api/lib/entities/scrobble.ts +++ b/packages/pl-api/lib/entities/scrobble.ts @@ -1,6 +1,7 @@ import * as v from 'valibot'; import { accountSchema } from './account'; +import { datetimeSchema } from './utils'; const scrobbleSchema = v.pipe( v.any(), @@ -11,7 +12,7 @@ const scrobbleSchema = v.pipe( v.object({ id: v.pipe(v.unknown(), v.transform(String)), account: accountSchema, - created_at: z.string().datetime({ offset: true }), + created_at: datetimeSchema, title: v.string(), artist: v.fallback(v.string(), ''), album: v.fallback(v.string(), ''), diff --git a/packages/pl-api/lib/entities/status-edit.ts b/packages/pl-api/lib/entities/status-edit.ts index aacd65c41..9aaa1a74c 100644 --- a/packages/pl-api/lib/entities/status-edit.ts +++ b/packages/pl-api/lib/entities/status-edit.ts @@ -3,14 +3,14 @@ import * as v from 'valibot'; import { accountSchema } from './account'; import { customEmojiSchema } from './custom-emoji'; import { mediaAttachmentSchema } from './media-attachment'; -import { dateSchema, filteredArray } from './utils'; +import { datetimeSchema, filteredArray } from './utils'; /** @see {@link https://docs.joinmastodon.org/entities/StatusEdit/} */ const statusEditSchema = v.object({ content: v.fallback(v.string(), ''), spoiler_text: v.fallback(v.string(), ''), sensitive: v.pipe(v.unknown(), v.transform(Boolean)), - created_at: dateSchema, + created_at: v.fallback(datetimeSchema, new Date().toISOString()), account: accountSchema, poll: v.fallback(v.nullable(v.object({ options: v.array(v.object({ diff --git a/packages/pl-api/lib/entities/status.ts b/packages/pl-api/lib/entities/status.ts index acd6d3140..b89c78578 100644 --- a/packages/pl-api/lib/entities/status.ts +++ b/packages/pl-api/lib/entities/status.ts @@ -13,12 +13,12 @@ import { pollSchema } from './poll'; import { previewCardSchema } from './preview-card'; import { tagSchema } from './tag'; import { translationSchema } from './translation'; -import { dateSchema, filteredArray } from './utils'; +import { datetimeSchema, filteredArray } from './utils'; const statusEventSchema = v.object({ name: v.fallback(v.string(), ''), - start_time: v.fallback(v.nullable(z.string().datetime()), null), - end_time: v.fallback(v.nullable(z.string().datetime()), null), + start_time: v.fallback(v.nullable(datetimeSchema), null), + end_time: v.fallback(v.nullable(datetimeSchema), null), join_mode: v.fallback(v.nullable(v.picklist(['free', 'restricted', 'invite'])), null), participants_count: v.fallback(v.number(), 0), location: v.fallback(v.nullable(v.object({ @@ -39,7 +39,7 @@ const statusEventSchema = v.object({ const baseStatusSchema = v.object({ id: v.string(), uri: v.fallback(v.pipe(v.string(), v.url()), ''), - created_at: dateSchema, + created_at: v.fallback(datetimeSchema, new Date().toISOString()), account: accountSchema, content: v.fallback(v.string(), ''), visibility: v.fallback(v.string(), 'public'), @@ -63,7 +63,7 @@ const baseStatusSchema = v.object({ card: v.fallback(v.nullable(previewCardSchema), null), language: v.fallback(v.nullable(v.string()), null), text: v.fallback(v.nullable(v.string()), null), - edited_at: v.fallback(v.nullable(z.string().datetime()), null), + edited_at: v.fallback(v.nullable(datetimeSchema), null), favourited: v.pipe(v.unknown(), v.transform(Boolean)), reblogged: v.pipe(v.unknown(), v.transform(Boolean)), muted: v.pipe(v.unknown(), v.transform(Boolean)), @@ -79,11 +79,11 @@ const baseStatusSchema = v.object({ conversation_id: v.fallback(v.optional(v.string()), undefined), direct_conversation_id: v.fallback(v.optional(v.string()), undefined), in_reply_to_account_acct: v.fallback(v.optional(v.string()), undefined), - expires_at: v.fallback(v.optional(z.string().datetime({ offset: true })), undefined), + expires_at: v.fallback(v.optional(datetimeSchema), undefined), thread_muted: v.fallback(v.optional(v.boolean()), undefined), emoji_reactions: filteredArray(emojiReactionSchema), parent_visible: v.fallback(v.optional(v.boolean()), undefined), - pinned_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), + pinned_at: v.fallback(v.nullable(datetimeSchema), null), quote_visible: v.fallback(v.optional(v.boolean()), undefined), quote_url: v.fallback(v.optional(v.string()), undefined), quotes_count: v.fallback(v.number(), 0), diff --git a/packages/pl-api/lib/entities/utils.ts b/packages/pl-api/lib/entities/utils.ts index eaee8fe94..78fb1b1aa 100644 --- a/packages/pl-api/lib/entities/utils.ts +++ b/packages/pl-api/lib/entities/utils.ts @@ -1,7 +1,13 @@ import * as v from 'valibot'; /** Validate to Mastodon's date format, or use the current date. */ -const dateSchema = z.string().datetime({ offset: true }).catch(new Date().toUTCString()); +const datetimeSchema = v.pipe( + v.string(), + // Adapted from Zod + // https://github.com/colinhacks/zod/blob/main/src/types.ts#L619 + // at least it's not chatgpt + v.regex(/^\d{4}-\d{2}-\d{2}T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.\d+)?(([+-]\d{2}:?\d{2})|(Z)?)$/), +); /** Validates individual items in an array, dropping any that aren't valid. */ const filteredArray = (schema: v.BaseSchema>) => @@ -29,4 +35,4 @@ const coerceObject = (shape: T) => v.object(shape), ); -export { filteredArray, emojiSchema, dateSchema, mimeSchema, coerceObject }; +export { filteredArray, emojiSchema, datetimeSchema, mimeSchema, coerceObject }; From 905e1626a4154bcd1e61b8cd4e17500accd54c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 17:30:05 +0200 Subject: [PATCH 18/28] pl-fe: wip valibot migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/lib/entities/tag.ts | 4 ++-- packages/pl-fe/src/actions/auth.ts | 3 ++- packages/pl-fe/src/actions/external-auth.ts | 3 ++- .../src/api/hooks/admin/useAnnouncements.ts | 5 +++-- .../api/hooks/announcements/useAnnouncements.ts | 5 +++-- .../src/api/hooks/groups/useGroups.test.ts | 3 ++- .../hooks/settings/useInteractionPolicies.ts | 3 ++- packages/pl-fe/src/components/preview-card.tsx | 3 ++- .../src/entity-store/hooks/useEntityLookup.ts | 1 + .../src/features/account/components/header.tsx | 5 +++-- .../auth-login/components/login-form.test.tsx | 5 +++-- .../auth-login/components/login-page.test.tsx | 3 ++- .../compose/editor/nodes/image-component.tsx | 3 ++- .../src/features/draft-statuses/builder.tsx | 3 ++- .../features/group/components/group-header.tsx | 5 +++-- .../src/features/scheduled-statuses/builder.tsx | 3 ++- .../compose-event-modal/upload-button.tsx | 1 - .../src/features/ui/util/global-hotkeys.tsx | 1 - .../features/ui/util/pending-status-builder.ts | 3 ++- packages/pl-fe/src/jest/factory.ts | 17 +++++++++-------- packages/pl-fe/src/jest/mock-stores.tsx | 5 +++-- packages/pl-fe/src/normalizers/poll.ts | 1 - packages/pl-fe/src/normalizers/status.ts | 3 ++- packages/pl-fe/src/queries/chats.ts | 3 ++- packages/pl-fe/src/reducers/accounts-meta.ts | 15 +++------------ packages/pl-fe/src/reducers/auth.ts | 7 ++++--- packages/pl-fe/src/reducers/instance.ts | 5 +++-- packages/pl-fe/src/utils/emoji-reacts.test.ts | 13 +++++++------ packages/pl-fe/src/utils/emoji-reacts.ts | 7 ++++--- 29 files changed, 75 insertions(+), 63 deletions(-) diff --git a/packages/pl-api/lib/entities/tag.ts b/packages/pl-api/lib/entities/tag.ts index c1518bbde..f9010d436 100644 --- a/packages/pl-api/lib/entities/tag.ts +++ b/packages/pl-api/lib/entities/tag.ts @@ -1,10 +1,10 @@ import * as v from 'valibot'; -const historySchema = v.object({ +const historySchema = v.array(v.object({ day: v.pipe(v.unknown(), v.transform(Number)), accounts: v.pipe(v.unknown(), v.transform(Number)), uses: v.pipe(v.unknown(), v.transform(Number)), -}); +})); /** @see {@link https://docs.joinmastodon.org/entities/tag} */ const tagSchema = v.object({ diff --git a/packages/pl-fe/src/actions/auth.ts b/packages/pl-fe/src/actions/auth.ts index a438cef01..7b1d8c986 100644 --- a/packages/pl-fe/src/actions/auth.ts +++ b/packages/pl-fe/src/actions/auth.ts @@ -8,6 +8,7 @@ */ import { credentialAccountSchema, PlApiClient, type CreateAccountParams, type Token } from 'pl-api'; import { defineMessages } from 'react-intl'; +import * as v from 'valibot'; import { createAccount } from 'pl-fe/actions/accounts'; import { createApp } from 'pl-fe/actions/apps'; @@ -157,7 +158,7 @@ const verifyCredentials = (token: string, accountUrl?: string) => if (error?.response?.status === 403 && error?.response?.json?.id) { // The user is waitlisted const account = error.response.json; - const parsedAccount = credentialAccountSchema.parse(error.response.json); + const parsedAccount = v.parse(credentialAccountSchema, error.response.json); dispatch(importFetchedAccount(parsedAccount)); dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account: parsedAccount }); if (account.id === getState().me) dispatch(fetchMeSuccess(parsedAccount)); diff --git a/packages/pl-fe/src/actions/external-auth.ts b/packages/pl-fe/src/actions/external-auth.ts index db8626377..da6f01687 100644 --- a/packages/pl-fe/src/actions/external-auth.ts +++ b/packages/pl-fe/src/actions/external-auth.ts @@ -7,6 +7,7 @@ */ import { instanceSchema, PlApiClient, type Instance } from 'pl-api'; +import * as v from 'valibot'; import { createApp } from 'pl-fe/actions/apps'; import { authLoggedIn, verifyCredentials, switchAccount } from 'pl-fe/actions/auth'; @@ -24,7 +25,7 @@ const fetchExternalInstance = (baseURL: string) => if (error.response?.status === 401) { // Authenticated fetch is enabled. // Continue with a limited featureset. - return instanceSchema.parse({}); + return v.parse(instanceSchema, {}); } else { throw error; } diff --git a/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts b/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts index a17eebc1c..38da607ab 100644 --- a/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts +++ b/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts @@ -5,6 +5,7 @@ import { type AdminCreateAnnouncementParams, type AdminUpdateAnnouncementParams, } from 'pl-api'; +import * as v from 'valibot'; import { useClient } from 'pl-fe/hooks'; import { normalizeAnnouncement, AdminAnnouncement } from 'pl-fe/normalizers'; @@ -36,7 +37,7 @@ const useAnnouncements = () => { retry: false, onSuccess: (data) => queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray) => - [...prevResult, adminAnnouncementSchema.parse(data)], + [...prevResult, v.parse(adminAnnouncementSchema, data)], ), onSettled: () => userAnnouncements.refetch(), }); @@ -50,7 +51,7 @@ const useAnnouncements = () => { retry: false, onSuccess: (data) => queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray) => - prevResult.map((announcement) => announcement.id === data.id ? adminAnnouncementSchema.parse(data) : announcement), + prevResult.map((announcement) => announcement.id === data.id ? v.parse(adminAnnouncementSchema, data) : announcement), ), onSettled: () => userAnnouncements.refetch(), }); diff --git a/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts b/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts index 44fda104b..3c30f743b 100644 --- a/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts +++ b/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts @@ -1,11 +1,12 @@ import { useMutation, useQuery } from '@tanstack/react-query'; import { announcementReactionSchema, type AnnouncementReaction } from 'pl-api'; +import * as v from 'valibot'; import { useClient } from 'pl-fe/hooks'; import { type Announcement, normalizeAnnouncement } from 'pl-fe/normalizers'; import { queryClient } from 'pl-fe/queries/client'; -const updateReaction = (reaction: AnnouncementReaction, count: number, me?: boolean, overwrite?: boolean) => announcementReactionSchema.parse({ +const updateReaction = (reaction: AnnouncementReaction, count: number, me?: boolean, overwrite?: boolean) => v.parse(announcementReactionSchema, { ...reaction, me: typeof me === 'boolean' ? me : reaction.me, count: overwrite ? count : (reaction.count + count), @@ -18,7 +19,7 @@ const updateReactions = (reactions: AnnouncementReaction[], name: string, count: reactions = reactions.map(reaction => reaction.name === name ? updateReaction(reaction, count, me, overwrite) : reaction); } - return [...reactions, updateReaction(announcementReactionSchema.parse({ name }), count, me, overwrite)]; + return [...reactions, updateReaction(v.parse(announcementReactionSchema, { name }), count, me, overwrite)]; }; const useAnnouncements = () => { diff --git a/packages/pl-fe/src/api/hooks/groups/useGroups.test.ts b/packages/pl-fe/src/api/hooks/groups/useGroups.test.ts index e0200b92d..856a1289b 100644 --- a/packages/pl-fe/src/api/hooks/groups/useGroups.test.ts +++ b/packages/pl-fe/src/api/hooks/groups/useGroups.test.ts @@ -1,4 +1,5 @@ import { instanceSchema } from 'pl-api'; +import * as v from 'valibot'; import { __stub } from 'pl-fe/api'; import { buildGroup } from 'pl-fe/jest/factory'; @@ -8,7 +9,7 @@ import { useGroups } from './useGroups'; const group = buildGroup({ id: '1', display_name: 'soapbox' }); const store = { - instance: instanceSchema.parse({ + instance: v.parse(instanceSchema, { version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)', }), }; diff --git a/packages/pl-fe/src/api/hooks/settings/useInteractionPolicies.ts b/packages/pl-fe/src/api/hooks/settings/useInteractionPolicies.ts index d85b19c25..aa09e5f5d 100644 --- a/packages/pl-fe/src/api/hooks/settings/useInteractionPolicies.ts +++ b/packages/pl-fe/src/api/hooks/settings/useInteractionPolicies.ts @@ -1,10 +1,11 @@ import { useMutation, useQuery } from '@tanstack/react-query'; import { type InteractionPolicies, interactionPoliciesSchema } from 'pl-api'; +import * as v from 'valibot'; import { useClient, useFeatures, useLoggedIn } from 'pl-fe/hooks'; import { queryClient } from 'pl-fe/queries/client'; -const emptySchema = interactionPoliciesSchema.parse({}); +const emptySchema = v.parse(interactionPoliciesSchema, {}); const useInteractionPolicies = () => { const client = useClient(); diff --git a/packages/pl-fe/src/components/preview-card.tsx b/packages/pl-fe/src/components/preview-card.tsx index dc275ff50..9564fbe1e 100644 --- a/packages/pl-fe/src/components/preview-card.tsx +++ b/packages/pl-fe/src/components/preview-card.tsx @@ -1,6 +1,7 @@ import clsx from 'clsx'; import { type MediaAttachment, type PreviewCard as CardEntity, mediaAttachmentSchema } from 'pl-api'; import React, { useState, useEffect } from 'react'; +import * as v from 'valibot'; import Blurhash from 'pl-fe/components/blurhash'; import { HStack, Stack, Text, Icon } from 'pl-fe/components/ui'; @@ -43,7 +44,7 @@ const PreviewCard: React.FC = ({ const trimmedDescription = trim(card.description, maxDescription); const handlePhotoClick = () => { - const attachment = mediaAttachmentSchema.parse({ + const attachment = v.parse(mediaAttachmentSchema, { id: '', type: 'image', url: card.embed_url, diff --git a/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts b/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts index 683077c97..a8dd9d6c8 100644 --- a/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts +++ b/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import * as v from 'valibot'; import { z } from 'zod'; import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch'; diff --git a/packages/pl-fe/src/features/account/components/header.tsx b/packages/pl-fe/src/features/account/components/header.tsx index 6fdcec148..5fd42e744 100644 --- a/packages/pl-fe/src/features/account/components/header.tsx +++ b/packages/pl-fe/src/features/account/components/header.tsx @@ -3,6 +3,7 @@ import { GOTOSOCIAL, MASTODON, mediaAttachmentSchema } from 'pl-api'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; +import * as v from 'valibot'; import { biteAccount, blockAccount, pinAccount, removeFromFollowers, unblockAccount, unmuteAccount, unpinAccount } from 'pl-fe/actions/accounts'; import { mentionCompose, directCompose } from 'pl-fe/actions/compose'; @@ -240,7 +241,7 @@ const Header: React.FC = ({ account }) => { }; const onAvatarClick = () => { - const avatar = mediaAttachmentSchema.parse({ + const avatar = v.parse(mediaAttachmentSchema, { id: '', type: 'image', url: account.avatar, @@ -256,7 +257,7 @@ const Header: React.FC = ({ account }) => { }; const onHeaderClick = () => { - const header = mediaAttachmentSchema.parse({ + const header = v.parse(mediaAttachmentSchema, { type: 'image', url: account.header, }); diff --git a/packages/pl-fe/src/features/auth-login/components/login-form.test.tsx b/packages/pl-fe/src/features/auth-login/components/login-form.test.tsx index bc985d929..6ed9a7f23 100644 --- a/packages/pl-fe/src/features/auth-login/components/login-form.test.tsx +++ b/packages/pl-fe/src/features/auth-login/components/login-form.test.tsx @@ -1,5 +1,6 @@ import { instanceSchema } from 'pl-api'; import React from 'react'; +import * as v from 'valibot'; import { fireEvent, render, screen } from 'pl-fe/jest/test-helpers'; @@ -9,7 +10,7 @@ describe('', () => { it('renders for Pleroma', () => { const mockFn = vi.fn(); const store = { - instance: instanceSchema.parse({ + instance: v.parse(instanceSchema, { version: '2.7.2 (compatible; Pleroma 2.3.0)', }), }; @@ -22,7 +23,7 @@ describe('', () => { it('renders for Mastodon', () => { const mockFn = vi.fn(); const store = { - instance: instanceSchema.parse({ + instance: v.parse(instanceSchema, { version: '3.0.0', }), }; diff --git a/packages/pl-fe/src/features/auth-login/components/login-page.test.tsx b/packages/pl-fe/src/features/auth-login/components/login-page.test.tsx index 67762b8e7..103172437 100644 --- a/packages/pl-fe/src/features/auth-login/components/login-page.test.tsx +++ b/packages/pl-fe/src/features/auth-login/components/login-page.test.tsx @@ -1,5 +1,6 @@ import { instanceSchema } from 'pl-api'; import React from 'react'; +import * as v from 'valibot'; import { render, screen } from 'pl-fe/jest/test-helpers'; @@ -8,7 +9,7 @@ import LoginPage from './login-page'; describe('', () => { it('renders correctly on load', () => { const store = { - instance: instanceSchema.parse({ + instance: v.parse(instanceSchema, { version: '2.7.2 (compatible; Pleroma 2.3.0)', }), }; diff --git a/packages/pl-fe/src/features/compose/editor/nodes/image-component.tsx b/packages/pl-fe/src/features/compose/editor/nodes/image-component.tsx index c6b79615a..5289ff65a 100644 --- a/packages/pl-fe/src/features/compose/editor/nodes/image-component.tsx +++ b/packages/pl-fe/src/features/compose/editor/nodes/image-component.tsx @@ -26,6 +26,7 @@ import { mediaAttachmentSchema } from 'pl-api'; import * as React from 'react'; import { Suspense, useCallback, useEffect, useRef, useState } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; +import * as v from 'valibot'; import { HStack, Icon, IconButton } from 'pl-fe/components/ui'; import { useSettings } from 'pl-fe/hooks'; @@ -122,7 +123,7 @@ const ImageComponent = ({ ); const previewImage = () => { - const image = mediaAttachmentSchema.parse({ + const image = v.parse(mediaAttachmentSchema, { id: '', type: 'image', url: src, diff --git a/packages/pl-fe/src/features/draft-statuses/builder.tsx b/packages/pl-fe/src/features/draft-statuses/builder.tsx index 4272897d8..c93f34c3a 100644 --- a/packages/pl-fe/src/features/draft-statuses/builder.tsx +++ b/packages/pl-fe/src/features/draft-statuses/builder.tsx @@ -1,4 +1,5 @@ import { statusSchema } from 'pl-api'; +import * as v from 'valibot'; import { Entities } from 'pl-fe/entity-store/entities'; import { normalizeStatus } from 'pl-fe/normalizers'; @@ -22,7 +23,7 @@ const buildStatus = (state: RootState, draftStatus: DraftStatus) => { const me = state.me as string; const account = state.entities[Entities.ACCOUNTS]?.store[me]; - const status = statusSchema.parse({ + const status = v.parse(statusSchema, { id: 'draft', account, content: draftStatus.text.replace(new RegExp('\n', 'g'), '
'), /* eslint-disable-line no-control-regex */ diff --git a/packages/pl-fe/src/features/group/components/group-header.tsx b/packages/pl-fe/src/features/group/components/group-header.tsx index e1c1f61ea..c0c229db2 100644 --- a/packages/pl-fe/src/features/group/components/group-header.tsx +++ b/packages/pl-fe/src/features/group/components/group-header.tsx @@ -1,6 +1,7 @@ import { mediaAttachmentSchema } from 'pl-api'; import React, { useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; +import * as v from 'valibot'; import GroupAvatar from 'pl-fe/components/groups/group-avatar'; import { ParsedContent } from 'pl-fe/components/parsed-content'; @@ -52,7 +53,7 @@ const GroupHeader: React.FC = ({ group }) => { } const onAvatarClick = () => { - const avatar = mediaAttachmentSchema.parse({ + const avatar = v.parse(mediaAttachmentSchema, { id: '', type: 'image', url: group.avatar, @@ -68,7 +69,7 @@ const GroupHeader: React.FC = ({ group }) => { }; const onHeaderClick = () => { - const header = mediaAttachmentSchema.parse({ + const header = v.parse(mediaAttachmentSchema, { id: '', type: 'image', url: group.header, diff --git a/packages/pl-fe/src/features/scheduled-statuses/builder.tsx b/packages/pl-fe/src/features/scheduled-statuses/builder.tsx index ed9d7d7df..9d02b7d4b 100644 --- a/packages/pl-fe/src/features/scheduled-statuses/builder.tsx +++ b/packages/pl-fe/src/features/scheduled-statuses/builder.tsx @@ -1,4 +1,5 @@ import { statusSchema, type ScheduledStatus } from 'pl-api'; +import * as v from 'valibot'; import { Entities } from 'pl-fe/entity-store/entities'; import { normalizeStatus } from 'pl-fe/normalizers/status'; @@ -9,7 +10,7 @@ const buildStatus = (state: RootState, scheduledStatus: ScheduledStatus) => { const me = state.me as string; const account = state.entities[Entities.ACCOUNTS]?.store[me]; - const status = statusSchema.parse({ + const status = v.parse(statusSchema, { account, content: scheduledStatus.params.text?.replace(new RegExp('\n', 'g'), '
'), /* eslint-disable-line no-control-regex */ created_at: scheduledStatus.params.scheduled_at, diff --git a/packages/pl-fe/src/features/ui/components/modals/compose-event-modal/upload-button.tsx b/packages/pl-fe/src/features/ui/components/modals/compose-event-modal/upload-button.tsx index 7931b8d32..842ec67a5 100644 --- a/packages/pl-fe/src/features/ui/components/modals/compose-event-modal/upload-button.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/compose-event-modal/upload-button.tsx @@ -29,7 +29,6 @@ const UploadButton: React.FC = ({ disabled, onSelectFile }) => { fileElement.current?.click(); }; - return ( = ({ children, node }) => { return handlers; }, [account?.id]); - return ( {children} diff --git a/packages/pl-fe/src/features/ui/util/pending-status-builder.ts b/packages/pl-fe/src/features/ui/util/pending-status-builder.ts index 4ef68ea39..9143e67a5 100644 --- a/packages/pl-fe/src/features/ui/util/pending-status-builder.ts +++ b/packages/pl-fe/src/features/ui/util/pending-status-builder.ts @@ -1,5 +1,6 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { statusSchema } from 'pl-api'; +import * as v from 'valibot'; import { normalizeStatus } from 'pl-fe/normalizers/status'; import { makeGetAccount } from 'pl-fe/selectors'; @@ -46,7 +47,7 @@ const buildStatus = (state: RootState, pendingStatus: PendingStatus, idempotency visibility: pendingStatus.visibility, }; - return normalizeStatus(statusSchema.parse(status)); + return normalizeStatus(v.parse(statusSchema, status)); }; export { buildStatus }; diff --git a/packages/pl-fe/src/jest/factory.ts b/packages/pl-fe/src/jest/factory.ts index b4000333a..47fcfa3b3 100644 --- a/packages/pl-fe/src/jest/factory.ts +++ b/packages/pl-fe/src/jest/factory.ts @@ -17,6 +17,7 @@ import { type Relationship, type Status, } from 'pl-api'; +import * as v from 'valibot'; import type { PartialDeep } from 'type-fest'; @@ -24,18 +25,18 @@ import type { PartialDeep } from 'type-fest'; // This looks promising but didn't work on my first attempt: https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock const buildAccount = (props: PartialDeep = {}): Account => - accountSchema.parse(Object.assign({ + v.parse(accountSchema, Object.assign({ id: crypto.randomUUID(), url: `https://soapbox.test/users/${crypto.randomUUID()}`, }, props)); const buildCard = (props: PartialDeep = {}): PreviewCard => - previewCardSchema.parse(Object.assign({ + v.parse(previewCardSchema, Object.assign({ url: 'https://soapbox.test', }, props)); const buildGroup = (props: PartialDeep = {}): Group => - groupSchema.parse(Object.assign({ + v.parse(groupSchema, Object.assign({ id: crypto.randomUUID(), owner: { id: crypto.randomUUID(), @@ -43,28 +44,28 @@ const buildGroup = (props: PartialDeep = {}): Group => }, props)); const buildGroupRelationship = (props: PartialDeep = {}): GroupRelationship => - groupRelationshipSchema.parse(Object.assign({ + v.parse(groupRelationshipSchema, Object.assign({ id: crypto.randomUUID(), }, props)); const buildGroupMember = ( props: PartialDeep = {}, accountProps: PartialDeep = {}, -): GroupMember => groupMemberSchema.parse(Object.assign({ +): GroupMember => v.parse(groupMemberSchema, Object.assign({ id: crypto.randomUUID(), account: buildAccount(accountProps), role: GroupRoles.USER, }, props)); -const buildInstance = (props: PartialDeep = {}) => instanceSchema.parse(props); +const buildInstance = (props: PartialDeep = {}) => v.parse(instanceSchema, props); const buildRelationship = (props: PartialDeep = {}): Relationship => - relationshipSchema.parse(Object.assign({ + v.parse(relationshipSchema, Object.assign({ id: crypto.randomUUID(), }, props)); const buildStatus = (props: PartialDeep = {}) => - statusSchema.parse(Object.assign({ + v.parse(statusSchema, Object.assign({ id: crypto.randomUUID(), account: buildAccount(), }, props)); diff --git a/packages/pl-fe/src/jest/mock-stores.tsx b/packages/pl-fe/src/jest/mock-stores.tsx index 01e0c3cf3..7d2b4db4e 100644 --- a/packages/pl-fe/src/jest/mock-stores.tsx +++ b/packages/pl-fe/src/jest/mock-stores.tsx @@ -1,14 +1,15 @@ import { instanceSchema } from 'pl-api'; +import * as v from 'valibot'; import alexJson from 'pl-fe/__fixtures__/pleroma-account.json'; import { buildAccount } from './factory'; /** Store with registrations open. */ -const storeOpen = { instance: instanceSchema.parse({ registrations: true }) }; +const storeOpen = { instance: v.parse(instanceSchema, { registrations: true }) }; /** Store with registrations closed. */ -const storeClosed = { instance: instanceSchema.parse({ registrations: false }) }; +const storeClosed = { instance: v.parse(instanceSchema, { registrations: false }) }; /** Store with a logged-in user. */ const storeLoggedIn = { diff --git a/packages/pl-fe/src/normalizers/poll.ts b/packages/pl-fe/src/normalizers/poll.ts index e7f10488f..ee86a20c6 100644 --- a/packages/pl-fe/src/normalizers/poll.ts +++ b/packages/pl-fe/src/normalizers/poll.ts @@ -1,7 +1,6 @@ import escapeTextContentForBrowser from 'escape-html'; import DOMPurify from 'isomorphic-dompurify'; - import emojify from 'pl-fe/features/emoji'; import { makeEmojiMap } from 'pl-fe/utils/normalizers'; diff --git a/packages/pl-fe/src/normalizers/status.ts b/packages/pl-fe/src/normalizers/status.ts index 18cc8359a..6b24a7c06 100644 --- a/packages/pl-fe/src/normalizers/status.ts +++ b/packages/pl-fe/src/normalizers/status.ts @@ -6,6 +6,7 @@ import escapeTextContentForBrowser from 'escape-html'; import DOMPurify from 'isomorphic-dompurify'; import { type Account as BaseAccount, type Status as BaseStatus, type CustomEmoji, type MediaAttachment, mentionSchema, type Translation } from 'pl-api'; +import * as v from 'valibot'; import emojify from 'pl-fe/features/emoji'; import { unescapeHTML } from 'pl-fe/utils/html'; @@ -111,7 +112,7 @@ const normalizeStatus = (status: BaseStatus & { const hasSelfMention = status.mentions.some(mention => status.account.id === mention.id); if (isSelfReply && !hasSelfMention) { - const selfMention = mentionSchema.parse(status.account); + const selfMention = v.parse(mentionSchema, status.account); mentions = [selfMention, ...mentions]; } diff --git a/packages/pl-fe/src/queries/chats.ts b/packages/pl-fe/src/queries/chats.ts index 4b2d3ec37..e5076b6f0 100644 --- a/packages/pl-fe/src/queries/chats.ts +++ b/packages/pl-fe/src/queries/chats.ts @@ -1,6 +1,7 @@ import { InfiniteData, keepPreviousData, useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'; import sumBy from 'lodash/sumBy'; import { type Chat, type ChatMessage as BaseChatMessage, type PaginatedResponse, chatMessageSchema, type Relationship } from 'pl-api'; +import * as v from 'valibot'; import { importFetchedAccount, importFetchedAccounts } from 'pl-fe/actions/importer'; import { ChatWidgetScreens, useChatContext } from 'pl-fe/contexts/chat-context'; @@ -171,7 +172,7 @@ const useChatActions = (chatId: string) => { ...page, items: [ normalizeChatMessage({ - ...chatMessageSchema.parse({ + ...v.parse(chatMessageSchema, { chat_id: variables.chatId, content: variables.content, id: pendingId, diff --git a/packages/pl-fe/src/reducers/accounts-meta.ts b/packages/pl-fe/src/reducers/accounts-meta.ts index 7a0b9f3b9..1349f69ac 100644 --- a/packages/pl-fe/src/reducers/accounts-meta.ts +++ b/packages/pl-fe/src/reducers/accounts-meta.ts @@ -4,11 +4,11 @@ */ import { produce } from 'immer'; -import { Account, accountSchema } from 'pl-api'; import { VERIFY_CREDENTIALS_SUCCESS, AUTH_ACCOUNT_REMEMBER_SUCCESS } from 'pl-fe/actions/auth'; import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from 'pl-fe/actions/me'; +import type { Account, CredentialAccount } from 'pl-api'; import type { AnyAction } from 'redux'; interface AccountMeta { @@ -18,16 +18,8 @@ interface AccountMeta { type State = Record; -const importAccount = (state: State, data: unknown): State => { - const result = accountSchema.safeParse(data); - - if (!result.success) { - return state; - } - - const account = result.data; - - return produce(state, draft => { +const importAccount = (state: State, account: CredentialAccount): State => + produce(state, draft => { const existing = draft[account.id]; draft[account.id] = { @@ -35,7 +27,6 @@ const importAccount = (state: State, data: unknown): State => { source: account.__meta.source ?? existing?.source, }; }); -}; const accounts_meta = (state: Readonly = {}, action: AnyAction): State => { switch (action.type) { diff --git a/packages/pl-fe/src/reducers/auth.ts b/packages/pl-fe/src/reducers/auth.ts index 067909563..48559b8c5 100644 --- a/packages/pl-fe/src/reducers/auth.ts +++ b/packages/pl-fe/src/reducers/auth.ts @@ -1,6 +1,7 @@ import { List as ImmutableList, Map as ImmutableMap, Record as ImmutableRecord, fromJS } from 'immutable'; import trim from 'lodash/trim'; import { applicationSchema, PlApiClient, tokenSchema, type Application, type CredentialAccount, type Token } from 'pl-api'; +import * as v from 'valibot'; import { MASTODON_PRELOAD_IMPORT } from 'pl-fe/actions/preload'; import * as BuildConfig from 'pl-fe/build-config'; @@ -60,8 +61,8 @@ const getLocalState = () => { if (!state) return undefined; return ReducerRecord({ - app: state.app && applicationSchema.parse(state.app), - tokens: ImmutableMap(Object.entries(state.tokens).map(([key, value]) => [key, tokenSchema.parse(value)])), + app: state.app && v.parse(applicationSchema, state.app), + tokens: ImmutableMap(Object.entries(state.tokens).map(([key, value]) => [key, v.parse(tokenSchema, value)])), users: ImmutableMap(Object.entries(state.users).map(([key, value]) => [key, AuthUserRecord(value as any)])), me: state.me, client: new PlApiClient(parseBaseURL(state.me) || backendUrl, state.users[state.me]?.access_token), @@ -237,7 +238,7 @@ const importMastodonPreload = (state: State, data: ImmutableMap) => const accessToken = data.getIn(['meta', 'access_token']) as string; if (validId(accessToken) && validId(accountId) && isURL(accountUrl)) { - state.setIn(['tokens', accessToken], tokenSchema.parse({ + state.setIn(['tokens', accessToken], v.parse(tokenSchema, { access_token: accessToken, account: accountId, me: accountUrl, diff --git a/packages/pl-fe/src/reducers/instance.ts b/packages/pl-fe/src/reducers/instance.ts index 8333b5e1e..f5acfcf33 100644 --- a/packages/pl-fe/src/reducers/instance.ts +++ b/packages/pl-fe/src/reducers/instance.ts @@ -1,6 +1,7 @@ import { produce } from 'immer'; import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; import { type Instance, instanceSchema } from 'pl-api'; +import * as v from 'valibot'; import { ADMIN_CONFIG_UPDATE_REQUEST, ADMIN_CONFIG_UPDATE_SUCCESS } from 'pl-fe/actions/admin'; import { INSTANCE_FETCH_FAIL, INSTANCE_FETCH_SUCCESS, InstanceAction } from 'pl-fe/actions/instance'; @@ -10,11 +11,11 @@ import ConfigDB from 'pl-fe/utils/config-db'; import type { AnyAction } from 'redux'; -const initialState: Instance = instanceSchema.parse({}); +const initialState: Instance = v.parse(instanceSchema, {}); const preloadImport = (state: Instance, action: Record, path: string) => { const instance = action.data[path]; - return instance ? instanceSchema.parse(instance) : state; + return instance ? v.parse(instanceSchema, instance) : state; }; const getConfigValue = (instanceConfig: ImmutableMap, key: string) => { diff --git a/packages/pl-fe/src/utils/emoji-reacts.test.ts b/packages/pl-fe/src/utils/emoji-reacts.test.ts index 1eff793b0..3e12a9dc9 100644 --- a/packages/pl-fe/src/utils/emoji-reacts.test.ts +++ b/packages/pl-fe/src/utils/emoji-reacts.test.ts @@ -1,5 +1,6 @@ import { List as ImmutableList, fromJS } from 'immutable'; import { emojiReactionSchema } from 'pl-api'; +import * as v from 'valibot'; import { simulateEmojiReact, @@ -11,7 +12,7 @@ describe('simulateEmojiReact', () => { const emojiReacts = ImmutableList([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 2, 'me': false, 'name': '❤', 'url': undefined }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateEmojiReact(emojiReacts, '❤')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 3, 'me': true, 'name': '❤', 'url': undefined }, @@ -22,7 +23,7 @@ describe('simulateEmojiReact', () => { const emojiReacts = ImmutableList([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 2, 'me': false, 'name': '❤', 'url': undefined }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateEmojiReact(emojiReacts, '😯')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 2, 'me': false, 'name': '❤', 'url': undefined }, @@ -34,7 +35,7 @@ describe('simulateEmojiReact', () => { const emojiReacts = ImmutableList([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 2, 'me': false, 'name': '❤', 'url': undefined }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateEmojiReact(emojiReacts, 'soapbox', 'https://gleasonator.com/emoji/Gleasonator/soapbox.png')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 2, 'me': false, 'name': '❤', 'url': undefined }, @@ -48,7 +49,7 @@ describe('simulateUnEmojiReact', () => { const emojiReacts = ImmutableList([ { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 3, 'me': true, 'name': '❤' }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateUnEmojiReact(emojiReacts, '❤')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 2, 'me': false, 'name': '❤' }, @@ -60,7 +61,7 @@ describe('simulateUnEmojiReact', () => { { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 2, 'me': false, 'name': '❤' }, { 'count': 1, 'me': true, 'name': '😯' }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateUnEmojiReact(emojiReacts, '😯')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 2, 'me': false, 'name': '❤' }, @@ -72,7 +73,7 @@ describe('simulateUnEmojiReact', () => { { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 2, 'me': false, 'name': '❤' }, { 'count': 1, 'me': true, 'name': 'soapbox', 'url': 'https://gleasonator.com/emoji/Gleasonator/soapbox.png' }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateUnEmojiReact(emojiReacts, 'soapbox')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 2, 'me': false, 'name': '❤' }, diff --git a/packages/pl-fe/src/utils/emoji-reacts.ts b/packages/pl-fe/src/utils/emoji-reacts.ts index f93516b6d..d6fffa01d 100644 --- a/packages/pl-fe/src/utils/emoji-reacts.ts +++ b/packages/pl-fe/src/utils/emoji-reacts.ts @@ -1,18 +1,19 @@ import { emojiReactionSchema, type EmojiReaction } from 'pl-api'; +import * as v from 'valibot'; const simulateEmojiReact = (emojiReacts: Array, emoji: string, url?: string) => { const idx = emojiReacts.findIndex(e => e.name === emoji); const emojiReact = emojiReacts[idx]; if (idx > -1 && emojiReact) { - return emojiReacts.map((reaction, id) => id === idx ? emojiReactionSchema.parse({ + return emojiReacts.map((reaction, id) => id === idx ? v.parse(emojiReactionSchema, { ...emojiReact, count: (emojiReact.count || 0) + 1, me: true, url, }) : reaction); } else { - return [...emojiReacts, emojiReactionSchema.parse({ + return [...emojiReacts, v.parse(emojiReactionSchema, { count: 1, me: true, name: emoji, @@ -30,7 +31,7 @@ const simulateUnEmojiReact = (emojiReacts: Array, emoji: string) if (emojiReact) { const newCount = (emojiReact.count || 1) - 1; if (newCount < 1) return emojiReacts.filter((_, id) => id !== idx); - return emojiReacts.map((reaction, id) => id === idx ? emojiReactionSchema.parse({ + return emojiReacts.map((reaction, id) => id === idx ? v.parse(emojiReactionSchema, { ...emojiReact, count: (emojiReact.count || 1) - 1, me: false, From e30d2d8033211922f02568c0dae24dc453a8ac11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 17:30:46 +0200 Subject: [PATCH 19/28] pl-api: remove zod MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/package.json | 3 +-- packages/pl-api/yarn.lock | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/pl-api/package.json b/packages/pl-api/package.json index b81524f07..027686dba 100644 --- a/packages/pl-api/package.json +++ b/packages/pl-api/package.json @@ -42,8 +42,7 @@ "object-to-formdata": "^4.5.1", "query-string": "^9.1.0", "semver": "^7.6.3", - "valibot": "^0.42.1", - "zod": "^3.23.8" + "valibot": "^0.42.1" }, "module": "./dist/main.es.js", "types": "dist/main.d.ts", diff --git a/packages/pl-api/yarn.lock b/packages/pl-api/yarn.lock index bd2dfc87d..ac923967f 100644 --- a/packages/pl-api/yarn.lock +++ b/packages/pl-api/yarn.lock @@ -2635,8 +2635,3 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zod@^3.23.8: - version "3.23.8" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" - integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== From cf39cf1c36a4e8363fdeb0ef1b8f5e69761fe0e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 17:41:29 +0200 Subject: [PATCH 20/28] pl-fe: wow it compiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/package.json | 1 - packages/pl-fe/src/entity-store/hooks/useBatchedEntities.ts | 4 ++-- packages/pl-fe/src/entity-store/hooks/useCreateEntity.ts | 4 ++-- packages/pl-fe/src/entity-store/hooks/useEntities.ts | 2 +- packages/pl-fe/src/entity-store/hooks/useEntity.ts | 4 ++-- packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts | 3 +-- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/pl-fe/package.json b/packages/pl-fe/package.json index c5ea26e27..0d16d8753 100644 --- a/packages/pl-fe/package.json +++ b/packages/pl-fe/package.json @@ -147,7 +147,6 @@ "vite-plugin-require": "^1.2.14", "vite-plugin-static-copy": "^1.0.6", "wicg-inert": "^3.1.3", - "zod": "^3.23.8", "zustand": "^5.0.0-rc.2" }, "devDependencies": { diff --git a/packages/pl-fe/src/entity-store/hooks/useBatchedEntities.ts b/packages/pl-fe/src/entity-store/hooks/useBatchedEntities.ts index 746452753..40aa0f523 100644 --- a/packages/pl-fe/src/entity-store/hooks/useBatchedEntities.ts +++ b/packages/pl-fe/src/entity-store/hooks/useBatchedEntities.ts @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { z } from 'zod'; +import * as v from 'valibot'; import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch'; import { useAppSelector } from 'pl-fe/hooks/useAppSelector'; @@ -29,7 +29,7 @@ const useBatchedEntities = ( const getState = useGetState(); const dispatch = useAppDispatch(); const { entityType, listKey, path } = parseEntitiesPath(expandedPath); - const schema = opts.schema || z.custom(); + const schema = opts.schema || v.custom(() => true); const isEnabled = opts.enabled ?? true; const isFetching = useListState(path, 'fetching'); diff --git a/packages/pl-fe/src/entity-store/hooks/useCreateEntity.ts b/packages/pl-fe/src/entity-store/hooks/useCreateEntity.ts index 68ede31e7..a7ff90741 100644 --- a/packages/pl-fe/src/entity-store/hooks/useCreateEntity.ts +++ b/packages/pl-fe/src/entity-store/hooks/useCreateEntity.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import * as v from 'valibot'; import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch'; import { useLoading } from 'pl-fe/hooks/useLoading'; @@ -31,7 +31,7 @@ const useCreateEntity = = {}, ): Promise => { const result = await setPromise(entityFn(data)); - const schema = opts.schema || z.custom(); + const schema = opts.schema || v.custom(() => true); let entity: TEntity | TTransformedEntity = v.parse(schema, result); if (opts.transform) entity = opts.transform(entity); diff --git a/packages/pl-fe/src/entity-store/hooks/useEntities.ts b/packages/pl-fe/src/entity-store/hooks/useEntities.ts index 30629a0da..a6de94ac4 100644 --- a/packages/pl-fe/src/entity-store/hooks/useEntities.ts +++ b/packages/pl-fe/src/entity-store/hooks/useEntities.ts @@ -43,7 +43,7 @@ const useEntities = selectEntities(state, path)); - const schema = opts.schema || z.custom(); + const schema = opts.schema || v.custom(() => true); const isEnabled = opts.enabled ?? true; const isFetching = useListState(path, 'fetching'); diff --git a/packages/pl-fe/src/entity-store/hooks/useEntity.ts b/packages/pl-fe/src/entity-store/hooks/useEntity.ts index 793434014..2cd808b37 100644 --- a/packages/pl-fe/src/entity-store/hooks/useEntity.ts +++ b/packages/pl-fe/src/entity-store/hooks/useEntity.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import z from 'zod'; +import * as v from 'valibot'; import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch'; import { useAppSelector } from 'pl-fe/hooks/useAppSelector'; @@ -35,7 +35,7 @@ const useEntity = (); + const defaultSchema = v.custom(() => true); const schema = opts.schema || defaultSchema; const entity = useAppSelector(state => selectEntity(state, entityType, entityId)); diff --git a/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts b/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts index a8dd9d6c8..a59be3c67 100644 --- a/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts +++ b/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react'; import * as v from 'valibot'; -import { z } from 'zod'; import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch'; import { useAppSelector } from 'pl-fe/hooks/useAppSelector'; @@ -23,7 +22,7 @@ const useEntityLookup = , opts: UseEntityOpts = {}, ) => { - const { schema = z.custom() } = opts; + const { schema = v.custom(() => true) } = opts; const dispatch = useAppDispatch(); const [fetchedEntity, setFetchedEntity] = useState(); From 0377c197a2f6a75dccaf3a96d8e010bf4510496d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 17:44:32 +0200 Subject: [PATCH 21/28] pl-fe: remove zod MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/package.json | 2 +- packages/pl-fe/package.json | 2 +- .../pl-fe/src/entity-store/hooks/useEntities.ts | 2 +- .../pl-fe/src/entity-store/hooks/useEntity.ts | 2 +- packages/pl-fe/yarn.lock | 15 +++++---------- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/pl-api/package.json b/packages/pl-api/package.json index 027686dba..a5c039d54 100644 --- a/packages/pl-api/package.json +++ b/packages/pl-api/package.json @@ -1,6 +1,6 @@ { "name": "pl-api", - "version": "0.0.42", + "version": "0.1.0", "type": "module", "homepage": "https://github.com/mkljczk/pl-fe/tree/fork/packages/pl-api", "repository": { diff --git a/packages/pl-fe/package.json b/packages/pl-fe/package.json index 0d16d8753..7013947d1 100644 --- a/packages/pl-fe/package.json +++ b/packages/pl-fe/package.json @@ -103,7 +103,7 @@ "mini-css-extract-plugin": "^2.9.1", "multiselect-react-dropdown": "^2.0.25", "path-browserify": "^1.0.1", - "pl-api": "^0.0.42", + "pl-api": "^0.1.0", "postcss": "^8.4.47", "process": "^0.11.10", "punycode": "^2.1.1", diff --git a/packages/pl-fe/src/entity-store/hooks/useEntities.ts b/packages/pl-fe/src/entity-store/hooks/useEntities.ts index a6de94ac4..158345d65 100644 --- a/packages/pl-fe/src/entity-store/hooks/useEntities.ts +++ b/packages/pl-fe/src/entity-store/hooks/useEntities.ts @@ -17,7 +17,7 @@ import type { PaginatedResponse } from 'pl-api'; /** Additional options for the hook. */ interface UseEntitiesOpts { - /** A zod schema to parse the API entities. */ + /** A valibot schema to parse the API entities. */ schema?: EntitySchema; /** * Time (milliseconds) until this query becomes stale and should be refetched. diff --git a/packages/pl-fe/src/entity-store/hooks/useEntity.ts b/packages/pl-fe/src/entity-store/hooks/useEntity.ts index 2cd808b37..c4aba44ac 100644 --- a/packages/pl-fe/src/entity-store/hooks/useEntity.ts +++ b/packages/pl-fe/src/entity-store/hooks/useEntity.ts @@ -14,7 +14,7 @@ import type { PlfeResponse } from 'pl-fe/api'; /** Additional options for the hook. */ interface UseEntityOpts { - /** A zod schema to parse the API entity. */ + /** A valibot schema to parse the API entity. */ schema?: EntitySchema; /** Whether to refetch this entity every time the hook mounts, even if it's already in the store. */ refetch?: boolean; diff --git a/packages/pl-fe/yarn.lock b/packages/pl-fe/yarn.lock index 4d552f5f7..d7549f2e4 100644 --- a/packages/pl-fe/yarn.lock +++ b/packages/pl-fe/yarn.lock @@ -7556,10 +7556,10 @@ pirates@^4.0.1: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== -pl-api@^0.0.42: - version "0.0.42" - resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.0.42.tgz#e15cd33ae494384e84feee4a1022849d7e61290b" - integrity sha512-BEkaJVIHC+8F/ta+5qdSIfK5/F8lCev2FboZoLp/aE9OtiRkvRxiZsBB4VLn5oSRSv26HmhoNYR8fHaJyw/VEw== +pl-api@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.1.0.tgz#bcb059a4f9562a7e1294535898f6098161129eaa" + integrity sha512-n1hSb1u4tfsSeseWpJOWCKdenvNQ5iXkb1OuAQq7c0duCuoib1q51Etp9sZiW52YpqcP6CKkkF6McJnlh551sg== dependencies: blurhash "^2.0.5" http-link-header "^1.1.3" @@ -7567,7 +7567,7 @@ pl-api@^0.0.42: object-to-formdata "^4.5.1" query-string "^9.1.0" semver "^7.6.3" - zod "^3.23.8" + valibot "^0.42.1" possible-typed-array-names@^1.0.0: version "1.0.0" @@ -10455,11 +10455,6 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zod@^3.23.8: - version "3.23.8" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" - integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== - zustand@^5.0.0-rc.2: version "5.0.0-rc.2" resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.0-rc.2.tgz#d28d95ffb6f0b20cadbaea39210f18446a5bf989" From 595c16ace27468e39de6ed2e7783e7e2973a4493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 18:46:14 +0200 Subject: [PATCH 22/28] externalize pl-api deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/vite.config.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/pl-api/vite.config.ts b/packages/pl-api/vite.config.ts index 79e513c5f..0928cfc7a 100644 --- a/packages/pl-api/vite.config.ts +++ b/packages/pl-api/vite.config.ts @@ -3,6 +3,8 @@ import { resolve } from 'path'; import { defineConfig } from 'vite'; import dts from 'vite-plugin-dts'; +import pkg from './package.json'; + export default defineConfig({ plugins: [dts({ include: ['lib'], insertTypesEntry: true })], build: { @@ -15,5 +17,8 @@ export default defineConfig({ }, target: 'esnext', sourcemap: true, + rollupOptions: { + external: Object.keys(pkg.dependencies), + }, }, }); From 1f4d879296013e32c5a5650563c29aff819465f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 20:12:41 +0200 Subject: [PATCH 23/28] bump pl-api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/package.json | 2 +- packages/pl-fe/package.json | 2 +- packages/pl-fe/yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/pl-api/package.json b/packages/pl-api/package.json index a5c039d54..2fe3a57e9 100644 --- a/packages/pl-api/package.json +++ b/packages/pl-api/package.json @@ -1,6 +1,6 @@ { "name": "pl-api", - "version": "0.1.0", + "version": "0.1.1", "type": "module", "homepage": "https://github.com/mkljczk/pl-fe/tree/fork/packages/pl-api", "repository": { diff --git a/packages/pl-fe/package.json b/packages/pl-fe/package.json index 7013947d1..75262306a 100644 --- a/packages/pl-fe/package.json +++ b/packages/pl-fe/package.json @@ -103,7 +103,7 @@ "mini-css-extract-plugin": "^2.9.1", "multiselect-react-dropdown": "^2.0.25", "path-browserify": "^1.0.1", - "pl-api": "^0.1.0", + "pl-api": "^0.1.1", "postcss": "^8.4.47", "process": "^0.11.10", "punycode": "^2.1.1", diff --git a/packages/pl-fe/yarn.lock b/packages/pl-fe/yarn.lock index d7549f2e4..8c49212de 100644 --- a/packages/pl-fe/yarn.lock +++ b/packages/pl-fe/yarn.lock @@ -7556,10 +7556,10 @@ pirates@^4.0.1: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== -pl-api@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.1.0.tgz#bcb059a4f9562a7e1294535898f6098161129eaa" - integrity sha512-n1hSb1u4tfsSeseWpJOWCKdenvNQ5iXkb1OuAQq7c0duCuoib1q51Etp9sZiW52YpqcP6CKkkF6McJnlh551sg== +pl-api@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.1.1.tgz#e3465482697ea53e10bba510c5217c42b1d40ab0" + integrity sha512-JFao273dOG9XgrunqlfJn+e3DUr001MePCmJasQwaX3aPjM/L158hKfCnYPmoDKn1u5bfekMK6tVauBtbnkmqA== dependencies: blurhash "^2.0.5" http-link-header "^1.1.3" From a70add24af373c39ff74c7275b4f2201e42203e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 20:22:39 +0200 Subject: [PATCH 24/28] pl-fe: Remove fix for custom emojis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/package.json | 1 - packages/pl-fe/src/reducers/custom-emojis.ts | 22 ++------------------ 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/pl-fe/package.json b/packages/pl-fe/package.json index 75262306a..28837b1f9 100644 --- a/packages/pl-fe/package.json +++ b/packages/pl-fe/package.json @@ -132,7 +132,6 @@ "reselect": "^5.1.1", "resize-observer-polyfill": "^1.5.1", "sass": "^1.79.4", - "semver": "^7.6.3", "stringz": "^2.1.0", "tiny-queue": "^0.2.1", "tslib": "^2.7.0", diff --git a/packages/pl-fe/src/reducers/custom-emojis.ts b/packages/pl-fe/src/reducers/custom-emojis.ts index d6fe9f49e..4273d44c9 100644 --- a/packages/pl-fe/src/reducers/custom-emojis.ts +++ b/packages/pl-fe/src/reducers/custom-emojis.ts @@ -1,5 +1,4 @@ import { buildCustomEmojis } from 'pl-fe/features/emoji'; -import emojiData from 'pl-fe/features/emoji/data'; import { addCustomToPool } from 'pl-fe/features/emoji/search'; import { CUSTOM_EMOJIS_FETCH_SUCCESS, type CustomEmojisAction } from '../actions/custom-emojis'; @@ -8,27 +7,10 @@ import type { CustomEmoji } from 'pl-api'; const initialState: Array = []; -// Populate custom emojis for composer autosuggest -const autosuggestPopulate = (emojis: Array) => { - addCustomToPool(buildCustomEmojis(emojis)); -}; - -const importEmojis = (customEmojis: Array) => { - const emojis = customEmojis.filter((emoji) => { - // If a custom emoji has the shortcode of a Unicode emoji, skip it. - // Otherwise it breaks EmojiMart. - // https://gitlab.com/soapbox-pub/soapbox/-/issues/610 - const shortcode = emoji.shortcode.toLowerCase(); - return !emojiData.emojis[shortcode]; - }); - - autosuggestPopulate(emojis); - return emojis; -}; - const custom_emojis = (state = initialState, action: CustomEmojisAction) => { if (action.type === CUSTOM_EMOJIS_FETCH_SUCCESS) { - return importEmojis(action.custom_emojis); + addCustomToPool(buildCustomEmojis(action.custom_emojis)); + return action.custom_emojis; } return state; From 3e45a3142b99c27bb950dd1c4730ed0e5d1f5468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 23:27:12 +0200 Subject: [PATCH 25/28] pl-fe: WIP: Lazy load emoji data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/features/emoji/data.ts | 52 +-------- packages/pl-fe/src/features/emoji/index.ts | 2 +- packages/pl-fe/src/features/emoji/mapping.ts | 107 +------------------ packages/pl-fe/src/features/emoji/search.ts | 11 +- 4 files changed, 11 insertions(+), 161 deletions(-) diff --git a/packages/pl-fe/src/features/emoji/data.ts b/packages/pl-fe/src/features/emoji/data.ts index db8db115b..6a560ff9c 100644 --- a/packages/pl-fe/src/features/emoji/data.ts +++ b/packages/pl-fe/src/features/emoji/data.ts @@ -1,61 +1,11 @@ import data from '@emoji-mart/data/sets/14/twitter.json'; -interface NativeEmoji { - unified: string; - native: string; - x: number; - y: number; -} - -interface CustomEmoji { - src: string; -} - -interface Emoji { - id: string; - name: string; - keywords: string[]; - skins: T[]; - version?: number; -} - -interface EmojiCategory { - id: string; - emojis: string[]; -} - -interface EmojiMap { - [s: string]: Emoji; -} - -interface EmojiAlias { - [s: string]: string; -} - -interface EmojiSheet { - cols: number; - rows: number; -} - -interface EmojiData { - categories: EmojiCategory[]; - emojis: EmojiMap; - aliases: EmojiAlias; - sheet: EmojiSheet; -} +import type { EmojiData } from './types'; const emojiData = data as EmojiData; const { categories, emojis, aliases, sheet } = emojiData; export { - type NativeEmoji, - type CustomEmoji, - type Emoji, - type EmojiCategory, - type EmojiMap, - type EmojiAlias, - type EmojiSheet, - type EmojiData, categories, emojis, aliases, diff --git a/packages/pl-fe/src/features/emoji/index.ts b/packages/pl-fe/src/features/emoji/index.ts index 1e85fc74f..2527d1cb1 100644 --- a/packages/pl-fe/src/features/emoji/index.ts +++ b/packages/pl-fe/src/features/emoji/index.ts @@ -2,8 +2,8 @@ import split from 'graphemesplit'; import unicodeMapping from './mapping'; +import type { Emoji as EmojiMart, CustomEmoji as EmojiMartCustom } from './types'; import type { CustomEmoji as BaseCustomEmoji } from 'pl-api'; -import type { Emoji as EmojiMart, CustomEmoji as EmojiMartCustom } from 'pl-fe/features/emoji/data'; /* * TODO: Consolate emoji object types diff --git a/packages/pl-fe/src/features/emoji/mapping.ts b/packages/pl-fe/src/features/emoji/mapping.ts index 5d5ba507a..d7fb32c52 100644 --- a/packages/pl-fe/src/features/emoji/mapping.ts +++ b/packages/pl-fe/src/features/emoji/mapping.ts @@ -1,106 +1,3 @@ -import data, { EmojiData } from './data'; +import type { UnicodeMap } from './types'; -const stripLeadingZeros = /^0+/; -interface UnicodeMap { - [s: string]: { - unified: string; - shortcode: string; - }; -} - -/* - * Twemoji strips their hex codes from unicode codepoints to make it look "pretty" - * - leading 0s are removed - * - fe0f is removed unless it has 200d - * - fe0f is NOT removed for 1f441-fe0f-200d-1f5e8-fe0f even though it has a 200d - * - * this is all wrong - */ - -const blacklist = { - '1f441-fe0f-200d-1f5e8-fe0f': true, -}; - -const tweaks = { - '#⃣': ['23-20e3', 'hash'], - '*⃣': ['2a-20e3', 'keycap_star'], - '0⃣': ['30-20e3', 'zero'], - '1⃣': ['31-20e3', 'one'], - '2⃣': ['32-20e3', 'two'], - '3⃣': ['33-20e3', 'three'], - '4⃣': ['34-20e3', 'four'], - '5⃣': ['35-20e3', 'five'], - '6⃣': ['36-20e3', 'six'], - '7⃣': ['37-20e3', 'seven'], - '8⃣': ['38-20e3', 'eight'], - '9⃣': ['39-20e3', 'nine'], - '❤‍🔥': ['2764-fe0f-200d-1f525', 'heart_on_fire'], - '❤‍🩹': ['2764-fe0f-200d-1fa79', 'mending_heart'], - '👁‍🗨️': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'], - '👁️‍🗨': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'], - '👁‍🗨': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'], - '🕵‍♂️': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'], - '🕵️‍♂': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'], - '🕵‍♂': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'], - '🕵‍♀️': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'], - '🕵️‍♀': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'], - '🕵‍♀': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'], - '🏌‍♂️': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'], - '🏌️‍♂': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'], - '🏌‍♂': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'], - '🏌‍♀️': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'], - '🏌️‍♀': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'], - '🏌‍♀': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'], - '⛹‍♂️': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'], - '⛹️‍♂': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'], - '⛹‍♂': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'], - '⛹‍♀️': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'], - '⛹️‍♀': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'], - '⛹‍♀': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'], - '🏋‍♂️': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'], - '🏋️‍♂': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'], - '🏋‍♂': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'], - '🏋‍♀️': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'], - '🏋️‍♀': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'], - '🏋‍♀': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'], - '🏳‍🌈': ['1f3f3-fe0f-200d-1f308', 'rainbow_flag'], - '🏳‍⚧️': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'], - '🏳️‍⚧': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'], - '🏳‍⚧': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'], -}; - -const stripcodes = (unified: string, native: string) => { - const stripped = unified.replace(stripLeadingZeros, ''); - - if (unified.includes('200d') && !(unified in blacklist)) { - return stripped; - } else { - return stripped.replaceAll('-fe0f', ''); - } -}; - -const generateMappings = (data: EmojiData): UnicodeMap => { - const result: UnicodeMap = {}; - const emojis = Object.values(data.emojis ?? {}); - - for (const value of emojis) { - for (const item of value.skins) { - const { unified, native } = item; - const stripped = stripcodes(unified, native); - - result[native] = { unified: stripped, shortcode: value.id }; - } - } - - for (const [native, [unified, shortcode]] of Object.entries(tweaks)) { - const stripped = stripcodes(unified, native); - - result[native] = { unified: stripped, shortcode }; - } - - return result; -}; - -const unicodeMapping = generateMappings(data); - -export { generateMappings, unicodeMapping as default }; +export default import.meta.compileTime('./mapping-compiletime.ts'); diff --git a/packages/pl-fe/src/features/emoji/search.ts b/packages/pl-fe/src/features/emoji/search.ts index 0316f6445..5e34595f4 100644 --- a/packages/pl-fe/src/features/emoji/search.ts +++ b/packages/pl-fe/src/features/emoji/search.ts @@ -1,17 +1,20 @@ import FlexSearch from 'flexsearch'; -import data from './data'; - import type { Emoji } from './index'; +import type { EmojiData } from './types'; import type { CustomEmoji } from 'pl-api'; +let emojis: EmojiData['emojis'] = {}; + +import('./data').then(data => emojis = data.emojis).catch(() => { }); + const index = new FlexSearch.Index({ tokenize: 'full', optimize: true, context: true, }); -const sortedEmojis = Object.entries(data.emojis).sort((a, b) => a[0].localeCompare(b[0])); +const sortedEmojis = Object.entries(emojis).sort((a, b) => a[0].localeCompare(b[0])); for (const [key, emoji] of sortedEmojis) { index.add('n' + key, `${emoji.id} ${emoji.name} ${emoji.keywords.join(' ')}`); } @@ -58,7 +61,7 @@ const search = ( } } - const skins = data.emojis[id.slice(1)]?.skins; + const skins = emojis[id.slice(1)]?.skins; if (skins) { return { From 8ae3adbe5a7a651ecc1b180bf9caa7b8dc301f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 23:36:49 +0200 Subject: [PATCH 26/28] pl-fe: fix emoji search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/features/emoji/search.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/pl-fe/src/features/emoji/search.ts b/packages/pl-fe/src/features/emoji/search.ts index 5e34595f4..c13f8ddb7 100644 --- a/packages/pl-fe/src/features/emoji/search.ts +++ b/packages/pl-fe/src/features/emoji/search.ts @@ -6,7 +6,14 @@ import type { CustomEmoji } from 'pl-api'; let emojis: EmojiData['emojis'] = {}; -import('./data').then(data => emojis = data.emojis).catch(() => { }); +import('./data').then(data => { + emojis = data.emojis; + + const sortedEmojis = Object.entries(emojis).sort((a, b) => a[0].localeCompare(b[0])); + for (const [key, emoji] of sortedEmojis) { + index.add('n' + key, `${emoji.id} ${emoji.name} ${emoji.keywords.join(' ')}`); + } +}).catch(() => { }); const index = new FlexSearch.Index({ tokenize: 'full', @@ -14,11 +21,6 @@ const index = new FlexSearch.Index({ context: true, }); -const sortedEmojis = Object.entries(emojis).sort((a, b) => a[0].localeCompare(b[0])); -for (const [key, emoji] of sortedEmojis) { - index.add('n' + key, `${emoji.id} ${emoji.name} ${emoji.keywords.join(' ')}`); -} - interface searchOptions { maxResults?: number; custom?: any; From 8ddc5a797c650c9e5aa7b7c15131344e37c494bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 17 Oct 2024 00:08:55 +0200 Subject: [PATCH 27/28] pl-fe: Lazy load emoji data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../src/features/emoji/mapping-compiletime.ts | 106 ++++++++++++++++++ packages/pl-fe/src/features/emoji/types.ts | 62 ++++++++++ 2 files changed, 168 insertions(+) create mode 100644 packages/pl-fe/src/features/emoji/mapping-compiletime.ts create mode 100644 packages/pl-fe/src/features/emoji/types.ts diff --git a/packages/pl-fe/src/features/emoji/mapping-compiletime.ts b/packages/pl-fe/src/features/emoji/mapping-compiletime.ts new file mode 100644 index 000000000..67d094e9a --- /dev/null +++ b/packages/pl-fe/src/features/emoji/mapping-compiletime.ts @@ -0,0 +1,106 @@ +import { createRequire } from 'node:module'; + +import type { EmojiData, UnicodeMap } from './types'; + +const require = createRequire(import.meta.url); +const data = require('@emoji-mart/data/sets/14/twitter.json'); + +const stripLeadingZeros = /^0+/; + +/* + * Twemoji strips their hex codes from unicode codepoints to make it look "pretty" + * - leading 0s are removed + * - fe0f is removed unless it has 200d + * - fe0f is NOT removed for 1f441-fe0f-200d-1f5e8-fe0f even though it has a 200d + * + * this is all wrong + */ +const blacklist = { + '1f441-fe0f-200d-1f5e8-fe0f': true, +}; + +const tweaks = { + '#⃣': ['23-20e3', 'hash'], + '*⃣': ['2a-20e3', 'keycap_star'], + '0⃣': ['30-20e3', 'zero'], + '1⃣': ['31-20e3', 'one'], + '2⃣': ['32-20e3', 'two'], + '3⃣': ['33-20e3', 'three'], + '4⃣': ['34-20e3', 'four'], + '5⃣': ['35-20e3', 'five'], + '6⃣': ['36-20e3', 'six'], + '7⃣': ['37-20e3', 'seven'], + '8⃣': ['38-20e3', 'eight'], + '9⃣': ['39-20e3', 'nine'], + '❤‍🔥': ['2764-fe0f-200d-1f525', 'heart_on_fire'], + '❤‍🩹': ['2764-fe0f-200d-1fa79', 'mending_heart'], + '👁‍🗨️': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'], + '👁️‍🗨': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'], + '👁‍🗨': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'], + '🕵‍♂️': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'], + '🕵️‍♂': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'], + '🕵‍♂': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'], + '🕵‍♀️': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'], + '🕵️‍♀': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'], + '🕵‍♀': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'], + '🏌‍♂️': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'], + '🏌️‍♂': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'], + '🏌‍♂': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'], + '🏌‍♀️': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'], + '🏌️‍♀': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'], + '🏌‍♀': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'], + '⛹‍♂️': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'], + '⛹️‍♂': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'], + '⛹‍♂': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'], + '⛹‍♀️': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'], + '⛹️‍♀': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'], + '⛹‍♀': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'], + '🏋‍♂️': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'], + '🏋️‍♂': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'], + '🏋‍♂': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'], + '🏋‍♀️': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'], + '🏋️‍♀': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'], + '🏋‍♀': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'], + '🏳‍🌈': ['1f3f3-fe0f-200d-1f308', 'rainbow_flag'], + '🏳‍⚧️': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'], + '🏳️‍⚧': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'], + '🏳‍⚧': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'], +}; + +const stripcodes = (unified: string, native: string) => { + const stripped = unified.replace(stripLeadingZeros, ''); + + if (unified.includes('200d') && !(unified in blacklist)) { + return stripped; + } else { + return stripped.replaceAll('-fe0f', ''); + } +}; + +const generateMappings = (emojiMap: EmojiData['emojis']): UnicodeMap => { + const result: UnicodeMap = {}; + const emojis = Object.values(emojiMap ?? {}); + + for (const value of emojis) { + for (const item of value.skins) { + const { unified, native } = item; + const stripped = stripcodes(unified, native); + + result[native] = { unified: stripped, shortcode: value.id }; + } + } + + for (const [native, [unified, shortcode]] of Object.entries(tweaks)) { + const stripped = stripcodes(unified, native); + + result[native] = { unified: stripped, shortcode }; + } + + return result; +}; + +const unicodeMapping = generateMappings(data.emojis); + +export default () => ({ + data: unicodeMapping, +}); diff --git a/packages/pl-fe/src/features/emoji/types.ts b/packages/pl-fe/src/features/emoji/types.ts new file mode 100644 index 000000000..d1cbbf450 --- /dev/null +++ b/packages/pl-fe/src/features/emoji/types.ts @@ -0,0 +1,62 @@ +interface UnicodeMap { + [s: string]: { + unified: string; + shortcode: string; + }; +} + +interface NativeEmoji { + unified: string; + native: string; + x: number; + y: number; +} + +interface CustomEmoji { + src: string; +} + +interface Emoji { + id: string; + name: string; + keywords: string[]; + skins: T[]; + version?: number; +} + +interface EmojiCategory { + id: string; + emojis: string[]; +} + +interface EmojiMap { + [s: string]: Emoji; +} + +interface EmojiAlias { + [s: string]: string; +} + +interface EmojiSheet { + cols: number; + rows: number; +} + +interface EmojiData { + categories: EmojiCategory[]; + emojis: EmojiMap; + aliases: EmojiAlias; + sheet: EmojiSheet; +} + +export type { + UnicodeMap, + NativeEmoji, + CustomEmoji, + Emoji, + EmojiCategory, + EmojiMap, + EmojiAlias, + EmojiSheet, + EmojiData, +}; From 9e723fcf95d11e04fa0225da1606e28cfaa33901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 17 Oct 2024 00:14:27 +0200 Subject: [PATCH 28/28] pl-fe: fix type definitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/features/emoji/data.ts | 52 +++++++++++++++- packages/pl-fe/src/features/emoji/index.ts | 2 +- .../src/features/emoji/mapping-compiletime.ts | 3 +- packages/pl-fe/src/features/emoji/mapping.ts | 9 ++- packages/pl-fe/src/features/emoji/search.ts | 2 +- packages/pl-fe/src/features/emoji/types.ts | 62 ------------------- 6 files changed, 63 insertions(+), 67 deletions(-) delete mode 100644 packages/pl-fe/src/features/emoji/types.ts diff --git a/packages/pl-fe/src/features/emoji/data.ts b/packages/pl-fe/src/features/emoji/data.ts index 6a560ff9c..db8db115b 100644 --- a/packages/pl-fe/src/features/emoji/data.ts +++ b/packages/pl-fe/src/features/emoji/data.ts @@ -1,11 +1,61 @@ import data from '@emoji-mart/data/sets/14/twitter.json'; -import type { EmojiData } from './types'; +interface NativeEmoji { + unified: string; + native: string; + x: number; + y: number; +} + +interface CustomEmoji { + src: string; +} + +interface Emoji { + id: string; + name: string; + keywords: string[]; + skins: T[]; + version?: number; +} + +interface EmojiCategory { + id: string; + emojis: string[]; +} + +interface EmojiMap { + [s: string]: Emoji; +} + +interface EmojiAlias { + [s: string]: string; +} + +interface EmojiSheet { + cols: number; + rows: number; +} + +interface EmojiData { + categories: EmojiCategory[]; + emojis: EmojiMap; + aliases: EmojiAlias; + sheet: EmojiSheet; +} const emojiData = data as EmojiData; const { categories, emojis, aliases, sheet } = emojiData; export { + type NativeEmoji, + type CustomEmoji, + type Emoji, + type EmojiCategory, + type EmojiMap, + type EmojiAlias, + type EmojiSheet, + type EmojiData, categories, emojis, aliases, diff --git a/packages/pl-fe/src/features/emoji/index.ts b/packages/pl-fe/src/features/emoji/index.ts index 2527d1cb1..aafe331d6 100644 --- a/packages/pl-fe/src/features/emoji/index.ts +++ b/packages/pl-fe/src/features/emoji/index.ts @@ -2,7 +2,7 @@ import split from 'graphemesplit'; import unicodeMapping from './mapping'; -import type { Emoji as EmojiMart, CustomEmoji as EmojiMartCustom } from './types'; +import type { Emoji as EmojiMart, CustomEmoji as EmojiMartCustom } from './data'; import type { CustomEmoji as BaseCustomEmoji } from 'pl-api'; /* diff --git a/packages/pl-fe/src/features/emoji/mapping-compiletime.ts b/packages/pl-fe/src/features/emoji/mapping-compiletime.ts index 67d094e9a..f5ef8cdf0 100644 --- a/packages/pl-fe/src/features/emoji/mapping-compiletime.ts +++ b/packages/pl-fe/src/features/emoji/mapping-compiletime.ts @@ -1,6 +1,7 @@ import { createRequire } from 'node:module'; -import type { EmojiData, UnicodeMap } from './types'; +import type { EmojiData } from './data'; +import type { UnicodeMap } from './mapping'; const require = createRequire(import.meta.url); const data = require('@emoji-mart/data/sets/14/twitter.json'); diff --git a/packages/pl-fe/src/features/emoji/mapping.ts b/packages/pl-fe/src/features/emoji/mapping.ts index d7fb32c52..a76d52245 100644 --- a/packages/pl-fe/src/features/emoji/mapping.ts +++ b/packages/pl-fe/src/features/emoji/mapping.ts @@ -1,3 +1,10 @@ -import type { UnicodeMap } from './types'; +interface UnicodeMap { + [s: string]: { + unified: string; + shortcode: string; + }; +} export default import.meta.compileTime('./mapping-compiletime.ts'); + +export type { UnicodeMap }; diff --git a/packages/pl-fe/src/features/emoji/search.ts b/packages/pl-fe/src/features/emoji/search.ts index c13f8ddb7..286873ca7 100644 --- a/packages/pl-fe/src/features/emoji/search.ts +++ b/packages/pl-fe/src/features/emoji/search.ts @@ -1,7 +1,7 @@ import FlexSearch from 'flexsearch'; +import type { EmojiData } from './data'; import type { Emoji } from './index'; -import type { EmojiData } from './types'; import type { CustomEmoji } from 'pl-api'; let emojis: EmojiData['emojis'] = {}; diff --git a/packages/pl-fe/src/features/emoji/types.ts b/packages/pl-fe/src/features/emoji/types.ts deleted file mode 100644 index d1cbbf450..000000000 --- a/packages/pl-fe/src/features/emoji/types.ts +++ /dev/null @@ -1,62 +0,0 @@ -interface UnicodeMap { - [s: string]: { - unified: string; - shortcode: string; - }; -} - -interface NativeEmoji { - unified: string; - native: string; - x: number; - y: number; -} - -interface CustomEmoji { - src: string; -} - -interface Emoji { - id: string; - name: string; - keywords: string[]; - skins: T[]; - version?: number; -} - -interface EmojiCategory { - id: string; - emojis: string[]; -} - -interface EmojiMap { - [s: string]: Emoji; -} - -interface EmojiAlias { - [s: string]: string; -} - -interface EmojiSheet { - cols: number; - rows: number; -} - -interface EmojiData { - categories: EmojiCategory[]; - emojis: EmojiMap; - aliases: EmojiAlias; - sheet: EmojiSheet; -} - -export type { - UnicodeMap, - NativeEmoji, - CustomEmoji, - Emoji, - EmojiCategory, - EmojiMap, - EmojiAlias, - EmojiSheet, - EmojiData, -};