Manipuler une archive Twitter

Et jouer avec ses données, le tout en TypeScript

Twitter, c’est bien sur le moment, mais ce n’est jamais très agréable de relire ses tweets d’il y a deux, trois, voire cinq ans (surtout ceux de quand on avait 15 ans).
Alors, on aimerait bien, à minima, revoir les dégâts (ou voir les progrès effectués, selon comment on le voit) ou les faire disparaître.

Pour cela, rien de plus simple: Twitter propose, conformément au règlement général de la protection des données, un accès à ses données personnelles, de toutes leurs natures: tweets, favoris, followings… à travers une archive.

Demander son archive

Tout commence dans les paramètres de votre compte. Depuis la page d’accueil Twitter, ouvrez le menu « plus » et sélectionnez paramètres et confidentialité.

Ensuite, dans la page sélectionnée par défaut (compte), cliquez sur le bouton « Voir mes données Twitter ».

Enfin, après saisie de votre mot de passe, vous pouvez demander vos données.

Celles-ci arriveront sur l’adresse e-mail renseignée sur votre compte, sous format ZIP.

L’attente

Alors oui, l’archive peut mettre son temps à venir.
Cependant, vous savez… l’attente c’est sacré chez Twitter (surtout chez TweetDeck), pas d’autre choix que prendre son mal en patience.

Vous en serez informé automatiquement sur votre téléphone lorsqu’elle sera prête, si le compte concerné est connecté à un de vos appareils mobiles. Revenez ici quand elle est prête !

Explorer vos données

N’y allons pas par quatre chemins: J’ai déjà réalisé un outil permettant d’explorer de A à Z votre archive Twitter ! Il est disponible sur archive-explorer.com.

Avec celui-ci, vous pouvez de manière simple explorer vos tweets, les supprimer en masse et voir un peu plus encore.

Mais si vous êtes là, c’est que vous en voulez un peu plus. Pourquoi pas automatiser certaines tâches, faire du data mining, des statistiques sur vos données ? C’est pour ça que l’on est là ici.

En construisant Archive Explorer, j’ai conçu une bibliothèque prête à l’emploi afin de lire des archives Twitter. Bonus: Elle fonctionne sur Node.js comme dans un navigateur, et est capable de lire au delà de la limite des 4 Go (trouvée dans JSZip sur navigateur) ou des 2 Go (des Buffer de Node.js). Et on voyait ensemble comment elle fonctionne ?

Commençons doucement

Je vous passe directement le lien vers le paquet: twitter-archive-reader. Le nom est plutôt équivoque !

Débutons avec une courte introduction.
Dans le monde des archives Twitter, vivait en harmonie depuis de longues années les « archives classiques », créées à la demande par les utilisateurs afin de recueillir leurs tweets et les explorer simplement. Celles-ci bénéficiaient d’une interface web intégrée avec la possibilité de rechercher et d’indexer par mois. En revanche… elles ne contenaient que les tweets. Pas de DMs, pas de favoris… Que les tweets.

En 2018, une nouvelle venue est née: L’archive RGPD (de la loi dont elle est tirée) est beaucoup plus exhaustive: Elle contient *tout*, absolument tout (sauf des données correctes pour vos retweets). Messages privés, blocks, mutes, et mêmes données servant à choisir les publicités pour vous.
Elle a également à l’intérieur toutes les images que vous avez envoyé sur le réseau social: Quand je disais qu’elle contenait tout ! Ceci explique son poids parfois démentiel (pouvant excéder les 4 Go sur certains comptes très actifs).

Maintenant que vous êtes initié à ces différences, sachez que le module twitter-archive-reader supporte les deux archives. D’ailleurs, si vous venez de télécharger la vôtre récemment, sachant que vous avez entre vos mains une archive RGPD !

Lisons notre première archive

Prêt•es ? C’est parti !
En premier, vous pouvez créer un nouveau dossier, initialiser npm, TypeScript et configurer les dossiers d’entrée/sortie du compilateur (ici src/build).
Assurez vous d’avoir Node (12+ recommandé) et TypeScript d’installé.

mkdir first-archive-reader 
cd first-archive-reader 
npm init -y 
tsc -init 
npm i twitter-archive-reader
npm i @types/node -D
mkdir src
touch src/index.ts
# Lançons le compilateur TS pour avoir notre sortie
tsc -w

Commençons par le commencement : Lisons notre archive en TypeScript !

Munissez vous de votre éditeur de code préféré, et ouvrez index.ts.
N’oubliez pas de télécharger votre archive depuis Twitter et mettez la dans un endroit accessible sur votre disque dur.

// D'abord, on importe le paquet
import TwitterArchive from 'twitter-archive-reader';
import path from 'path';

const PATH_TO_ARCHIVE = path.join(__dirname, "<Ici le chemin vers votre archive>");

// On débute par la création d'une fonction async,
// car on aura besoin d'await ici (pas encore de top-level await en Node !)
async function main() {
  // On va écrire toute la logique du script ici

  // L'archive est chargée depuis le fichier
  const archive = new TwitterArchive(PATH_TO_ARCHIVE);
  
  // Son chargement est asynchrone, 
  // on attend un peu qu'elle soit ok.
  await archive.ready();

  console.log("L'archive est prête !");

  // Utilisons {archive} maintenant
}

// On l'exécute immédiatement
main();

Ça y est ! On est fin prêt à tout découvrir, à parcourir, à débroussailler ses données.

Avant de partir à l’aventure, je tiens à rappeler que toute la documentation du paquet est sur GitHub. Ceci dit, un inventaire non exhaustif des données à votre portée :

  • archive.tweets, qui est un objet contenant l’ensemble des tweets de l’archive
  • archive.messages, qui donne accès aux DMs, triés par conversation (un peu comme sur le client officiel)
  • archive.tweets.all, qui renvoie un tableau brut des tweets de l’archive
  • archive.tweets.between(since, until), qui nous laisse lister les tweets postés dans un intervalle de temps très spécifique
  • archive.messages.all, qui est l’ensemble des conversations disponibles
  • conversation.all, qui liste l’ensemble des DMs envoyés/reçus dans cette conversation

Il faut qu’on voit les bases

Profitons en pour afficher quelques données de base sur notre archive quand elle est chargée.

// [function main()]
// Quelques statistiques sur notre archive.
// Données sur le créateur de l'archive (vous !)
const user_info = archive.info.user;

// On récupère jour/mois/année de la création
const account_creation_date = new Date(user_info.created_at);
const [day, month, year] = [
  account_creation_date.getDate(),
  account_creation_date.getMonth() + 1,
  account_creation_date.getFullYear()
];

// On affiche un peu tout !
console.log(`Mon archive contient ${archive.tweets.length} tweets.`);
console.log(`Je m'appelle ${user_info.full_name} (ou @${user_info.screen_name}).`)
console.log(`Il y a ${archive.messages.count} DMs (dans ${archive.messages.length} conversations).`);
console.log(`J'ai ${archive.followers.size} followers et je suis ${archive.followings.size} personnes.`);

console.log(`J'ai mis ${archive.favorites.length} tweets en favori depuis la création de mon compte,
  le ${day}/${month}/${year}.`);

console.log("Et je crois que c'est tout !");

Suspens… Exécutez-le chez vous, et voyez ce que ça donne !
Lancez dans un autre terminal :

node build/index.js

Chez moi, on peut voir ça :

Tout plein plein de belles données croustillantes !

En fonction de la taille de votre archive, la lecture de celle-ci (avant le « L’archive est prête ! », donc) peut être plus ou moins longue. Ceci peut prendre plusieurs secondes, c’est normal !

Bon… C’est bien beau tout ça, mais on en fait quoi de tout ce bazar ?

Pas de piste ?

Bon, j’ai ma petite idée. Si on prenait l’ensemble des messages que l’on envoyé à quelqu’un, on les mettait dans une moulinette-chaîne-de-markov, et qu’on générait des nouveaux messages ?
Du genre, quel est le style de message que vous pourriez envoyer à cette personne, en connaissant tous les autres ?

Génération procédurale des messages privés

Levons tout de suite le voile sur ce qui nous servira à générer ces-dits messages : Les chaînes de Markov.

Pour ceux qui ne les connaissent pas, petit topo très rapide : Vous prenez une phrase. Vous observez quel mot a tendance à suivre un autre. Faites ceci sur un grand ensemble de phrases et vous obtiendrez un jeu de probabilités sur les mots capables de suivre un autre.
Avec ce jeu, vous prenez un « mot de départ » et laissez les probabilités faire le reste : vous obtenez un message unique, généré à partir des autres !

Résultat de recherche d'images pour "text markov chain"
Une chaîne de Markov sur du texte (les probabilités sont indiquées dans les flèches)

Les bases étant posées, on va tenter de deviner par l’apprentissage « Qu’est-ce que j’aurais bien pu dire dans cette conversation » !

C’est l’heure de notre grand jeu : Qu’est-ce qu’il a bien pu dire ?

Afin de réaliser notre générateur, nous avons besoin d’un jeu de données. Celui-ci consiste en l’ensemble des messages postés dans une conversation spécifique, entre deux personnes : Vous-même (le propriétaire de l’archive) et un utilisateur de votre choix.
Ensuite, nous filtrerons notre résultat pour avoir uniquement vos messages dans cette conversation donnée (rappelons nous, nous voulons qu’est-ce que vous auriez bien pu dire !).

Première étape de notre parcours : récupérer notre conversation d’intérêt.
Nous allons avoir besoin de l’ID utilisateur de la personne avec qui vous avez discuté : En effet, dans les messages privés, Twitter stocke uniquement les IDs des participants, et non pas leur @ / TN.
Pour le savoir, je vous propose de taper l’@ de votre destinataire sur gettwitterid.com.

L’ID utilisateur est indiqué en premier !

Maintenant que vous avons ça sous la main, retour au code !
Tentons de récupérer la conversation qui nous intéresse, puis ses messages.

