ChatGPT als Markdown exportieren

Schmuckbild, drei Pesronen an einem großen Schreibtsich mit vielen Bildschirmen. Erstellt mit ChatGPT

Achtung:

Ich habe das Script wei­ter ergänzt, da im Original nur ‘/’ und ‘„ ‘ als Zeichen für den Titel her­aus­ge­fil­tert wur­den. Nun fil­tert das Skript auch ‘\’, ‘:’, ‘’’, ‘?’, ‘<’, ‘>’ und ‘|’, die eben­falls in Dateinamen auf ver­schie­de­nen Plattformen Probleme berei­ten.

14.02.2024

Dass ich ChatGPT nut­ze, habe ich schon häu­fi­ger erwähnt. Außerdem bin ich ein Freund der loka­len Datenspeicherung. Was läge also näher, als mei­ne Interaktionen mit ChatGPT auch lokal zu spei­chern und zugäng­lich zu machen? Da ich zudem ein Obsidian-Nutzer bin, bie­tet es sich an, alle Chats als Markdown in mei­nem Obsidian-Vault zu spei­chern.

Nach etwas Recherche stieß ich auf ein Skript von Gavi Narra, das Chat-Verläufe in Markdown-Dateien umwan­delt. Dieses Skript, das unter der MIT-Lizenz ver­füg­bar ist, habe ich leicht modi­fi­ziert, um es an mei­ne spe­zi­fi­schen Bedürfnisse anzu­pas­sen. Selbstverständlich steht das modi­fi­zier­te Ergebnis eben­falls unter der MIT-Lizenz.

Um das Skript zu ver­wen­den, muss zuerst der Chat-Verlauf von ChatGPT expor­tiert wer­den. Folgen Sie die­sen Schritten:

  1. Die Einstellungen von ChatGPT öff­nen und zum Abschnitt „Datenkontrollen“ navi­gie­ren .
  2. Auf „Daten expor­tie­ren“ kli­cken und dann auf den „Exportieren“-Knopf.
  3. Nach eini­ger Zeit erhält man eine E‑Mail mit einem Download-Link. Über die­sen Link kön­nen Sie eine ZIP-Datei her­un­ter­la­den und ent­pa­cken, die die u.a. benö­tig­te Datei „conversations.json“ ent­hält. Diese JSON-Datei hat eine kom­ple­xe Struktur, die alle Konversationsdaten ent­hält.

Das Python-Skript von Gavi benö­tigt den Pfad zur der „conversations.json“-Datei, den Pfad zu einem Ordner für die zu spei­chern­den Markdown-Dateien und einen optio­na­len Parameter. Dieser Parameter ermög­licht es, die Dateien in Unterordnern zu spei­chern, die nach dem Datum der Chats benannt sind.

Der Befehl zum Ausführen des Skriptes sieht wie folgt aus:

python3 convert.py [Eingabe-datei] [Ausgabe-Ordner] [--use-date-folders]
# Ohne datumsbasierte Ordner
python3 convert.py /pfad/conversations.json /pfad/Augabe-Ordner

# Mit datumsbasierte Ordner
python3 convert.py /pfad/conversations.json /pfad/Augabe-Ordner --use-date-folders 

Ich habe an der grund­le­gen­den Funktion des Skriptes nichts geän­dert. Die vor­ge­nom­me­nen Änderungen sind wie folgt im Skript mar­kiert:

  • Frontmatter-Ergänzung: Das Markup wird nun durch ein Frontmatter ergänzt, das ein Tag mit dem Namen „AI-Chat“ defi­niert.
  • Datum des Chats: Als wei­te­re Eigenschaft füge ich das Datum des Chats im Frontmatter hin­zu.
  • Footer mit Dataview-Statement: Am Ende der Datei füge ich einen Footer ein. Dieser ent­hält ein Dataview-Statement, das anzeigt, wo die Datei abge­legt ist.
  • Löschen wei­te­rer Zeichen im Titel: Ausser ‘/’ und ‘„ ‘ wer­den nun auch die Zeichen ‘\\’, ‘:’, ‘’’, ‘?’, ‘<’, ‘>’, und ‘|’ aus dem Titel gelöscht. Diese Zeichen kön­nen in Dateinamen Probleme machen.

Nachfolgend fin­den Sie mei­ne ange­pass­te Version des Skriptes. Der Aufruf bleibt gleich wie beim Originalskript:

