Vue lecture

Il y a de nouveaux articles disponibles, cliquez pour rafraîchir la page.

Les incidents critiques en net recul au fil des ans selon les experts Kaspersky

D’après le rapport « Anatomy of a Cyber World: Global Report » (Anatomie d’un cybermonde) de Kaspersky Security Services, le pourcentage d’incidents critiques a fortement reculé ces dernières années. Alors que l’année 2021 a enregistré la proportion la plus élevée avec 14,3 %, l’année 2025 a connu le taux le plus bas en six ans, […]

The post Les incidents critiques en net recul au fil des ans selon les experts Kaspersky first appeared on UnderNews.

100 000 postes à pourvoir d’ici 2030 : la cybersécurité, un enjeu majeur pour la souveraineté numérique française [Sponso]

Cet article a été réalisé en collaboration avec École Polytechnique Executive Education

Une formation diplômante de niveau Bac +5, proposée par l'École polytechnique, permet de faire évoluer sa carrière et de consolider ses connaissances et compétences en matière de cybersécurité.

Cet article a été réalisé en collaboration avec École Polytechnique Executive Education

Il s’agit d’un contenu créé par des rédacteurs indépendants au sein de l’entité Humanoid xp. L’équipe éditoriale de Numerama n’a pas participé à sa création. Nous nous engageons auprès de nos lecteurs pour que ces contenus soient intéressants, qualitatifs et correspondent à leurs intérêts.

En savoir plus

Razer's new Blade 16 drops AMD Ryzen for Intel Panther Lake — Brighter OLED display, faster RAM, and Thunderbolt 5 make it better than ever for mixed use

Razer has taken the wraps off its Blade 16 for 2026, and it's a significant refresh that now builds on Intel's Core Ultra Series 3 "Panther Lake" chips. Here's what you need to know (and where you can buy it today).

Permanently delete emails in Exchange Online with Priority Cleanup V2

Permanent email deletion with CleanUp V2 in Exchange Online
Microsoft is developing Priority Cleanup V2, an updated data-purging feature in Microsoft Purview that deletes Exchange Online mailbox content even when protected by retention policies or eDiscovery holds. This proposed version is designed to reduce the number of approval stages from three to two, accelerate deletion from days to hours, and introduce batch-processing safeguards.

Source

What are MCP extensions?

An MCP app in Claude
The Model Context Protocol (MCP) is an open-source standard for connecting AI agents to external resources such as database systems. MCP extensions are modular, optional additions to the Model Context Protocol that enable specialized capabilities without modifying the core specification. These extensions support features like interactive user interfaces, custom authentication flows, and industry-specific functionality while maintaining compatibility with the base protocol.

Source

Comment j'ai rendu mon serveur Plex surpuissant et silencieux grâce à un Mac Mini et des SSD Lexar

– Ccontient des liens affiliés Amazon –

Après des années de galère avec un NAS bruyant puis un miniPC pas beaucoup mieux, j'ai fini par trouver la configuration Plex idéale. Un Mac Mini M4 , deux SSD Lexar SL500 , et le silence absolu. Retour d'expérience.

Le bruit, l'ennemi numéro un

J'ai un serveur Plex depuis des années. Un serveur que je partage avec ma famille et mes amis les plus proches, et qui me sert à stocker des films et des séries souvent introuvables sur les plateformes légales, ou des versions numérisées de DVD et Blu-Ray que j'ai achetés, mais que je veux pouvoir streamer sur mon Apple TV. Vous voyez l'idée. Pendant longtemps, tout ça tournait sur un NAS Synology d'entrée de gamme. Ça marchait, mais dès que je voulais transcoder un film pour le regarder à distance, c'était mort. Lecture directe obligatoire, avec les problèmes de débit que ça implique, surtout à l'époque où j'étais encore en ADSL. Il y a trois ou quatre ans, j'ai décidé de monter d'un cran en déportant le serveur Plex sur un miniPC Beelink. Plus de puissance, transcodage enfin possible, bien pratique pour moi à distance ou pour mes proches qui n'ont pas forcément la fibre.

Sauf que toutes ces solutions avaient le même défaut. Le bruit. Entre les disques durs mécaniques, le ventilateur du NAS Synology qui ronronnait en permanence et celui du miniPC Beelink qui se mettait à souffler dès qu'on lui demandait un peu d'effort, c'était toujours pénible. Et comme je n'ai jamais eu la place de planquer tout ça dans un bureau ou un placard technique, le serveur a toujours tourné dans mon salon. Autant dire que les soirs de film, l'ambiance était moyennement au rendez-vous.

Le Mac Mini M4, une bête silencieuse

Et puis il y a quelques semaines, j'ai tout changé. Adios le miniPC, filé à un ami, et place au Mac Mini M4. Ce petit machin tout mignon, complètement silencieux, est une vraie bête de course pour Plex. On parle de quatre à cinq transcodages simultanés sans broncher, avec une sollicitation processeur qui reste sous les 3 à 4%. C'est presque absurde. Le tout en restant frais, sans ventilateur qui se déclenche, sans bruit parasite. Rien. Le silence total.

