5

Has anyone ever managed to save an entire chat from Microsoft Teams in any file format (including chat images e.g. screenshots, user avatars etc.)?

I've tried it in all browsers and in all thinkable ways.

The problem seems to be that Teams removes all text not contained in the current viewport, and even more so the images, from memory.

This looks like lazy loading, but apparently isn't, as it can't be prevented by disabling the lazy loading options in the browser (tried in Chrome and Firefox, with the browser-based version of Teams).

Only the content of the current viewport is displayed and loaded. All other content further up as well as further down is immediately unloaded when scrolling. Therefore, the entire chat cannot be selected and saved or exported, respectively, in any way, and not even be copied to the clipboard:

Contents outside the viewport are unloaded immediately when scrolling

David.P
  • 196
  • 1
  • 9

5 Answers5

3

Here's some spaghetti I came up with that works in Chrome console -- it scrolls to the top of whatever chat you have selected, and then goes piece by piece down the line, creating a printable/savable copy. Even loads all the inline pictures.

Only issue is that really long chat histories take a while to scroll to beginning of.

So yeah, to use, just open Chrome console window (Ctrl+Shift+J) copy-and-paste this in, and press enter. Then wait for it to do the magics.

let xxx = 0;
let ddd = 0;
let fullpage1 = '' 

fullpage1 = '<h1 id="chat-header-title2">'+jQuery('#chat-header-title').html()+'</h1>';

//**********************************************
//Go to top of page

jj234 = function() {


jQuery("virtual-repeat").scrollTop(-2000);



if (xxx < 50) {setTimeout(jj234, 50); } else {setTimeout(ScrollDownTakeNote, 1000); }

if (jQuery("virtual-repeat").hasClass("not-at-top")) {xxx = 0;} else {xxx += 1;} 

}

jj234();


//Create a place to hold a copy of the content
jQuery("body").append('<div id="thebigdiv" style="z-index: 10000; overflow: auto; background: white;"></div>');


//*************************************************
//Scroll down the chat, once things load, copy it to the temp location, removing some dynamic content

ScrollDownTakeNote = function() {

if (jQuery('.disable-event').length==0 && jQuery('.ts-image .loading').length==0) {
ddd += 1;

jQuery("#thebigdiv").append('<div id="boxdiv'+ddd+'"></div>');

jQuery("virtual-repeat div.clearfix").each( function () {
jQuery("#boxdiv"+ddd).append('<div class="messageClip" id="'+jQuery(this).attr('id')+'">' + jQuery(this).html() + '</div>');
});

jQuery("#boxdiv"+ddd+" [data-tid]").attr("data-tid","");

jQuery("#boxdiv"+ddd+" [ng-if]").attr("ng-if","");

jQuery("#boxdiv"+ddd+" [ng-class]").attr("ng-class","");

jQuery("#boxdiv"+ddd+" [ng-source]").attr("ng-source","");

jQuery("#boxdiv"+ddd+" [ng-class]").attr("ng-class","");

jQuery("#boxdiv"+ddd+" [simple-mouseenter]").attr("simple-mouseenter","");

jQuery("#boxdiv"+ddd+" [ng-mouseleave]").attr("ng-mouseleave","");

jQuery("#boxdiv"+ddd+" [message-view-model]").attr("message-view-model","");

jQuery("#boxdiv"+ddd+" [message-vm]").attr("message-vm","");

jQuery("#boxdiv"+ddd+" [is-enabled]").attr("is-enabled","");

jQuery("#boxdiv"+ddd+" [host-tenant-id]").attr("host-tenant-id","");


jQuery("#boxdiv"+ddd+" [scroll-item-tracker]").attr("scroll-item-tracker","");
jQuery("#boxdiv"+ddd+" [scroll-item-id]").attr("scroll-item-id","");
jQuery("#boxdiv"+ddd+" [scroll-item-event-name]").attr("scroll-item-event-name","");
jQuery("#boxdiv"+ddd+" [is-above-view-callback]").attr("is-above-view-callback","");
jQuery("#boxdiv"+ddd+" [is-in-view-callback]").attr("is-in-view-callback","");
jQuery("#boxdiv"+ddd+" [scroll-container]").attr("scroll-container","");
jQuery("#boxdiv"+ddd+" [check-above-viewport]").attr("check-above-viewport","");
jQuery("#boxdiv"+ddd+" [delay-scroll-tracker-init]").attr("delay-scroll-tracker-init","");
jQuery("#boxdiv"+ddd+" [deferred-render-ready]").attr("deferred-render-ready","");

jQuery("#boxdiv"+ddd+" img[target-src]").each(function () {$(this).attr("lazy-load","false"); $(this).attr("src",$(this).attr("target-src"));});

jQuery("#boxdiv"+ddd+" skype-status").remove();

fullpage1 += jQuery("#thebigdiv").html();
jQuery("#thebigdiv").html(".");

jQuery("virtual-repeat.simple-scrollbar").scrollTop(jQuery("virtual-repeat.simple-scrollbar").scrollTop()+2000);

}

if  (jQuery('[data-scroll-pos="1"').length==0 || jQuery("virtual-repeat").hasClass("not-at-bottom") || jQuery('.disable-event').length>0 || jQuery('.ts-image .loading').length>0 ) {setTimeout(ScrollDownTakeNote, 500);
} else {printdiv("#thebigdiv")}

};