import json
import os
import argparse
from datetime import datetime

# This project is open source and available under MIT by https://github.com/gavi/chatgpt-markdown
# All my marked changes are still under MIT


def get_conversation(node_id, mapping, list):
    node = mapping[node_id]
    if node.get('message') and 'content' in node['message'] and 'parts' in node['message']['content']:
        content_parts = node['message']['content']['parts']
        parts_text = []
        for part in content_parts:
            if isinstance(part, str):
                parts_text.append(part)
            elif isinstance(part, dict):
                # Handle the dictionary content here
                # For example, you might want to convert it to a string or extract specific information
                parts_text.append(str(part))  # Simple example: convert the dictionary to a string
        if parts_text:
            author_role = node['message']['author']['role']
            if author_role == "user":
                author_role = "Me:"
            elif author_role == "assistant":
                author_role = "ChatGPT:"
            list.append(f"## {author_role}\n {''.join(parts_text)}")

    for child_id in node.get('children', []):
        get_conversation(child_id, mapping, list)



def main(input_file, output_dir, use_date_folders):

    # Check if the directory exists
    if not os.path.isdir(output_dir):
        os.makedirs(output_dir)

    with open(input_file) as f:
        data = json.loads(f.read())
        for item in data:
            # Änderung: Weitere Zeichen hinzugefügt, die in Dateinamen Probleme machen könnten.
            title = item["title"].replace("/","_").replace('"','_').replace(':','_').replace('?','_').replace('|','_').replace('\\','_').replace('<','_').replace('>','_')
            if title == "New chat":
                title = "New chat " + str(int(item["create_time"]))
            root_node_id = [node_id for node_id, node in item['mapping'].items() if node['parent'] is None][0]
            list = []
            get_conversation(root_node_id, item['mapping'], list)


            date_folder = ''

            # Änderungen:
            # 1. date_iso definition aus dem if use_data_folder herausgeholt
            # 2. definition von fixed_text_start & fixed_text_end hinzugefügt 

            date_iso = datetime.fromtimestamp(item["create_time"]).date().isoformat()
            fixed_text_start = [f"---\ntags:\n- AI-chat\n\ntalk-date: {date_iso}\n---"]
            fixed_text_end = ["---\n", "Ordner: `=this.file.folder`"]

            # Handle the date-based folder structure
            if use_date_folders:
                # Convert create_time to datetime and then to ISO format
                date_folder = f"{output_dir}/{date_iso}"
                if not os.path.isdir(date_folder):
                    os.makedirs(date_folder)


            with open(f'{date_folder if use_date_folders else output_dir}/{title}.md', 'w') as outfile:
                # Änderung:
                # 1. Hinzufügen des Textes aus fixed_text_start am Anfang der Datei
                # 2. Hinzufügrn der Textes aus fixed_text_end am Ende der Datei 
                for line in fixed_text_start:
                    outfile.write(line + '\n')
                outfile.write('\n')  # Optional: Add an extra newline for separation

                # Existing content writing logic
                outfile.write('\n'.join(list) + '\n')  # Ensure there's a newline after the main content

                # Append fixed text at the end
                for line in fixed_text_end:
                    outfile.write('\n' + line)  # Ensure each end line starts on a new line

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Process conversation data.')
    parser.add_argument('input_file', help='JSON file containing conversations')
    parser.add_argument('output_dir', help='Directory to save output Markdown files')
    parser.add_argument('--use-date-folders', action='store_true', help='Store files under date-based folders')

    args = parser.parse_args()
    main(args.input_file, args.output_dir, args.use_date_folders)

Das ist alles zum Thema die­ses Skriptes. Zusätzlich habe ich in Chrome die Erweiterung One-Click Export ChatGPT Chat Record As Markdown Text – ChatGPT Pro instal­liert, wel­che es ermög­licht, ein Markdown des akti­ven Chats direkt aus der ChatGPT-Oberfläche zu gene­rie­ren.

Ich möch­te mich bei Gavi für die Bereitstellung sei­nes Skriptes bedan­ken. Ich hof­fe, dass die­ser Artikel hilf­reich für Sie ist. Ihr Feedback ist sehr will­kom­men – ich freue mich über jeg­li­che Anmerkungen und Vorschläge, solan­ge es kein SPAM ist. Teilen Sie Ihre Gedanken ger­ne in den Kommentaren mit.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert