# Auteur : Manuel DORNE (Korben)
# Date : 2023-27-04
# Version : 1.0
# Description : Générateur de contenu WordPress
# Licence : Copyright (c) Korben - Manuel Dorne - Réservé pour une utilisation non commerciale aux Patreons.

# Importation des modules requis
import json
import re
import sys
import time
from concurrent.futures import ThreadPoolExecutor
from xml.sax.saxutils import escape

import markdown
import openai
import trafilatura
import validators
from bs4 import BeautifulSoup
from openai.error import APIError, RateLimitError, Timeout
from retry import retry
from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods import posts

import var

# Définition de la clé API OpenAI
openai.api_key = var.OPENAI_API_KEY

# Définition des fonctions

# extract_content(url: str) -> str
# Extrait le contenu d'une URL donnée et le retourne sous forme de chaîne HTML.
def extract_content(url: str) -> str:
    extracted = trafilatura.extract(trafilatura.fetch_url(url), output_format='html', include_links=True)
    return extracted if extracted is not None else ''

def correct_html(html: str) -> str:
    """
    correct_html(html: str) -> str
    Prend une chaîne HTML en entrée, supprime les balises vides et retourne la chaîne HTML corrigée.
    """
    soup = BeautifulSoup(html, "lxml")
    for tag in soup.find_all():
        if not tag.contents and not tag.string:
            tag.extract()
    corrected_html = str(soup)
    return corrected_html

def collect_urls():
    """
    collect_urls() -> List[str]
    Collecte les URL d'articles à résumer auprès de l'utilisateur et les retourne sous forme de liste.
    """
    urls = []
    while True:
        url = input("Entrez l'URL de l'article à résumer (laissez vide pour terminer): ").strip()
        if not url:
            break
        if validators.url(url):
            urls.append(url)
        else:
            print("L'URL saisie n'est pas valide. Veuillez saisir une URL valide.")
    return urls

def html_to_gutenberg(html):
    """
    html_to_gutenberg(html: str) -> str
    Convertit une chaîne HTML en format Gutenberg (HTML avec des commentaires spécifiques pour le formatage des blocs Gutenberg) et retourne la chaîne convertie.
    """
    replacements = [
        (r"<h1>", "<!-- wp:heading {\"level\":1} --><h1>"),
        (r"</h1>", "</h1><!-- /wp:heading -->"),
        (r"<h2>", "<!-- wp:heading --><h2>"),
        (r"</h2>", "</h2><!-- /wp:heading -->"),
        (r"<h3>", "<!-- wp:heading {\"level\":3} --><h3>"),
        (r"</h3>", "</h3><!-- /wp:heading -->"),
        (r"<h4>", "<!-- wp:heading {\"level\":4} --><h4>"),
        (r"</h4>", "</h4><!-- /wp:heading -->"),
        (r"<h5>", "<!-- wp:heading {\"level\":5} --><h5>"),
        (r"</h5>", "</h5><!-- /wp:heading -->"),
        (r"<h6>", "<!-- wp:heading {\"level\":6} --><h6>"),
        (r"</h6>", "</h6><!-- /wp:heading -->"),
        (r"<p>", "<!-- wp:paragraph --><p>"),
        (r"</p>", "</p><!-- /wp:paragraph -->"),
        (r"<blockquote>", "<!-- wp:quote --><blockquote>"),
        (r"</blockquote>", "</blockquote><!-- /wp:quote -->"),
        (r"<ul>", "<!-- wp:list --><ul>"),
        (r"</ul>", "</ul><!-- /wp:list -->"),
        (r"<ol>", "<!-- wp:list {\"ordered\":true} --><ol>"),
        (r"</ol>", "</ol><!-- /wp:list -->"),
        (r"<table>", "<!-- wp:table --><figure class=\"wp-block-table\"><table>"),
        (r"</table>", "</table></figure><!-- /wp:table -->")
    ]

    gutenberg_html = html

    # Remplacer les balises img en utilisant une expression régulière
    img_pattern = re.compile(r'<img (.*?)\/>')
    gutenberg_html = img_pattern.sub(r'<!-- wp:image --><img \1 /><!-- /wp:image -->', gutenberg_html)

    # Effectuer d'autres remplacements
    for search_str, replace_str in replacements:
        gutenberg_html = re.sub(search_str, replace_str, gutenberg_html)

    return gutenberg_html