//**********************************
//Open a Save File dialog for the HTML source and open the print window for a Print-to-PDF
//(Why not both?)

function printdiv(printdivname) {
        jQuery(printdivname).html(fullpage1);
    jQuery('#outer-shell').remove();
    jQuery('body').css({"overflow":"scroll"});
    
    $('div.messageClip[id]').each(function () {
            $('div.messageClip[id="' + this.id + '"]:gt(0)').remove();
    });
    jQuery(printdivname).css({"margin-left":"15px"});
    jQuery('body').append("<style>@media print { * { overflow: visible !important; } .page { page-break-after:always; }}");
    jQuery('head').append('<link rel="stylesheet" href="https://statics.teams.cdn.office.net/hashed/stylesheets.theme-defaultV2.min-86505e7.css" />');
    
    fullpage2 = new XMLSerializer().serializeToString(document);
    //fullpage2 = fullpage2.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
    //  return '&#'+i.charCodeAt(0)+';'; 
    //});

    saveTextAsFile(fullpage2);  

    waitonimages();
    
}

let xasd = 0;
function waitonimages() {
    xasd = 0;
    jQuery("img").each(function () {if (!this.complete) { xasd+=1 }});
    if (xasd == 0) {window.print()} else {setTimeout(waitonimages,1000);}   
}

function saveTextAsFile(text1)
{
    //inputTextToSave--> the text area from which the text to save is
    //taken from
    var textToSave = text1;
    var textToSaveAsBlob = new Blob([textToSave], {type:"text/html"});
    var textToSaveAsURL = window.URL.createObjectURL(textToSaveAsBlob);
    //inputFileNameToSaveAs-->The text field in which the user input for 
    //the desired file name is input into.
    var fileNameToSaveAs = jQuery('#chat-header-title2').html() + "-source.htm";

    var downloadLink = document.createElement("a");
    downloadLink.download = fileNameToSaveAs;
    downloadLink.innerHTML = "Download File";
    downloadLink.href = textToSaveAsURL;
    downloadLink.onclick = destroyClickedElement;
    downloadLink.style.display = "none";
    document.body.appendChild(downloadLink);

    downloadLink.click();
}

function destroyClickedElement(event)
{
    document.body.removeChild(event.target);
}
Jon Ivy
  • 31
  • 2
  • I just tried this but I get ``VM27:5 Uncaught ReferenceError: jQuery is not defined at :5:1`` ‍♂️ – David.P Nov 22 '22 at 18:02
2

I ran into the same problem. I couldn't use the graph API since my user does not have the permissions (just a normal Teams user).

I also noticed the sort of "Lazy loading" you mention so a solution I found was to extend the view so everything is visible (and loaded).

I did this with the Firefox console commands suggested here:

  1. Make sure that you scrolled to the top and the bottom of the chat conversation you want to save
  2. Press F12 to open the developer tools
  3. Select the tab "Console" in the developer tools
  4. Paste the code below in the console, it should zoom out the page