// [function main()]
// L'utilisateur qui nous intéresse
// Dans l'archive des DMs, on ne connaît pas l'@ des personnes
// dans une conversation, on a besoin de leur Twitter User ID
// Stockez le comme une chaîne de caractères, car il peut dépasser la taille maximum d'un nombre en JS
const SELECTED_USER_ID = "385363361";

// Toutes les convs "directes" (entre 2 personnes) sont dans archive.messages.directs
const all_conversations = archive.messages.directs;

// On trouve la conversation qui nous intéresse
// Ici, c'est la conversation qui contient 
// notre utilisateur cible dans ses participants
const conversation = all_conversations.find(conv => conv.participants.has(SELECTED_USER_ID));

if (!conversation) {
  console.error("Notre conversation n'existe pas !");
  return;
}

// On récupère ses messages
const messages = conversation.all;

// C'est bon !

Nous avons, à la fin de ce code, récupéré un tableau de messages privés liés à notre conversation d’intérêt. Chacun de ces messages respecte l’interface LinkedDirectMessage.

Après cela, afin de générer notre chaîne de Markov, nous allons installer un paquet qui le fait pour nous: markov-strings. Exécutez simplement la ligne suivante dans un terminal :

npm i markov-strings

Importez le paquet au début du fichier.

import MarkovString, { MarkovResult } from 'markov-strings';

Nous reprenons maintenant l’écriture de notre fonction main().
Celle-ci doit désormais rassembler chaque phrase / message dans un tableau de chaînes de caractères. Nous appellerons ce tableau corpus.
Rappelons nous aussi que Twitter encode certains caractères spéciaux en entités HTML dans le texte des messages.

// [function main()]
// Twitter encode certains caractères spéciaux en HTML, on aura besoin de les décoder.
function decodeText(text: string) {
  return text.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
}

// On initialise un tableau qui va contenir nos messages
const corpus: string[] = [];

console.log("Création du corpus");
// Pour chaque message, on extrait le texte en retirant les liens
// On décode le message, puis on split les lignes/points
// pour en extraire les phrases !
for (const msg of messages) {
  // {msg} est un DM Twitter. 
  // Il respecte l'interface LinkedDirectMessage.

  // On ne garde que les messages que le propriétaire de l'archive a envoyé
  if (msg.senderId !== archive.user.id) {
    continue;
  }
  
  // Supprime les liens
  const text = msg.text.replace(/https?:\/\/t.co\/\S+/g, '').trim();
  
  // On décode, puis on sépare par notre liste de séparateur
  const splitted = decodeText(text).split(/\n|\.|\(|\)/g)
    // On trim chaque partie...
    .map(part => part.trim())
    // ...et on retire les parties vides
    .filter(part => part);

  // On ajoute toutes les parties restantes dans le corpus
  corpus.push(...splitted);
}

Notre corpus est désormais prêt. Les phrases ont été découpées et ce jeu de données est complet pour nourrir la chaîne de Markov.

Qu’est-ce qu’il a bien pu dire ? La réponse, maintenant, chez vous

On va y arriver bientôt…
Nous sommes à deux doigts de pouvoir, de manière totalement automatique, construire une phrase qui passerait crème au milieu de vos messages privés !

// [function main()]
// On instancie notre package Markov et on lui donne le corpus
const markov = new MarkovString(corpus, { stateSize: 2 });

// On génère la chaîne (peut être long)
console.log(`Génération de la chaîne de Markov (Taille du corpus: ${corpus.length})`);
markov.buildCorpus();

// On définit plusieurs options de génération
// Nombre d'essais, et condition de génération
const markov_options = {
  maxTries: 999, // Infinity
  filter: (result: MarkovResult) => result.string.split(' ').length >= 8 && result.score > 1
  // Conditions: Au moins 8 mots et au moins 2 messages source
};

// Enfin: on écoute stdin. Chaque appui sur entrée permet de lancer ce callback !
process.stdin.on('data', () => {
  const generated = markov.generate(markov_options);
  // Vous pouvez voir les messages pris dans {generated.refs}
  console.log("Message généré : " + generated.string);
});

console.log("Appuyez sur Entrée pour générer un résultat.");

Et… fini ! Nous avons réussi à générer une chaîne de Markov depuis notre conversation.

Plus précisément, celle-ci génère un message depuis l’ensemble des DMs présents dans mon archive envoyés à l’utilisateur #385363361 (c’est très précis !).

Un petit aperçu de ce que ça donne ?
Exécutez votre code, jusqu’à arriver sur l’invite à appuyez sur Entrée.
Appuyez et… *magic* !

Bourrinez votre touche Entrée, il y a parfois des choses marrantes qui sortent !

Amusez-vous bien !

Ce n’est qu’un simple exemple de traitement de données que l’on peut réaliser avec une archive Twitter. L’avantage, c’est que le dataset étant très souvent massif, faire un peu de big data est à la portée de tout le monde grâce à cet outil.

Libre à vous de créer votre propre algorithme d’extraction ou d’analyse de vos données Twitter.

Enfin, n’oubliez pas que pour explorer votre archive simplement et sans programmation, il y a Archive Explorer @ArchivExplorer ! (oui je fais ma pub ok ?!)

Laisser un commentaire