def extract_xml_from_text(text):
    """
    extract_xml_from_text(text: str) -> str
    Extrait la chaîne XML d'un texte et la retourne.
    """
    xml = re.search(r'<blog>.*</blog>', text, re.DOTALL)
    return xml

@retry(APIError, tries=9, delay=15, backoff=2)
def openai_chat(chat_messages, model=var.OPENAI_GENERATION_MODEL, temperature=1, topp=0.9, frequency_penalty=0, presence_penalty=0):
    """
    openai_chat(chat_messages: List[Dict[str, str]], model: str, temperature: float, frequency_penalty: float, presence_penalty: float) -> str
    Envoie une requête à l'API OpenAI avec les messages donnés, le modèle, la température, la pénalité de fréquence et la pénalité de présence. Retourne le contenu du message de réponse.
    """
    print("Envoi de la requête à l'API OpenAI...")
    try:
        response = openai.ChatCompletion.create(
            model=model,
            temperature=temperature,
            top_p=topp,
            frequency_penalty=frequency_penalty,
            presence_penalty=presence_penalty,
            messages=chat_messages
        )

        return response.choices[0].message.content

    except Timeout as e:
        print(f"Erreur : {e}. Nouvelle tentative...")
    except RateLimitError as e:
        print(f"Erreur : {e}. Attente avant de réessayer...")
        wait_time = int(e.headers.get('Retry-After', 60))
        time.sleep(wait_time)

def generate_summary(text):
    """
    generate_summary(text: str) -> str
    Génère un résumé en français du texte donné sous forme de chaîne HTML et le retourne.
    """
    chat_messages = [
        {"role": "system", "content": "Tu es une IA spécialisée dans la rédaction de résumés."},
        {"role": "assistant", "content": "Compris, je suis une IA spécialisée dans la rédaction de résumés."},
        {"role": "user", "content": "Condense le texte en entrée en préservant les informations et détails essentiels."},
        {"role": "assistant", "content": "Je vais maintenant résumer le texte fourni en français, en préservant les liens existants, et le formater en tant que paragraphe HTML."},
    ]

    summary = openai_chat(
        chat_messages=chat_messages,
        model=var.OPENAI_GENERATION_MODEL2,
        temperature=1,
        topp=0.9,
    )
    #print(summary)
    return summary

def generate_content(full_text, article_url):
    chat_messages = [
        {"role": "system", "content": "Comporte-toi comme Toto, rédacteur people pour le magazine Closer pour rédiger un article de presse."},
        {"role": "assistant", "content": "Compris. Je vais rédiger un article de presse dans le style de Toto de Closer. Je m'assurerai d'utiliser un ton solennel."},
        {"role": "user", "content": f"TEXTE : {full_text}"},
        {"role": "assistant", "content": "D'accord. J'utiliserai le texte fourni comme base pour l'article de presse."},
        {"role": "user", "content": f"URL : {article_url}"},
        {"role": "assistant", "content": "Ok. L'URL sera incluse dans l'article de presse."},
        {"role": "user", "content": "Voici le contenu de l'article de blog:"},
    ]


    # Ajoutez ici la logique pour générer le contenu à partir du texte
    content = openai_chat(
            chat_messages=chat_messages,
            model=var.OPENAI_GENERATION_MODEL,
            temperature=0.8,
            topp=0.95,
        )
    return content


def generate_title(text: str) -> str:
    # Ajoutez ici la logique pour générer le titre à partir du texte
    chat_messages = [
            {"role": "user", "content": "Génère un titre pour l'article."},
            {"role": "assistant", "content": "Compris."},
            {"role": "user", "content": "Voici le titre:"},
        ]
    title = openai_chat(
            chat_messages=chat_messages,
            model=var.OPENAI_GENERATION_MODEL,
            temperature=1,
            topp=1,
        )
    return title