Pour l'administration, pas besoin d'écran ni de clavier. Tout se fait à distance via l'application Partage d'écran de macOS. Le Mac Mini est branché directement sur ma Livebox, et ça tourne comme une horloge. Et comme bonus, ça me fait un second Mac pour faire des tests quand j'en ai besoin. Pas mal pour une machine qui fait à peine la taille d'une main.

Les SSD Lexar SL500 en remplacement du NAS

Pour compléter le tableau, j'ai déplacé mes données les plus consultées, les films et les séries que ma famille et moi regardons le plus souvent, sur deux SSD SL500 de chez Lexar. Et là, c'est le coup de grâce pour le bruit. Non seulement les ventilateurs ont disparu avec le Mac Mini, mais les vibrations et le ronronnement des disques mécaniques du NAS aussi. Le silence est total. J'ai quand même gardé un NAS Synology en arrière-plan pour stocker les données froides, mon Time Machine et les films que personne ne regarde jamais. Il reste accessible à Plex au cas où, mais il est si peu sollicité qu'on l'entend à peine.

Le résultat, c'est une configuration compacte, silencieuse, et qui gère sans effort tout ce que je lui demande. Le Mac Mini fait tourner Plex comme si de rien n'était, les SSD Lexar offrent des temps d'accès instantanés, et le NAS se contente de dormir dans son coin.

Franchement, si vous êtes du genre à soigner votre setup multimédia à la maison, ce genre de configuration change la vie. Ça a un coût, on ne va pas se mentir, un Mac Mini M4 plus deux SSD externes ce n'est pas donné. Mais le confort au quotidien est incomparable. Plus de bruit, des performances de dingue pour le transcodage, et une machine qui ne chauffe même pas. Si vous avez la possibilité de basculer votre serveur Plex sur un Mac Mini, n'hésitez pas trop longtemps. Moi en tout cas, je ne reviendrais pas en arrière.

Si vous voulez vous équiper, voilà ma config :

Quand 10 000 bots volent 8 millions aux artistes sur Spotify

Un mec de 54 ans vient de plaider coupable pour avoir siphonné 8 millions de dollars aux artistes musicaux en utilisant 10 000 bots et de la musique générée par IA. Michael Smith, résident de Cornelius en Caroline du Nord, a monté pendant des années une ferme à streams qui écoutait en boucle des centaines de milliers de fausses chansons sur Spotify et Apple Music.

Le truc, c'est que ces plateformes ne paient pas un tarif fixe par écoute. Elles fonctionnent avec un pot commun mensuel qu'elles redistribuent proportionnellement au nombre de streams. Du coup, chaque fausse écoute générée par les bots de Smith grignotait directement la part des vrais artistes. En gros, c'est pas Spotify qui se faisait voler, c'est les musiciens qui galèrent déjà à vivre de leur art !

Pour le contenu, Smith avait en fait trouvé un deal avec le CEO d'une boîte de musique IA qui lui pondait des milliers de morceaux par semaine. Les fichiers WAV arrivaient sous forme de chaînes aléatoires de lettres et de chiffres, et il les renommait avec des noms d'artistes fictifs du genre "Calorie Event", "Calms Scorching" ou encore "Calypso Xored" (on sent le générateur de noms random). Les titres, pareil... "Zygotes", "Zyme Bedewing"... si vous tombez là-dessus dans votre discover, y'a de quoi tiquer quand même mais bon...

Et ce problème, ça pose une question que Spotify connaît bien : comment distinguer les vrais streams des faux quand les bots sont suffisamment dispersés sur des milliers de morceaux ? Smith avait justement calibré ses 10 000 bots pour ne pas déclencher les alertes anti-fraude, en répartissant les écoutes sur un catalogue énorme plutôt que de matraquer un seul titre. Pas con.

Mais le bonhomme s'est quand même fait choper. Il a accepté de rendre la totalité des 8 091 843 dollars et risque jusqu'à 5 ans de prison lors de son procès qui aura lieu le 29 juillet prochain. Pas sûr que le ratio risque/récompense en valait la chandelle, en fait.

Le problème de fond, c'est que cette affaire n'est probablement que la partie émergée de l'iceberg. Et je suis sûr que y'en a en France qui font la même... bah sachez que c'est pas cool et que vous risquez d'avoir de GROS ennuis... Avec les outils de génération musicale par IA qui se démocratisent, n'importe qui peut inonder les plateformes de contenu synthétique pour gratter des royalties.

Et tant que le modèle de rémunération repose sur un pot commun plutôt que sur un paiement direct par utilisateur, il sera vulnérable. Encore une fois, les vrais perdants, c'est pas les plateformes (elles prennent leur commission quoi qu'il arrive), mais ce sont les artistes indépendants qui voient leur part du gâteau fondre à chaque bot supplémentaire.