const zoomFactor = (document.querySelector(".ts-main-flex").offsetHeight / document.querySelector(".list-wrap").offsetHeight);
document.documentElement.style.setProperty("transform", "scale(" + zoomFactor + ")");
document.documentElement.style.setProperty("transform-origin", "top");
document.documentElement.style.setProperty("min-height", (100 / zoomFactor) + "vh");
  1. Once the page is fully loaded, paste the code below in the console, it should expand all the grouped chats.
document.querySelectorAll(".expand-collapse").forEach(link => link.click());
document.querySelectorAll(".ts-see-more-button.ts-see-more-fold").forEach(link => link.click());
  1. Paste the code below in the console, it should adjust the zoom level
const newZoomFactor = (document.querySelector(".ts-main-flex").offsetHeight / document.querySelector(".list-wrap").offsetHeight);
document.documentElement.style.setProperty("transform", "scale(" + newZoomFactor + ")");
document.documentElement.style.setProperty("min-height", (100 / newZoomFactor) + "vh");
  1. Once the page is fully loaded, paste the code below to reset the zoom and add a scrollbar
document.documentElement.style.removeProperty("transform");
document.documentElement.style.removeProperty("transform-origin");
document.documentElement.style.setProperty("overflow", "auto");
  1. Save the page with SingleFile
  2. Take a coffee, enjoy the passing of time, pray for your CPU
  3. And voilà! Your saved page should be okay

Saving the page with SingleFile worked great for me.

For very long chats I used SingleFileZ which is a fork of SingleFile that adds compression and generates an auto-extracting file. In these cases some of the steps took me a few hours to complete.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • This looks awesome. I will definitely try it the next time I have a need to save a long Teams thread. For threads that are not that long, I've been using Opera recently with good success. There, you can set the zoom so small that ideally the whole thread is visible in the browser window. Then you can use Opera's unique PDF screenshot function to save the entire thread as one PDF page (vectorially, with real fonts etc.). Also, any inline images are included with a very usable resolution. – David.P Nov 25 '21 at 13:41
  • Unfortunately, on the first code block I get this error message in Firefox: ``Uncaught TypeError: document.querySelector(...) is null`` ‍♂️ – David.P Nov 22 '22 at 18:24
1

You can get teams chat using graph API along with the images but it is limited only for 20 days data. You can fetch the teams chat up to 20 days. Also There is an feature request to export the teams chat externally. Currently you cannot export teams chat. Could you please upvote Teams chat history export feature to available this in future. You can reach out to Product support channels for more info

Nikitha-MSFT
  • 577
  • 1
  • 3
  • 6
  • Thank you. I believe that the graph API solution goes over my head, unfortunately. I must have read all forum threads about this feature request in the meantime. Sadly, the currently only usable option seems to be to save the chat as a series of screenshots. Not even this is easy because of that Lazy Loading thing that Teams does. Fortunately, there is a [Chrome extension](https://chrome.google.com/webstore/detail/singlefile/mpiodijhokgodhhofbcjdecpffjipkle) which does the heavy lifting. – David.P Feb 19 '21 at 09:49
  • However, the included images then only have preview resolution and size, and attachments are not saved at all. Therefore, a solution to export Teams chats to, for example, Outlook [MSG or EML](https://stackoverflow.com/questions/16229591/difference-between-a-msg-file-and-a-eml-file) files (that can include images, attachments etc.) would be ideal. Anyway, I added my vote to that [Uservoice thread](https://microsoftteams.uservoice.com/forums/555103-public/suggestions/37466629-teams-chat-history-export-feature). – David.P Feb 19 '21 at 09:51
0

The easiest and best way for me on not too long chats was opening the chat in the Opera browser, then zooming out as much as possible until the entire chat is visible in tiny size, and then saving the page to PDF.

This way, you get a PDF of the chat with high quality vector text and images.

David.P
  • 196
  • 1
  • 9
-1

I found opening the Teams chat in the browser lets you select and copy-paste multiple messages and images in the conversation.

next: use regex-replace in the destination (MS Word) file to remove all the meta-information (timestamp + name).

Replace '[???????????????????????' with ''

Arend
  • 313
  • 2
  • 4