def generate_keywords(text: str):
    # Ajoutez ici la logique pour générer les mots clés à partir du texte
    chat_messages = [
        {"role": "user", "content": "Génère une liste de mots-clés pour l'article."},
        {"role": "assistant", "content": "Compris."},
        {"role": "user", "content": "Voici la liste des 5 mots-clés:"},
    ]
    keywords = openai_chat(
            chat_messages=chat_messages,
            model=var.OPENAI_GENERATION_MODEL,
        )
    return keywords


def generate_slug(text: str) -> str:
    # Ajoutez ici la logique pour générer le slug à partir du titre
    chat_messages = [
        {"role": "user", "content": "Génère un slug."},
        {"role": "assistant", "content": "Compris."},
        {"role": "user", "content": "Voici le slug:"},
    ]

    slug = openai_chat(
            chat_messages=chat_messages,
            model=var.OPENAI_GENERATION_MODEL,
        )
    return slug

def generate_summary(text: str) -> str:
    # Ajoutez ici la logique pour générer le résumé à partir du texte
    chat_messages = [
            {"role": "user", "content": f"Résume en français ce texte : \n texte : ''' \n {text} \n '''"},
            {"role": "assistant", "content": "Voici le résumé:"},
        ]

    summary = openai_chat(
            chat_messages=chat_messages,
            model=var.OPENAI_GENERATION_MODEL,
        )
    return summary

def generate_blog_post(content_chunks, article_url):
    """
    generate_blog_post(content_chunks: List[str], article_url: str) -> Dict[str, str]
    Génère un article de blog en français au format XML basé sur les résumés de contenu et l'URL de l'article source. Retourne un dictionnaire contenant les clés 'title', 'slug', 'content' et 'hashtags'.
    """
    article_data = {}
    # Générer des résumés pour chaque morceau de contenu
    with ThreadPoolExecutor(max_workers=4) as executor:
        summaries = list(executor.map(generate_summary, content_chunks))

    summaries = [summary for summary in summaries if summary is not None]

    full_text = "\n".join(summaries)
    print(article_url)
    print("Génération de l'article...")
    content = generate_content(full_text, article_url)
    title = generate_title(content)
    hashtags = generate_keywords(content)
    slug =  generate_slug(content)
    summary = generate_summary(content)
    # Extraire les données de l'article à partir du JSON
    article_data = {
                        'title': title,
                        'slug': slug,
                        'hashtags': hashtags,
                        'content': content,
                        'summary': summary
                    }
    
    # Convertir le markdown en HTML
    article_data['content'] = markdown.markdown(article_data['content'], output_format='html5')

    print(f"Élément de titre: {article_data['title']}")
    print(f"Élément de slug: {article_data['slug']}")
    print(f"Élément de hashtags: {article_data['hashtags']}")
    print(f"Élément de contenu: {article_data['content']}")
    print(f"Élément de résumé: {article_data['summary']}")
    print("JSON valide trouvé")
    return article_data

def generate_blog_post_from_text(content_chunks, article_url):
    # Ouvrir le fichier openai.txt et lire son contenu dans la variable response_text
    with open('openai.txt', 'r') as file:
        response_text = file.read()

    print(f"Réponse du modèle IA: {response_text}")
    
    # Extraire le JSON de la réponse
    print("Contenu de response_text:")
    #print(response_text)
    json_pattern = r'({\s*\"[\s\S]*?\"\s*:)[\s\S]*?}(?![^\n]*\})'
    json_match = re.search(json_pattern, response_text, re.DOTALL | re.MULTILINE)
    if json_match:
        json_string = json_match.group(0).strip()
        data = json.loads(json_string, strict=False)
    else:
        raise ValueError("Aucune chaîne JSON trouvée dans response_text")

    #print(data)

    # Extraire les données de l'article à partir du JSON
    article_data = {
        'title': data['title'],
        'slug': data['slug'],
        'hashtags': data['hashtags'],
        'content': data['content']
    }
    
    # Convertir le markdown en HTML
    article_data['content'] = markdown.markdown(article_data['content'], output_format='html5')

    print(f"Élément de titre: {article_data['title']}")
    print(f"Élément de slug: {article_data['slug']}")
    print(f"Élément de hashtags: {article_data['hashtags']}")
    print(f"Élément de contenu: {article_data['content']}")
    print("JSON valide trouvé")
    return article_data