Moche...

Bref, la prochaine fois que votre playlist de découvertes vous propose un artiste nommé "Calypso Xored" ou un connerie de ce style... méfiance !

Source

Reverse-SynthID - Le filigrane de Gemini mis à nu

SynthID, le filigrane invisible que Google injecte dans chaque image Gemini, c'était censé être incassable. Sauf qu'un dev a eu l'idée toute bête de générer des images noires et blanches avec Gemini, puis de regarder ce qui restait dans le domaine fréquentiel. Et là, surprise... le watermark est apparu en clair avec toutes ses fréquences porteuses !

Le projet reverse-SynthID documente le truc de A à Z où on comprend en gros, que le marquage IA de Google fonctionne en injectant de l'énergie à des fréquences bien précises dans le spectre de l'image via une transformation de Fourier . Le chercheur a identifié 6 fréquences porteuses principales, toutes avec une cohérence de phase supérieure à 99,9% et la blague, c'est que ce pattern est fixe. Donc pas de message unique par image, pas de clé qui change... c'est juste la même empreinte spectrale sur toutes les images sorties du modèle Gemini.

Spectre FFT du watermark SynthID - les pics lumineux correspondent aux fréquences porteuses identifiées

Du coup, une fois que vous avez profilé cette empreinte avec une cinquantaine d'images PNG de référence (25 noires, 25 blanches, générées via l'API Gemini), vous pouvez faire deux trucs. D'abord, détecter le filigrane avec 90% de précision, sans avoir le moindre accès au code source de Google. Et ensuite le retirer en soustrayant les composantes spectrales identifiées, fréquence par fréquence, tout en préservant la qualité de l'image à plus de 40 dB PSNR. Visuellement identique à l'original !

Et c'est là que la différence avec UnMarker (dont je vous avais parlé) saute aux yeux car ce dernier "secoue" l'image en aveugle pour casser le watermark. Alors que Reverse-SynthID, c'est plutôt scruté à la loupe et hyper ciblé. Résultat, y'a clairement moins de dégradation et un drop de confiance du détecteur.

Les fréquences porteuses reconstruites - la structure diagonale du watermark SynthID

Par contre, je l'ai implémenté en Rust et j'ai essayé de voir si ça marchait vraiment sur mes propres images générée avec Gemini. Hé bien non, car le bypass ne fait PAS chuter la confiance du détecteur de 100 à 0, mais juste de quelques pourcents.

Le watermark est atténué, mais pas effacé. Ce n'est donc pas un outil clé en main pour faire disparaître tous les filigranes SynthID en un clic. Mais le fait qu'une seule personne, avec du Python et du traitement de signal classique (FFT, filtres notch, soustraction spectrale), ait pu reverse-engineerer un système que Google présente comme LA solution anti-deepfakes...

Ça confirme ce que les chercheurs de l'Université de Waterloo avaient déjà démontré : le watermarking d'images IA, c'est pété by design.

D'ailleurs, Google le sait très bien et ils pourraient changer le pattern demain et tout serait à refaire, mais ça confirme surtout que le principe même du watermarking spectral a une date de péremption. Après, ça arrange tout le monde d'avoir un truc à montrer quand les gouvernements demandent "et contre les deepfakes, vous faites quoi ?"

Et si c'est la petite étoile visible en bas à droite des images Gemini qui vous gêne (pas le watermark spectral invisible, juste le marqueur visuel), j'ai développé un outil pour mes Patreons qui s'en occupe.

Bref, tout est sur le repo si le reverse-engineering de watermarks IA, ça vous branche !

Intel Core Ultra 3 205 : des premiers tests prometteurs sur PassMark

Le Core Ultra 3 205 d'Intel apparaît pour la première fois sur PassMark avec un score monocœur supérieur à ceux du Core Ultra 5 225 et du Ryzen 5 9600X, mais reste réservé aux configurations OEM malgré un positionnement tarifaire attractif.

L’article Intel Core Ultra 3 205 : des premiers tests prometteurs sur PassMark est apparu en premier sur Tom’s Hardware.

full

thumbnail

100 000 postes à pourvoir d’ici 2030 : la cybersécurité, un enjeu majeur pour la souveraineté numérique française [Sponso]

Cet article a été réalisé en collaboration avec École Polytechnique Executive Education

Une formation diplômante de niveau Bac +5, proposée par l'École polytechnique, permet de faire évoluer sa carrière et de consolider ses connaissances et compétences en matière de cybersécurité.

Cet article a été réalisé en collaboration avec École Polytechnique Executive Education

Il s’agit d’un contenu créé par des rédacteurs indépendants au sein de l’entité Humanoid xp. L’équipe éditoriale de Numerama n’a pas participé à sa création. Nous nous engageons auprès de nos lecteurs pour que ces contenus soient intéressants, qualitatifs et correspondent à leurs intérêts.

En savoir plus

Dire à une IA qu'elle est experte la rend moins performante

