Skip to content

imessage_tools

imessage_tools

« Lis ma conversation avec Maman » — chat.db, en lecture, honnêtement.

Atlas, grand chantier n°1, 25 août 2026. imessage_get_conversation était déclaré depuis des mois sans exécuteur. L'outil vit ici : données strictement locales (chat.db en lecture seule), le petit nom résolu par le même résolveur que messages_send, et les trois issues explicites de la maison — trouvé, rien, ou illisible-avec-le-remède (l'Accès complet au disque manque encore sur cette machine : l'outil l'avoue, il ne se tait pas).

Classes

IMessageConversationTool

Bases: BaseTool

La conversation Messages avec quelqu'un, lue dans chat.db.

Functions:

lire_conversation

lire_conversation(
    identifiant: str,
    *,
    limite: int = 30,
    db_path: str = _DB_PAR_DEFAUT,
) -> dict

Les derniers messages avec un correspondant — issues explicites.

{"issue": "unreadable"|"not_found"|"found", "messages": [...], "sans_texte": n}. Les rangées au texte NULL (attributedBody des macOS récents) sont comptées, pas inventées.

Source code in src/diapason/tools/imessage_tools.py
def lire_conversation(
    identifiant: str, *, limite: int = 30, db_path: str = _DB_PAR_DEFAUT
) -> dict:
    """Les derniers messages avec un correspondant — issues explicites.

    {"issue": "unreadable"|"not_found"|"found", "messages": [...],
    "sans_texte": n}. Les rangées au texte NULL (attributedBody des macOS
    récents) sont comptées, pas inventées.
    """
    try:
        conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
    except sqlite3.OperationalError as exc:
        return {"issue": "unreadable", "detail": str(exc), "messages": []}
    try:
        lignes = conn.execute(
            "SELECT m.text, m.is_from_me, m.date "
            "FROM message m "
            "JOIN chat_message_join cmj ON cmj.message_id = m.ROWID "
            "JOIN chat c ON c.ROWID = cmj.chat_id "
            "WHERE c.chat_identifier = ? "
            "ORDER BY m.date DESC LIMIT ?",
            (identifiant.strip(), max(1, limite)),
        ).fetchall()
    except sqlite3.OperationalError as exc:
        # TCC a deux portes : l'ouverture peut réussir et la PREMIÈRE
        # lecture échouer (leçon du connecteur imessage).
        return {"issue": "unreadable", "detail": str(exc), "messages": []}
    finally:
        conn.close()
    if not lignes:
        return {"issue": "not_found", "messages": [], "sans_texte": 0}
    from diapason.connectors.imessage import _apple_ts_to_datetime

    messages = []
    sans_texte = 0
    for texte, de_moi, date_ns in reversed(lignes):
        if not texte:
            sans_texte += 1
            continue
        messages.append(
            {
                "texte": texte,
                "de_moi": bool(de_moi),
                "quand": _apple_ts_to_datetime(int(date_ns or 0)).isoformat(
                    timespec="minutes"
                ),
            }
        )
    return {"issue": "found", "messages": messages, "sans_texte": sans_texte}