def create_wordpress_draft(title, slug, content_html, summary, hashtags):
    """
    create_wordpress_draft(title: str, slug: str, content_html: str, hashtags: str) -> None
    Crée un brouillon de publication WordPress avec le titre, le slug, le contenu HTML et les hashtags donnés.
    """
    if content_html is None:    
        print("Erreur : le contenu HTML est vide. Brouillon non créé.")
        return
    print("Publication du brouillon sur WordPress...")
    wp = Client(var.WORDPRESS_URL, var.WORDPRESS_USERNAME, var.WORDPRESS_PASSWORD)
    post = WordPressPost()
    post.title = title
    post.content = content_html
    post.post_status = 'draft'
    post.slug = slug
    post.excerpt = summary
    
    # Diviser les hashtags et créer une liste
    hashtags_list = hashtags.split(',')

    # Utiliser 'post_tag' au lieu de 'hashtags'
    post.terms_names = {'post_tag': hashtags_list}

    try:
        post_id = wp.call(posts.NewPost(post))
        print(f'Brouillon de publication WordPress créé avec succès avec l\'ID : {post_id}')
    except Exception as e:
        print(f"Erreur lors de la création du brouillon WordPress : {e}")

def mix_all_content(all_content):
    chat_messages = [
        {"role": "system", "content": "Tu es ChatGPT, un modèle de langage de grande taille entraîné par OpenAI. Tu peux créer un texte unique et cohérent en combinant plusieurs morceaux de contenu."},
        {"role": "user", "content": f"Crée-moi un article à partir de ce contenu : \n\n {all_content} \n\n Article :"},
    ]

    mixed_content = openai_chat(
        chat_messages=chat_messages,
        model=var.OPENAI_GENERATION_MODEL,
        temperature=0.8,
        topp=0.95,
        frequency_penalty=0.0,
        presence_penalty=0.0
    )
    return mixed_content



def main():
    """
    main() -> None
    Fonction principale qui gère l'extraction du contenu de l'article, la génération de l'article de blog et la création du brouillon WordPress.
    """
    urls = sys.argv[1:] if len(sys.argv) > 1 else collect_urls()
    print("Extraction du contenu de l'article...")
    all_content = "\n".join([f"URL : {url}\nCONTENU : {extract_content(url)}\n----------" for url in urls])
    print("===========CONTENU BRUT=================")
    print(all_content)
    print("===========CONTENU REMIXé==================")
    #all_content = mix_all_content(all_content)
    print(f"Contenu total: {all_content}")
    print(f"Contenu extrait avec succès ! Taille du contenu : {len(all_content)} caractères.")
    print("Génération de l'article...")
    content_chunks = [all_content[i:i+2000] for i in range(0, len(all_content), 2000)]
    print(f"Nombre de morceaux : {len(content_chunks)}")
    print("Génération de l'article XML...")
    if urls:
        article_xml = generate_blog_post(content_chunks, urls[0])
    else:
        print("Aucune URL n'a été fournie. Fin du programme.")
        return
    #article_xml = generate_blog_post_from_text(content_chunks, urls[0])
    print("Création du brouillon WordPress...")
    if article_xml is not None:
        content_html = html_to_gutenberg(article_xml['content'])
        create_wordpress_draft(article_xml['title'], article_xml['slug'], content_html, article_xml['summary'], article_xml['hashtags'])

if __name__ == "__main__":
    main()