Des chercheurs de l'université de Californie du Sud viennent de publier une étude improbable : demander à un modèle d'IA de jouer les experts dégrade ses performances sur les tâches factuelles. Commencer un prompt par "Tu es un expert en programmation" produit de moins bons résultats que de poser la question directement.

Le piège du "tu es un expert"

L'étude, intitulée "Expert Personas Improve LLM Alignment but Damage Accuracy", a mesuré l'impact des instructions de rôle sur les réponses des modèles de langage.

Sur le benchmark MMLU, qui teste les connaissances générales et le raisonnement, les modèles avec une persona d'expert ont obtenu 68 % de bonnes réponses contre 71,6 % sans aucune instruction de rôle.

La baisse est constante sur toutes les catégories testées : maths, code, sciences, culture générale. Bref, dire à une IA qu'elle est brillante la rend un peu moins brillante.

Quand ça marche quand même

Par contre, le persona prompting fonctionne très bien pour un autre type de tâches : la sécurité et l'alignement. En attribuant un rôle de "moniteur de sécurité" au modèle, les chercheurs ont augmenté le taux de refus d'attaques de 53,2 % à 70,9 %, soit une hausse de 17,7 points. Pour les tâches d'écriture et de mise en forme, les personas aident aussi.

L'explication est assez logique : quand on colle un rôle d'expert au modèle, il bascule en mode "suivi d'instructions" et mobilise moins de ressources pour aller chercher les faits dans ses données d'entraînement. Aucune connaissance n'est ajoutée, on déplace juste l'attention du modèle.

Le bon réflexe à adopter

Les chercheurs de l'USC proposent un outil baptisé PRISM qui active automatiquement les personas uniquement quand c'est utile. Mais en attendant que ce genre de système soit intégré aux chatbots grand public, la recommandation est simple : si vous avez besoin de réponses factuelles ou de code, posez votre question directement sans ajouter de rôle.

Si vous voulez que l'IA respecte un ton, un format ou des consignes de sécurité, le persona prompting reste la bonne approche.

On a quand même passé deux ans à répéter partout qu'il fallait commencer ses prompts par "Tu es un expert en..." pour avoir de meilleurs résultats. Visiblement, c'était un peu du vent.

Source : Search Engine Journal

NTSYNC - Wine 11 booste les jeux Linux de 678%

Dirt 3 qui passe de 110 à 860 FPS sous nunux, non, j'ai pas fumé la moquette ! En fait c'est surtout grâce au fameux module de synchronisation kernel NTSYNC promis avec Wine 11 qui est enfin dispo dans certaines distros. Et la bonne nouvelle c'est que les premiers benchmarks développeurs viennent de tomber, donc on va regarder ça ensemble !

Concrètement, Fedora 42, Ubuntu 25.04 et SteamOS 3.7.20 beta embarquent maintenant le module par défaut avec le kernel 6.14. Du coup Resident Evil 2 bondit de 26 à 77 FPS, Call of Juarez grimpe de 99 à 224 FPS, et Tiny Tina's Wonderlands passe de 130 à 360. Et Call of Duty Black Ops est maintenant devenu... jouable ! Woohoo !

Alors attention, ces benchmarks comparent Wine vanilla (sans aucune optimisation) avec Wine + le module. Cela veut dire que si vous utilisiez déjà fsync via Proton ou Lutris, les gains seront moins spectaculaires. Après les jeux qui en profitent le plus sont ceux avec de grosses charges multi-thread où la synchronisation était vraiment le problèmo noméro uno.

Pour capter pourquoi cette news est un gros morceau, faut regarder un peu sous le capot. Au temps jadis, chaque fois qu'un jeu Windows devait coordonner ses threads (genre, attendre qu'une texture finisse de charger), Wine faisait des allers-retours avec wineserver... des milliers de fois par seconde. Du coup, on se tapait des micro-sacades et une cadence d'images pourrie.

Y'a eu des tentatives pour arranger ça. D'abord esync, puis fsync... ça améliorait les choses mais c'était du bricolage. Ça nécessitait des patchs kernel non-officiels que personne ne maintenait vraiment, et certains jeux gourmands faisaient carrément tout planter.

Mais tout cela c'est de l'histoire ancienne puisque NTSYNC, semble être enfin la bonne approche. Elizabeth Figura (CodeWeavers), la même dev qui avait pondu les solutions précédentes, a créé, cette fois, un vrai module intégré directement dans le noyau Linux. Comme ça, plus de bidouilles à la con et surtout plus d'approximations. Le noyau gère enfin la synchronisation lui-même, nativement, comme il aurait toujours dû le faire.

La stonksitude du barbu gamer est à son maaaax

Après des années de boulot et une présentation à la Linux Plumbers Conference 2023, le module a fini par être mergé dans le kernel mainline il y a peu. Ça marche donc "out of the box" et ça c'est plutôt chouette !

