2025-02-19 16:24:20 +01:00
#!/bin/python3
2025-02-19 16:29:20 +01:00
# trouver les articles précédents et suivants
2025-02-19 16:24:20 +01:00
from utils import *
from website_config import configs_sites
import os
import json
import re
2025-02-19 16:29:20 +01:00
import argparse
2025-02-19 22:39:11 +01:00
import pypandoc
from jinja2 import Environment , FileSystemLoader
2025-02-20 14:53:59 +01:00
import time # Importer le module time
# Démarrer le chronomètre
start_time = time . time ( )
2025-02-21 23:40:35 +01:00
# Configs pour tester
2025-02-20 17:14:53 +01:00
generate_linkings_json = True
2025-02-23 20:47:50 +01:00
2025-02-21 23:40:35 +01:00
2025-02-19 16:29:20 +01:00
# Configurer argparse pour prendre le blog en argument
parser = argparse . ArgumentParser ( description = ' Générer une liste des derniers articles de blog. ' )
2025-02-23 19:12:15 +01:00
parser . add_argument ( ' blog ' , type = str , help = ' Nom du dossier du blog à traiter ' , default = ' cipherbliss_blog ' )
2025-02-27 19:34:49 +01:00
parser . add_argument ( ' --run_gemini ' , type = str , help = ' Activer ou non la génération des billets gemini ' , default = True )
2025-02-23 19:12:15 +01:00
parser . add_argument ( ' --run_pandoc ' , type = str , help = ' Activer ou non la génération des fichiers html ' , default = True )
2025-02-23 20:23:38 +01:00
parser . add_argument ( ' --enable_roam_id_rewrite ' , type = str , help = ' Activer ou non la réécriture des liens roam ' , default = False )
parser . add_argument ( ' --force_html_regen ' , action = ' store_true ' , help = ' Forcer la régénération des fichiers HTML même s \' ils existent déjà ' )
2025-02-22 18:55:01 +01:00
2025-02-19 16:29:20 +01:00
args = parser . parse_args ( )
2025-02-23 19:12:15 +01:00
run_gemini = args . run_gemini
run_pandoc = args . run_pandoc
2025-02-23 20:23:38 +01:00
force_html_regen = args . force_html_regen
# TODO check cette fonctionnalité
enable_roam_id_rewrite = args . enable_roam_id_rewrite
2025-02-19 16:24:20 +01:00
# Fonction pour extraire le basename d'un fichier
def get_basename ( file_name ) :
return os . path . splitext ( file_name ) [ 0 ]
# Chemin du dossier contenant les fichiers orgmode
2025-02-27 16:18:47 +01:00
directory_pages = f ' sources/ { args . blog } / '
2025-02-27 19:34:49 +01:00
lang_fr = f ' sources/ { args . blog } /lang_fr '
lang_en = f ' sources/ { args . blog } /lang_en '
directories_to_scan = [ directory_pages , lang_fr , lang_en ]
2025-02-27 16:18:47 +01:00
2025-02-19 16:29:20 +01:00
destination_json = f ' sources/ { args . blog } /build '
2025-02-19 22:39:11 +01:00
destination_html = f ' html-websites/ { args . blog } / '
2025-02-21 23:40:35 +01:00
destination_gmi = f ' gemini-capsules/ { args . blog } / '
2025-02-19 16:24:20 +01:00
# Dictionnaire pour stocker les informations des fichiers
2025-02-23 20:47:50 +01:00
# Vérifier si le fichier JSON existe déjà
json_file = destination_json + ' /articles_info.json '
if os . path . exists ( json_file ) :
print ( f " Chargement du fichier JSON existant: { json_file } " )
try :
with open ( json_file , ' r ' , encoding = ' utf-8 ' ) as f :
files_dict = json . load ( f )
print ( f " Fichier JSON chargé avec succès, { len ( files_dict ) } articles trouvés " )
except Exception as e :
print ( f " Erreur lors du chargement du fichier JSON: { e } " )
files_dict = { }
else :
print ( " Aucun fichier JSON existant trouvé " )
files_dict = { }
2025-02-19 16:24:20 +01:00
2025-02-21 23:40:35 +01:00
2025-02-27 16:18:47 +01:00
count_articles = count_files_in_directories ( directories_to_scan )
2025-02-21 23:40:35 +01:00
2025-02-23 20:47:50 +01:00
counter = 0
rebuild_counter = 0
pandoc_runs_counter = 0
2025-02-27 16:18:47 +01:00
lang_folder = global_config . get ( ' lang_default ' , ' fr ' )
2025-02-23 20:47:50 +01:00
2025-02-20 17:14:53 +01:00
if generate_linkings_json :
2025-02-23 19:12:15 +01:00
print ( f " Génération des liens entre articles pour { count_articles } articles " )
print ( f " run_pandoc: { run_pandoc } " )
print ( f " run_gemini: { run_gemini } " )
2025-02-27 16:18:47 +01:00
article_type = " article "
2025-02-20 17:14:53 +01:00
# Parcourir les fichiers du dossier
2025-02-27 16:18:47 +01:00
for index , directory in enumerate ( directories_to_scan ) :
2025-02-27 19:34:49 +01:00
if directory == " .. " or directory == " build " or directory == " templates " or directory == " img " :
continue
2025-02-27 16:18:47 +01:00
# Déterminer le type d'article en fonction du chemin
if directory == ' / ' :
article_type = " page "
else :
article_type = " article "
# Extraire la langue du dossier si elle commence par "lang_"
if directory . split ( ' / ' ) [ - 1 ] . startswith ( ' lang_ ' ) :
lang_folder = directory . split ( ' / ' ) [ - 1 ] [ 5 : ] # Prend les caractères après "lang_"
for file_name in os . listdir ( directory ) :
2025-02-27 19:34:49 +01:00
# Vérifier si le fichier se termine par une extension supportée
if not ( file_name . endswith ( ' .org ' ) or file_name . endswith ( ' .md ' ) or file_name . endswith ( ' .gmi ' ) ) :
continue
2025-02-27 16:18:47 +01:00
if file_name . endswith ( ' .org ' ) :
counter + = 1
if force_html_regen and counter % 10 == 0 :
print ( f " { time . strftime ( ' % H: % M: % S ' ) } : Articles traités : { counter } / { count_articles } " )
file_path = os . path . join ( directory , file_name )
with open ( file_path , " r " , encoding = " utf-8 " ) as f :
content = f . read ( )
date_modified = time . ctime ( os . path . getmtime ( file_path ) )
basename = get_basename ( file_name )
date_str , annee , slug = find_year_and_slug_on_filename ( basename )
tags = extract_tags_from_file ( file_path , global_config [ ' excluded_tags ' ] )
2025-02-23 20:09:39 +01:00
2025-02-27 16:18:47 +01:00
# Convertir les tags en liste si c'est un set
if isinstance ( tags , set ) :
tags = list ( tags )
boom = basename . split ( ' __ ' )
# Convertir le contenu Org en HTML
title = find_first_level1_title ( content )
# Désactiver les warning d'identifiant dupliqué dans la conversion pandoc
content_without_h1 = re . sub ( r ' ^ \ *.*?$ ' , ' ' , content , count = 1 , flags = re . MULTILINE )
gemini_content = ' '
html_content = ' '
html_content_without_h1 = ' '
# Vérifier l'existence du fichier HTML pour déterminer last_html_build
html_path = f " html-websites/ { args . blog } / { annee } / { slug } /index.html "
last_html_build_time = None
if os . path . exists ( html_path ) :
# Obtenir la date de création du fichier HTML
last_html_build_time = os . path . getctime ( html_path )
# print(f"last_html_build: {last_html_build_time} : {html_path}")
2025-02-24 11:57:17 +01:00
else :
2025-02-27 16:18:47 +01:00
print ( f " ----------- last_html_build html_path: { html_path } n ' existe pas " )
# Vérifier l'existence du fichier Gemini pour déterminer last_gemini_build
2025-02-27 19:34:49 +01:00
os . makedirs ( os . path . dirname ( f " ./gemini-capsules/ { args . blog } / { annee } " ) , exist_ok = True )
gemini_path = f " ./gemini-capsules/ { args . blog } / { annee } / { slug } .gmi "
2025-02-27 16:18:47 +01:00
last_gemini_build = None
rebuild_this_article_gemini = False
if os . path . exists ( gemini_path ) :
last_gemini_build = time . ctime ( os . path . getmtime ( gemini_path ) )
# Vérifier si l'article doit être reconstruit en comparant les dates de modification
if last_gemini_build :
file_modified_time = os . path . getmtime ( file_path )
last_build_time = time . mktime ( time . strptime ( last_gemini_build ) )
rebuild_this_article_gemini = file_modified_time > last_build_time
else :
rebuild_this_article_gemini = True
# Vérifier si l'article doit être reconstruit en comparant les dates de modification
rebuild_this_article_html = False
if last_html_build_time :
file_modified_time = os . path . getmtime ( file_path )
# print(f"--------- file_modified_time: {file_path} : {file_modified_time}")
# Obtenir l'heure de dernière modification du fichier HTML
rebuild_this_article_html = file_modified_time > last_html_build_time
# print(f"--------- article modifié après le build de son rendu html: {file_path}, {rebuild_this_article_html}")
else :
# si il n'y a pas de fichier html, on le construit pour la première fois
print ( ' on reconstruit le html de l \' article ' , file_name )
rebuild_this_article_html = True
2025-02-23 20:47:50 +01:00
2025-02-27 16:18:47 +01:00
if rebuild_this_article_html :
rebuild_counter + = 1
# Garder le contenu HTML existant si déjà présent
if f " { annee } / { slug } " in files_dict and ' html_content ' in files_dict [ f " { annee } / { slug } " ] :
print ( ' on reprend le contenu html existant ' )
if len ( files_dict [ f " { annee } / { slug } " ] [ ' html_content ' ] ) > 0 :
html_content = files_dict [ f " { annee } / { slug } " ] [ ' html_content ' ]
if len ( files_dict [ f " { annee } / { slug } " ] [ ' html_content_without_h1 ' ] ) > 0 :
html_content_without_h1 = files_dict [ f " { annee } / { slug } " ] [ ' html_content_without_h1 ' ]
else :
html_content_without_h1 = re . sub ( r ' <h1>.*?</h1> ' , ' ' , html_content )
if run_pandoc and rebuild_this_article_html or force_html_regen :
# convertir le contenu d'article org vers html
# print(f"\033[91mBRRRRRRRRRRRRR pandoc time {time.strftime('%H:%M:%S')} : Conversion de {file_name} en html\033[0m")
print ( f " \033 [91m. \033 [0m " , end = ' ' , flush = True )
html_content = pypandoc . convert_text ( content_without_h1 , ' html ' , format = ' org ' )
pandoc_runs_counter + = 1
else :
html_content = content_without_h1
if run_gemini and rebuild_this_article_gemini :
2025-02-27 19:34:49 +01:00
print ( ' -----------on régénère le gemini ' )
2025-02-27 16:18:47 +01:00
# convertir le contenu d'article org vers gmi pour la capsule gemini
2025-02-27 19:34:49 +01:00
gemini_content = org_to_gmi ( content_without_h1 )
print ( ' len(gemini_content) ' , len ( gemini_content ) )
else :
print ( ' -----------on ne régénère pas le gemini ' )
2025-02-27 16:18:47 +01:00
files_dict [ f " { annee } / { slug } " ] = {
' path ' : file_path ,
' basename ' : basename ,
' roam_id ' : find_org_roam_id ( content ) ,
' slug ' : f " { slug } / " ,
' slug_with_year ' : f " { annee } / { slug } " ,
' date ' : boom [ 0 ] ,
' lang ' : lang_folder ,
' article_type ' : article_type ,
' date_modified ' : date_modified ,
' first_picture_url ' : get_first_picture_url ( content ) ,
' date_formattee ' : datetime . strptime ( date_str , ' % Y % m %d % H % M % S ' ) . strftime ( ' %d % B % Y à % H: % M: % S ' ) if len ( date_str ) == 14 else datetime . strptime ( date_str , ' % Y % m %d T % H % M % S ' ) . strftime ( ' %d % B % Y à % H: % M: % S ' ) if len ( date_str ) == 15 else datetime . strptime ( date_str , ' % Y- % m- %d ' ) . strftime ( ' %d % B % Y ' ) ,
' annee ' : annee ,
' tags ' : tags ,
' title ' : title ,
' next ' : None ,
' previous ' : None ,
' last_html_build ' : last_html_build_time ,
' last_gemini_build ' : last_gemini_build ,
' org_content ' : content , # Contenu Org original
' html_content_without_h1 ' : html_content_without_h1 , # Contenu HTML converti sans le titre de premier niveau
' html_content ' : html_content , # Contenu first_picture_urlHTML converti
' gemini_content ' : gemini_content , # Contenu gemini
}
2025-02-19 16:24:20 +01:00
2025-02-23 20:09:39 +01:00
print ( f " ======= Nombre d ' articles reconstruits: { rebuild_counter } " )
print ( f " ======= Nombre de runs de pandoc: { pandoc_runs_counter } " )
2025-02-19 16:24:20 +01:00
# Trier les basenames par ordre décroissant
sorted_basenames = sorted ( files_dict . keys ( ) , reverse = True )
2025-02-20 14:53:59 +01:00
print ( len ( sorted_basenames ) , ' articles trouvés ' )
2025-02-19 16:24:20 +01:00
2025-02-23 15:50:56 +01:00
template_content = get_blog_template_conf ( args . blog )
# Dictionnaire des identifiants roam qui mène à un slug d'article pour réécrire les références
articles_roam_id_to_slugs = { info [ ' roam_id ' ] : slug for slug , info in files_dict . items ( ) }
# Parcourir les articles de files_dict et ajouter une clé rewritten_roam_links_html là où un lien vers un identifiant roam est trouvé dans le html_content
2025-02-23 20:23:38 +01:00
if enable_roam_id_rewrite :
for slug , info in files_dict . items ( ) :
html_content = info [ ' html_content ' ]
rewritten_html_content = html_content
for roam_id , slug in articles_roam_id_to_slugs . items ( ) :
if roam_id is not None and isinstance ( rewritten_html_content , str ) and roam_id in rewritten_html_content :
print ( f ' { roam_id } -> { slug } ' )
rewritten_html_content = rewritten_html_content . replace ( f ' href= " # { roam_id } " ' , f ' href= " { template_content [ " NDD " ] } / { slug } " ' )
info [ ' rewritten_roam_links_html ' ] = rewritten_html_content
2025-02-23 15:50:56 +01:00
# Ajouter les infos des articles suivant et précédent dans la liste des articles
2025-02-19 16:24:20 +01:00
for i in range ( len ( sorted_basenames ) ) :
basename = sorted_basenames [ i ]
2025-02-23 15:50:56 +01:00
2025-02-19 16:24:20 +01:00
if i > 0 :
files_dict [ basename ] [ ' previous ' ] = sorted_basenames [ i - 1 ]
if i < len ( sorted_basenames ) - 1 :
files_dict [ basename ] [ ' next ' ] = sorted_basenames [ i + 1 ]
2025-02-23 15:50:56 +01:00
os . makedirs ( destination_json , exist_ok = True )
json_file = destination_json + ' /articles_info.json '
2025-02-23 20:47:50 +01:00
2025-02-27 19:34:49 +01:00
# sauver le json de tous les articles et pages
2025-02-23 20:47:50 +01:00
if pandoc_runs_counter > 0 or not os . path . exists ( json_file ) :
print ( f " \033 [91m Les articles ont changé, Génération du json { json_file } \033 [0m " )
with open ( json_file , ' w ' , encoding = ' utf-8 ' ) as json_file :
files_dict_serialized = json . dumps ( files_dict , ensure_ascii = False , indent = 4 )
json_file . write ( files_dict_serialized )
2025-02-19 16:24:20 +01:00
2025-02-19 22:39:11 +01:00
print ( f " Nombre d ' articles trouvés : { len ( sorted_basenames ) } " )
count_articles_updated = 0
2025-02-27 19:34:49 +01:00
current_year = datetime . now ( ) . year
2025-02-19 22:39:11 +01:00
for basename , info in files_dict . items ( ) :
date_str = info [ ' date ' ]
2025-02-23 17:37:53 +01:00
if date_str > f ' { current_year } 0101 ' :
2025-02-19 22:39:11 +01:00
count_articles_updated + = 1
2025-02-23 17:37:53 +01:00
print ( f " Nombre d ' articles mis à jour après le 01 01 { current_year } : { count_articles_updated } " )
2025-02-19 22:39:11 +01:00
def generate_blog_index ( json_file , template_file , output_file ) :
"""
Génère la page d ' index du blog à partir des informations JSON et d ' un template Jinja2 .
: param json_file : Chemin du fichier JSON contenant les informations des articles .
: param template_file : Chemin du fichier template Jinja2 .
: param output_file : Chemin du fichier HTML de sortie .
"""
# Charger les données JSON
with open ( json_file , ' r ' , encoding = ' utf-8 ' ) as f :
articles_info = json . load ( f )
# Trier les articles par date (ou par slug) et prendre les 10 derniers
2025-02-20 17:14:53 +01:00
sorted_articles = sorted ( articles_info . values ( ) , key = lambda x : x [ ' date ' ] , reverse = True ) [ : global_config [ ' posts_per_page ' ] ]
2025-02-19 22:39:11 +01:00
# Configurer Jinja2
env = Environment ( loader = FileSystemLoader ( ' . ' ) )
template = env . get_template ( template_file )
2025-02-20 17:14:53 +01:00
articles_others = sorted ( articles_info . values ( ) , key = lambda x : x [ ' date ' ] , reverse = True ) [ 10 : ]
2025-02-23 15:50:56 +01:00
template_content = get_blog_template_conf ( args . blog )
2025-02-19 22:39:11 +01:00
# Rendre le template avec les données
2025-02-21 23:40:35 +01:00
output_index_html = template . render (
2025-02-23 15:50:56 +01:00
template_content = template_content ,
2025-02-19 22:39:11 +01:00
articles = sorted_articles [ : global_config [ ' posts_per_page ' ] ] ,
2025-02-20 17:14:53 +01:00
articles_others = articles_others
2025-02-19 22:39:11 +01:00
)
2025-02-21 23:40:35 +01:00
gmi_list_articles = ' '
for basename , article in files_dict . items ( ) :
2025-02-27 19:34:49 +01:00
gmi_list_articles + = f " \n => { article [ ' slug_with_year ' ] } .gmi "
2025-02-23 15:50:56 +01:00
2025-02-21 23:40:35 +01:00
output_index_gmi = f """
2025-02-23 15:50:56 +01:00
# {template_content['BLOG_TITLE']}
2025-02-21 23:40:35 +01:00
== == == == == == == == == == == == == == == == == == == == == == == =
2025-02-19 22:39:11 +01:00
2025-02-23 15:50:56 +01:00
{ template_content [ ' BANNIERE_ENTETE ' ] }
Par { template_content [ ' AUTHOR ' ] }
2025-02-21 23:40:35 +01:00
Dernière mise à jour : { datetime . now ( ) }
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2025-02-23 15:50:56 +01:00
{ template_content [ ' DESCRIPTION ' ] }
2025-02-21 23:40:35 +01:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2025-02-23 15:50:56 +01:00
{ template_content [ ' SITE_ICON ' ] }
2025-02-21 23:40:35 +01:00
- - - - - - - - - - - - - - - - - - - - -
Index des { len ( files_dict . items ( ) ) } articles :
{ gmi_list_articles }
- - - - - - - - - - - - - - - - - - - - -
Pages :
= > index . gmi
"""
gmi_index_file = destination_gmi + ' index.gmi '
# Écrire le fichier de sortie en html et en gmi
with open ( output_file , ' w ' , encoding = ' utf-8 ' ) as f :
f . write ( output_index_html )
print ( f " Page d ' index générée dans { output_file } " )
2025-02-27 19:34:49 +01:00
os . makedirs ( os . path . dirname ( gmi_index_file ) , exist_ok = True )
print ( ' gmi_index_file ' , gmi_index_file )
2025-02-21 23:40:35 +01:00
with open ( gmi_index_file , ' w ' , encoding = ' utf-8 ' ) as f :
f . write ( output_index_gmi )
print ( f " Page d ' index gemini générée dans { gmi_index_file } " )
2025-02-19 22:39:11 +01:00
2025-02-23 20:47:50 +01:00
# Générer la page d'index seulement si des articles ont été convertis
# if pandoc_runs_counter > 0:
# Appel de la fonction pour générer la page d'index
generate_blog_index ( destination_json + ' /articles_info.json ' , ' templates/html/index.html.jinja ' , destination_html + ' index.html ' )
print ( f " \033 [91m index régénéré { destination_html } index.html \033 [0m " )
# else:
# print("Aucun article n'a été converti, la page d'index n'est pas régénérée")
2025-02-19 23:10:28 +01:00
def generate_article_pages ( json_file , template_file , output_dir ) :
"""
Génère les pages HTML pour chaque article à partir des informations JSON et d ' un template Jinja2.
: param json_file : Chemin du fichier JSON contenant les informations des articles .
: param template_file : Chemin du fichier template Jinja2 .
: param output_dir : Répertoire de sortie pour les fichiers HTML .
"""
2025-02-27 19:34:49 +01:00
counter_gemini = 0
2025-02-20 17:14:53 +01:00
print ( ' generate_article_pages: ouverture du json ' )
2025-02-19 23:10:28 +01:00
# Charger les données JSON
2025-02-27 19:34:49 +01:00
try :
# Charger les données JSON
with open ( json_file , ' r ' , encoding = ' utf-8 ' ) as f :
print ( ' ----------------------- yay json chargé ' )
articles_info = json . load ( f )
# Configurer Jinja2
env = Environment ( loader = FileSystemLoader ( ' . ' ) )
template = env . get_template ( template_file )
template_content = get_blog_template_conf ( args . blog )
# Générer les pages pour chaque article
for article in articles_info . values ( ) :
print ( ' article ' , article [ ' title ' ] )
if article [ ' first_picture_url ' ] :
template_content [ ' OG_IMAGE ' ] = article [ ' first_picture_url ' ]
else :
template_content [ ' OG_IMAGE ' ] = template_content [ ' SITE_ICON ' ]
output_html = template . render (
template_content = template_content ,
article = article ,
all_articles = articles_info
)
slug_to_use = article [ ' slug_with_year ' ]
# Déterminer le slug à utiliser selon le type d'article
if ' article_type ' in article and article [ ' article_type ' ] == ' article ' :
slug_to_use = article [ ' slug_with_year ' ]
else :
slug_to_use = article [ ' slug ' ]
# Construire le chemin de sortie html en fonction du slug avec l'année
output_subdir = os . path . join ( output_dir , slug_to_use )
os . makedirs ( output_subdir , exist_ok = True )
output_file = os . path . join ( output_subdir , " index.html " )
# Écrire le fichier de sortie en HTML pour un article
with open ( output_file , ' w ' , encoding = ' utf-8 ' ) as f :
f . write ( output_html )
print ( f " Génération de la page HTML pour { article [ ' title ' ] } " )
if ' gemini_content ' in article and len ( article [ ' gemini_content ' ] ) > 0 :
# Construire le chemin de sortie gmi en fonction du slug avec l'année
save_gemini_file ( args . blog , article )
counter_gemini + = 1
else :
print ( f " ----------- on ne génère pas le gemini pour { article [ ' slug ' ] } " )
print ( ' generate_article_pages: fin de génération de l index ' )
print ( f " Nombre d ' articles générés en gemini: { counter_gemini } " )
except FileNotFoundError :
print ( f " Erreur : Le fichier JSON { json_file } n ' a pas été trouvé " )
return
except json . JSONDecodeError :
print ( f " Erreur : Le fichier JSON { json_file } est mal formaté " )
return
except Exception as e :
print ( f " Erreur lors de la lecture du fichier JSON : { str ( e ) } " )
return
2025-02-19 23:10:28 +01:00
with open ( json_file , ' r ' , encoding = ' utf-8 ' ) as f :
articles_info = json . load ( f )
2025-02-23 19:46:10 +01:00
2025-02-27 19:34:49 +01:00
# if pandoc_runs_counter or run_gemini:
2025-02-19 23:10:28 +01:00
# Appel de la fonction pour générer les pages des articles
2025-02-27 19:34:49 +01:00
generate_article_pages ( destination_json + ' /articles_info.json ' , ' templates/html/article.html.jinja ' , destination_html )
2025-02-19 23:10:28 +01:00
2025-02-20 14:53:59 +01:00
# À la fin du script, calculer et afficher le temps d'exécution
execution_time = time . time ( ) - start_time
print ( f " Temps d ' exécution : { execution_time : .2f } secondes " )
2025-02-19 23:10:28 +01:00