Et pour les possesseurs de Steam Deck, quand Valve rebasera Proton officiel sur Wine 11, tout le monde aura ça gratos !! En attendant, si vous êtes impatient, sachez que Proton-GE le supporte déjà ! Entre ça et le fait que 90% des jeux Windows tournent maintenant sous Linux , y'a clairement plus d'excuses pour rester sous Windows si c'est le gaming qui vous retenait, mes cocos !

Bref, c'est carrément la plus grosse avancée gaming Linux depuis Proton. Pas mal pour un module kernel bien velu quand même !

Source

Sora ferme - Comment sauvegarder vos vidéos IA avant la coupure

Sora, c’est fini les amis !

Hé oui, cest chacals d'OpenAI ferment leur plateforme de vidéos IA, et franchement, ça me rend un peu triste. À vrai dire, même si c’était que de la vidéo générée à partir de prompts, moi je me marrais bien. C'était fun de regarder le produit de ses prompts mais aussi de regarder les conneries des autres. Les versions québécoises, aïe aïe aïe, c’était quelque chose quand même !

Mais bon, le plus urgent maintenant, c’est de sauvegarder vos vidéos avant que tout disparaisse. OpenAI n’a pas encore communiqué de date précise pour la coupure, juste un vague « on vous dira bientôt ». Du coup, autant ne pas traîner, parce que quand ce genre de service cloud ferme, en général c’est pas 6 mois de préavis qu’on vous file...

Depuis la fuite du modèle jusqu’à aujourd’hui, Sora aura fait parler de lui. Côté raisons, c’est Fidji Simo (la patronne de la division Applications) qui a lâché le morceau : ils éparpillent leurs efforts sur trop d’apps, d’API et de stacks serveur différents, et ça les ralentit. En gros, entre préparer une entrée en bourse pour fin 2026 et cramer du GPU H100 sur des vidéos de chats en IA, le choix est vite fait. L’équipe de recherche Sora, elle, continuera à bosser sur la simulation de mondes 3D... mais pour la robotique. Et le fameux deal à 1 milliard de dollars avec Disney pour des films et séries ? Pouf, magie magie, c'est envolé !!

Faut dire que les chiffres n’étaient pas glorieux non plus. Après un lancement en fanfare fin 2024 (et une app iOS lancée à l’automne 2025 qui avait cartonné dans les charts), les téléchargements sur l’App Store avaient plongé de 32% entre novembre et décembre 2025. La hype, ça dure qu’un temps.

Mais maintenant les gens, on passe aux choses sérieuses !

Sora Backup - le script qui sauve vos vidéos

Je n'avais absolument pas de temps aujourd'hui, mais j'ai quand même taffé pour vous développer un petit script JavaScript qui récupère TOUTES vos vidéos Sora d’un coup, avec les prompts et les métadonnées, et qui vous génère un joli ZIP prêt à archiver. Pas besoin d’installer quoi que ce soit, pas d’extension louche. Vous avez juste besoin d'être connecté à votre profil Sora et d'un navigateur.

Comment ça marche

Allez sur sora.com , connectez-vous à votre compte, puis ouvrez la console JavaScript de votre navigateur (F12 sur Chrome ou Firefox, onglet Console). Ensuite, glissez-déplacez ou collez le script ci-dessous dedans et appuyez sur Entrée.

Le script va automatiquement récupérer votre token d’authentification (pas besoin de le chercher vous-même), puis il va paginer sur votre profil Sora pour récupérer tous vos posts publiés. Pour chaque post, il extrait les vidéos attachées (MP4), les télécharge, et empaquette le tout dans un fichier ZIP directement dans votre navigateur.

Y’a même un fichier manifest.json dans le ZIP qui contient tous vos prompts, les dimensions, les durées, les permalinks, les dates de création... bref, tout ce qu’il faut pour retrouver vos petits. Le ZIP est généré en format STORE (pas compressé, parce que compresser du MP4 ça sert à rien), avec un calcul CRC32 maison et sans aucune librairie externe.

Le script complet

Voici le code à coller dans la console :

// ==========================================================
// SORA BACKUP - Sauvegarde complète vidéos + images + prompts par Korben
// ==========================================================
// Usage : Ouvrir https://sora.com, F12 > Console, coller ce script
// Les fichiers sont téléchargés via le navigateur (dossier Downloads)
// Un fichier manifest.json récapitule tout (prompts, metadata, URLs)
// ==========================================================

(async () => {
 // --- Mini ZIP builder (STORE, pas de lib externe) ---
 const crc32table = new Uint32Array(256);
 for (let i = 0; i < 256; i++) {
 let c = i;
 for (let j = 0; j < 8; j++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
 crc32table[i] = c;
 }
 function crc32(buf) {
 let c = 0xFFFFFFFF;
 for (let i = 0; i < buf.length; i++) c = crc32table[(c ^ buf[i]) & 0xFF] ^ (c >>> 8);
 return (c ^ 0xFFFFFFFF) >>> 0;
 }
 const zipFiles = []; // {name, data (Uint8Array), crc, size}

 const PAGE_SIZE = 50;
 const DELAY_MS = 1500;
 const manifest = [];
 let totalDownloaded = 0;
 let totalErrors = 0;

 // --- Auth : récupérer le Bearer token ---
 // OPTION 1 : Coller ton token ici (Network tab > Authorization header)
 // OPTION 2 : Laisser vide, le script tentera de le récupérer auto
 let AUTH_TOKEN = '';

 async function getAuthToken() {
 if (AUTH_TOKEN) return AUTH_TOKEN;

 // Auto-detect : endpoint session ChatGPT
 for (const path of ['/api/auth/session', '/backend-api/auth/session']) {
 try {
 const r = await fetch(path, { credentials: 'include' });
 if (r.ok) {
 const json = await r.json();
 if (json.accessToken) {
 AUTH_TOKEN = json.accessToken;
 console.log(' 🔑 Token récupéré automatiquement');
 return AUTH_TOKEN;
 }
 }
 } catch(e) {}
 }

 // Fallback : demander à l'utilisateur
 const input = prompt(
 'Token non trouvé automatiquement.\n\n' +
 'Pour le récupérer :\n' +
 '1. F12 > onglet Réseau\n' +
 '2. Rafraîchis la page\n' +
 '3. Clique sur une requête /backend/...\n' +
 '4. Copie le header Authorization\n\n' +
 'Colle le token ici (Bearer eyJ...):'
 );
 if (input) {
 AUTH_TOKEN = input.replace(/^Bearer\s+/i, '').trim();
 return AUTH_TOKEN;
 }

 console.error(' ❌ Pas de token. Annulation.');
 return null;
 }

 // --- Fetch API avec auth ---
 async function apiFetch(url) {
 const token = await getAuthToken();
 const headers = {};
 if (token) headers['Authorization'] = 'Bearer ' + token;

 // oai-device-id requis par certains endpoints
 const deviceId = localStorage.getItem('oai-did') || '';
 if (deviceId) headers['oai-device-id'] = deviceId;

 const resp = await fetch(url, {
 method: 'GET',
 credentials: 'include',
 headers
 });

 if (!resp.ok) throw new Error(`HTTP ${resp.status} for ${url}`);
 return resp.json();
 }

 // --- Pagination générique ---
 async function fetchAllPages(baseUrl, dataField = 'data', cursorParam = 'after', cursorField = 'last_id') {
 let allItems = [];
 let cursor = '';
 let page = 0;

 while (true) {
 let url = baseUrl;
 if (cursor) url += `&${cursorParam}=${cursor}`;

 console.log(` 📄 Page ${++page} (${allItems.length} items so far)...`);
 const json = await apiFetch(url);

 const items = json[dataField];
 if (!Array.isArray(items) || items.length === 0) break;

 allItems = allItems.concat(items);
 cursor = json[cursorField] || '';

 if (!json.has_more && !cursor) break;
 await sleep(DELAY_MS);
 }

 return allItems;
 }

 // Variante pour les endpoints project_y (cursor-based)
 async function fetchAllPagesCursor(baseUrl) {
 let allItems = [];
 let cursor = '';
 let page = 0;

 while (true) {
 let url = baseUrl;
 if (cursor) url += `&cursor=${cursor}`;

 console.log(` 📄 Page ${++page} (${allItems.length} items so far)...`);
 const json = await apiFetch(url);

 const items = json.items;
 if (!Array.isArray(items) || items.length === 0) break;

 allItems = allItems.concat(items);
 cursor = json.cursor || '';

 if (!cursor) break;
 await sleep(DELAY_MS);
 }

 return allItems;
 }

 function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

 // --- Extraire URL du média depuis une generation ---
 function getMediaUrl(gen) {
 return gen?.encodings?.source?.path
 || gen?.downloadable_url
 || gen?.url
 || '';
 }

 // --- Extraire le prompt (peut être dans actions, prompt, ou input_text) ---
 function getPrompt(item, gen) {
 // Prompt direct
 if (gen?.prompt) return gen.prompt;
 if (item?.prompt) return item.prompt;
 if (item?.input_text) return item.input_text;
 // Storyboard : les actions sont les descriptions des scènes
 if (item?.actions && typeof item.actions === 'object') {
 return Object.entries(item.actions)
 .sort((a,b) => Number(a[0]) - Number(b[0]))
 .map(([frame, desc]) => `[frame ${frame}] ${desc}`)
 .join(' | ');
 }
 if (gen?.actions && typeof gen.actions === 'object') {
 return Object.entries(gen.actions)
 .sort((a,b) => Number(a[0]) - Number(b[0]))
 .map(([frame, desc]) => `[frame ${frame}] ${desc}`)
 .join(' | ');
 }
 return '';
 }

 // --- Dérouler les items du profil Sora en items plats ---
 function flattenProfileItems(items) {
 const flat = [];
 for (const item of items) {
 const post = item.post || item;
 const attachments = post.attachments || [];
 if (attachments.length === 0) continue;

 for (const att of attachments) {
 const url = att.encodings?.source?.path || att.downloadable_url || att.url || '';
 if (!url) continue;

 flat.push({
 id: post.id || att.generation_id || '',
 generation_id: att.generation_id || '',
 task_id: att.task_id || '',
 title: att.title || post.discovery_phrase || '',
 prompt: post.text || '',
 emoji: post.emoji || '',
 type: att.generation_type || att.kind || '',
 width: att.width || 0,
 height: att.height || 0,
 duration_s: att.duration_s || 0,
 is_public: !!post.posted_to_public,
 created_at: post.posted_at ? new Date(post.posted_at * 1000).toISOString() : '',
 url: url,
 permalink: post.permalink || '',
 username: item.profile?.username || '',
 });
 }
 }
 return flat;
 }

 // --- Sanitize filename ---
 function sanitize(name) {
 return name.replace(/[<>:"\/\\|?*\x00-\x1f]/g, '_').substring(0, 100);
 }

 // --- Ajouter un fichier au ZIP ---
 async function addToZip(url, filename) {
 try {
 const resp = await fetch(url);
 if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
 const buf = await resp.arrayBuffer();
 const data = new Uint8Array(buf);
 zipFiles.push({ name: filename, data, crc: crc32(data), size: data.length });
 totalDownloaded++;
 return true;
 } catch(e) {
 console.warn(` ⚠️ Erreur ${filename}:`, e.message);
 totalErrors++;
 return false;
 }
 }

 // --- Déduire l'extension ---
 function getExt(url, type) {
 if (!url) return type === 'video' ? '.mp4' : '.png';
 const m = url.match(/\.(mp4|webm|mov|png|jpg|jpeg|webp|gif)/i);
 return m ? '.' + m[1].toLowerCase() : (type === 'video' ? '.mp4' : '.png');
 }

 // ==========================================================
 // MAIN
 // ==========================================================
 const origin = window.location.origin;
 console.log('🎬 SORA BACKUP - Démarrage');
 console.log('='.repeat(50));

 // 1. Mes posts Sora (profil)
 console.log('\n📦 1/2 - Récupération de mes posts Sora...');
 let myPosts = [];
 try {
 myPosts = await fetchAllPagesCursor(
 `${origin}/backend/project_y/profile_feed/me?limit=${PAGE_SIZE}&cut=nf2`
 );
 console.log(`  ${myPosts.length} posts de profil`);
 // Debug premier item
 if (myPosts.length > 0) {
 const first = myPosts[0];
 console.log(' 🔍 Premier item - clés:', Object.keys(first).join(', '));
 console.log(' 🔍 URL:', first.url?.substring(0, 80) || 'none');
 console.log(' 🔍 DL:', first.downloadable_url?.substring(0, 80) || 'none');
 console.log(' 🔍 ENC:', first.encodings?.source?.path?.substring(0, 80) || 'none');
 console.log(' 🔍 GENS:', first.generations?.length || 'none');
 console.log(' 🔍 TITLE:', first.title || 'none');
 }
 } catch(e) {
 console.warn(' ⚠️ profil failed:', e.message);
 }

 // 2. Mes likes sur Sora
 console.log('\n📦 2/2 - Récupération de mes likes Sora...');
 let myLikes = [];
 try {
 myLikes = await fetchAllPagesCursor(
 `${origin}/backend/project_y/profile_feed/me?limit=${PAGE_SIZE}&cut=appearances`
 );
 if (myCameos.length) console.log(`  ${myCameos.length} cameos trouvés`);
 } catch(e) {}

 // --- Dérouler les generations et dédupliquer ---
 console.log('\n🔄 Extraction des vidéos...');
 const rawAll = [...myPosts, ...myLikes];
 const flatItems = flattenProfileItems(rawAll);

 const seen = new Set();
 const allItems = [];
 for (const item of flatItems) {
 if (item.id && seen.has(item.id)) continue;
 // Filtrer : vidéos uniquement
 const isVideo = item.type === 'video_gen' || item.url.includes('/videos/') || item.url.includes('.mp4');
 if (!isVideo) continue;
 if (item.id) seen.add(item.id);
 allItems.push(item);
 }

 console.log(`📊 Total unique: ${allItems.length} vidéos à télécharger`);
 console.log('='.repeat(50));

 // --- Construire le manifest et télécharger ---
 console.log('\n⬇️ Téléchargement en cours...');
 console.log('(Les fichiers arrivent dans ton dossier Downloads)');

 for (let i = 0; i < allItems.length; i++) {
 const meta = allItems[i];
 const url = meta.url;

 if (!url) {
 console.log(` ⏭️ [${i+1}/${allItems.length}] ${meta.id} - pas d'URL, skip`);
 meta.downloaded = false;
 manifest.push(meta);
 continue;
 }

 const type = (meta.task_type === 'image_gen' || url.match(/\.(png|jpg|jpeg|webp|gif)/i)) ? 'image' : 'video';
 const ext = getExt(url, type);
 const nameBase = meta.title
 ? sanitize(meta.title)
 : (meta.prompt ? sanitize(meta.prompt.substring(0, 60)) : meta.id);
 const filename = `sora_${String(i+1).padStart(4,'0')}_${nameBase}${ext}`;

 console.log(` ⬇️ [${i+1}/${allItems.length}] ${filename}`);
 meta.filename = filename;
 meta.downloaded = await addToZip(url, filename);
 manifest.push(meta);

 // Pause entre downloads pour pas surcharger
 if (i < allItems.length - 1) await sleep(800);
 }

 // --- Ajouter le manifest au ZIP ---
 console.log('\n📝 Ajout du manifest au ZIP...');
 const manifestData = new TextEncoder().encode(JSON.stringify(manifest, null, 2));
 zipFiles.push({ name: 'manifest.json', data: manifestData, crc: crc32(manifestData), size: manifestData.length });

 // --- Générer le ZIP (format STORE, pas de compression) ---
 console.log('\n📦 Génération du ZIP...');
 const enc = new TextEncoder();
 const blobParts = [];
 const centralParts = [];
 let offset = 0;

 for (const f of zipFiles) {
 const nameBytes = enc.encode(f.name);
 // Local file header (30 bytes + name)
 const lh = new ArrayBuffer(30);
 const lv = new DataView(lh);
 lv.setUint32(0, 0x04034b50, true);
 lv.setUint16(4, 20, true);
 lv.setUint16(8, 0, true); // STORE
 lv.setUint32(14, f.crc, true);
 lv.setUint32(18, f.size, true);
 lv.setUint32(22, f.size, true);
 lv.setUint16(26, nameBytes.length, true);
 blobParts.push(new Uint8Array(lh), nameBytes, f.data);

 // Central directory entry (46 bytes + name)
 const ch = new ArrayBuffer(46);
 const cv = new DataView(ch);
 cv.setUint32(0, 0x02014b50, true);
 cv.setUint16(4, 20, true);
 cv.setUint16(6, 20, true);
 cv.setUint16(10, 0, true); // STORE
 cv.setUint32(16, f.crc, true);
 cv.setUint32(20, f.size, true);
 cv.setUint32(24, f.size, true);
 cv.setUint16(28, nameBytes.length, true);
 cv.setUint32(42, offset, true);
 centralParts.push(new Uint8Array(ch), nameBytes);

 offset += 30 + nameBytes.length + f.size;
 }

 const centralSize = centralParts.reduce((s, p) => s + p.length, 0);
 const eocd = new ArrayBuffer(22);
 const ev = new DataView(eocd);
 ev.setUint32(0, 0x06054b50, true);
 ev.setUint16(8, zipFiles.length, true);
 ev.setUint16(10, zipFiles.length, true);
 ev.setUint32(12, centralSize, true);
 ev.setUint32(16, offset, true);

 const zipBlob = new Blob([...blobParts, ...centralParts, new Uint8Array(eocd)], { type: 'application/zip' });

 const zipName = `sora_backup_${new Date().toISOString().split('T')[0]}.zip`;
 const a = document.createElement('a');
 a.href = URL.createObjectURL(zipBlob);
 a.download = zipName;
 document.body.appendChild(a);
 a.click();
 document.body.removeChild(a);
 URL.revokeObjectURL(a.href);

 // --- Résumé ---
 const sizeMB = (zipBlob.size / 1024 / 1024).toFixed(1);
 console.log('\n' + '='.repeat(50));
 console.log('🎬 SORA BACKUP TERMINÉ');
 console.log(`  Vidéos dans le ZIP : ${totalDownloaded}`);
 console.log(`  Erreurs : ${totalErrors}`);
 console.log(` 📦 Fichier : ${zipName} (${sizeMB} MB)`);
 console.log(` 📝 manifest.json inclus dans le ZIP`);
 console.log('='.repeat(50));
})();

Quelques précisions

Si le token n’est pas récupéré automatiquement (ça peut arriver selon votre config), le script vous demandera de le coller manuellement. Pour le trouver, c’est simple : F12 > onglet Réseau > rafraîchissez la page > cliquez sur n’importe quelle requête vers /backend/... > copiez le header Authorization.

D’ailleurs, si la vidéo IA vous branche toujours, Higgsfield propose des séries entièrement générées par IA. C’est pas la même approche que Sora, mais c’est un signe que la vidéo IA ne meurt pas avec la fermeture d’un seul service.

Bon, bref, c’est la fin d’un truc sympa. Moi je préférais largement scroller sur Sora sur d'aller sur TikTok ou Instagram parce qu'au moins c'était drôle !

Merci à mes Patreons qui me permettent de prendre le temps de développer ce genre de petits outils pour vous. Sans eux, j’aurais jamais pu me poser une après-midi pour coder ça.

Source

❌