以编程方式将 Microsoft Teams 频道中的聊天消息导出为 word 或 pdf,无需管理员角色、权限或权限

问题描述 投票:0回答:2

我正在寻找一种编程方式,将每个 Microsoft Teams 频道的所有聊天消息(文本和图像内容)导出到 word 或 pdf 文档(任何支持文本和消息的输出介质)。我需要能够做到这一点,而无需向公司全局管理员寻求特定角色的许可。我已经研究了不同的方法,例如 Graph API(azure 应用程序注册)、电子发现以及从隐藏的 Outlook 文件夹中提取此信息。这些方法的共同主题是,我们需要针对导出策略中的特定需求寻求 IT 管理员的许可。

到目前为止,我已经尝试使用 Microsoft Teams 的 Web 应用程序版本并使用网页抓取方法,我已经能够循环浏览每个渠道中的消息并将其导出到 Word 文档中。我想知道是否有一种更优雅、更好、更不容易出错的方法。

寻找一些建议。

xpath web-scraping outlook microsoft-teams microsoft-graph-teams
2个回答
1
投票

这是一个名义上的解决方案。诚然,它不是很好/完整。但它在某种程度上确实有效。

你激励我清理我的尝试并发布它:

https://github.com/poleguy/selenium_teams

仅在 ubuntu 20.04 上测试。

克隆存储库。

运行 ./setup_python 以获取 conda 环境。

编辑脚本以指定您的网址/登录名。

运行 python ./selenium_teams.py

在弹出的浏览器中手动登录。

转到您想要聊天的聊天室。单击“输入新消息”部分。

按 Enter 键让 python 继续。

这将开始将所有消息保存到文本文件中。

获得文本文件后,将其转换为 pdf 或 word。

(不完整:无法处理图像,运行速度非常慢,可能会耗尽内存......测试很少。)


0
投票

我的这个 Javascript 脚本肯定可以优化,但它确实有效。 要使用它,您应该直接在 https://teams.microsoft.com 网站上浏览到您选择的团队对话。 将脚本放入开发人员工具的控制台窗格中,检查聊天窗格中的至少一个元素,然后运行脚本。

不用说,聊天记录越长,完成该过程所需的时间就越长。通过这个脚本,我已经成功地将多个聊天记录传输到 PDF。

/*
For this script to work you must be viewing Teams from your browser.
https://teams.microsoft.com
This script was tested in Firefox.
You must be using Developer tools of your browser
YOU MUST INSPECT a message, otherwise the HTML elements will not be available
The elements are burried in an iFrame, and you must inspect something for the iFrame code to be available to the script
Once you have inspected something, go to the Console tab of your developer tools
paste this whole script and run it
You can run it by pressing the Play button,
or with the keyboard shortcut Ctrl + Enter
It will first scroll all the way to the first message
Then it will create a new browser window with the messages in it
Then it is up to you to do what you want with it
I suggest you print the content to pdf (Ctrl + P)

It is possible the process will stop before reaching the top
If that is the case and you want to keep scrolling,
you can retrigger the script but call it with a false parameter to maintain the existing storage list
main(false)
*/

/*
 * This is the div in which the scroll bar resides
 */
var scrollDiv = () => { return document.getElementById("main-window-body").querySelector('[data-tid="message-pane-list-viewport"]'); }
/* 
 * this is where all the messages are displayed
 */
var chatPaneList = () => { return document.getElementById("chat-pane-list"); }
/*
 * all the messages being displayed in the chat pane
 * in a virtual list
 * as more are added, some are dropped and some loose their content
 * so, you must keep track of the messages as you scroll up the message pane
 */
var messagesInChatPane = () => { return chatPaneList().querySelectorAll('.fui-unstable-ChatItem'); }
/* this variable is to stop the scrolling while */
var stop;
/* just a means to keep track of wheter we should keep scrolling or not */
var msgOnSCreenBeforeScroll;
/* just a means to keep track of wheter we should keep scrolling or not */
var msgOnSCreenAfterScroll;
/* 
 * how long to wait before the sniffing the message pane after a scroll
 * if it is set too low, or not at all, the messages do not have enough time to load
 * the slower your computer or connection, the higher this value should be
 * the higher this value will be, the longer it will take to process a chat window
 */
var delayTime = 500;
/* log messages to console */
var logToConsole = true;
/* perhaps you only need a few scrolls worth or data */
var maxScrollOccurences = 0;//0 for inifinite
/* this keeps track of how many times we have scrolled so far */
var scrollOccurences = 0;
/* this array will hold the messages to display */
var messagesToDisplay = [];
/* 
 * sometimes having the right delay value is not enough
 * so, we add a fudge factor in case the delay was not long enough
 * this allows us to re-sniff the message pane without scrolling again
 * when we run out of fudge, we assume that we have reached the top of the message pane
 */
var maxFudge = () => { return 10; }

var firstRender = true;

main();//run this one to start a fresh process
//main(false);//run this one if you want to keep processing an existing window

/*
 * This is the only function call you should be calling
 * set FirstRender to true, or omit it, when you run the script for the first time in a chat pane
 * but, set FirstRender to false for all subsequent runs of the same chat pane
 */
async function main(FirstRender) {
    if (FirstRender != undefined) {
        firstRender = FirstRender
    }
    await ScrollToTopOfMessages();
    PrintIt();
}




async function ScrollToTopOfMessages() {
    var fudgeFactor = maxFudge();
    stop = false;
    msgOnSCreenBeforeScroll = 0;

    if (firstRender) {
        /* start with an empty list */
        messagesToDisplay = [];
    }

    while (!stop) {
        msgOnSCreenBeforeScroll = messagesToDisplay.length;
        LogThis("displayed before: " + msgOnSCreenBeforeScroll);
        await ScrollAndWait();
        scrollOccurences++;
        msgOnSCreenAfterScroll = messagesToDisplay.length;
        LogThis("displayed after: " + msgOnSCreenAfterScroll);

        //if more items are displayed than before, we continue scrolling
        //otherwise we stop and proceed to the next step
        var fudging = false;
        if (msgOnSCreenBeforeScroll == msgOnSCreenAfterScroll) {
            scrollOccurences--;//fudges do no count count. only scrolls that actually occurred
            fudgeFactor--;
            LogThis("fudging " + fudgeFactor + "/" + maxFudge());
            fudging = true;
            if (fudgeFactor == 0) {
                stop = true;
                LogThis("too much fudging");
            }
        }
        else {
            fudgeFactor = maxFudge();
        }

        /*
         * stop scrolling if we have reached the maxScrollOccurences
         */
        if (!fudging && maxScrollOccurences > 0 && scrollOccurences == maxScrollOccurences) {
            LogThis("bumped into maxScrollOccurences");
            stop = true;
        }
    }
}

async function ScrollAndWait() {
    //msgOnSCreenBeforeScroll = msgOnSCreenAfterScroll;
    scrollDiv().scrollTop = 0;
    await delay(delayTime);//let the messages load

    var messages = messagesInChatPane();
    for (var i = messages.length - 1; i > -1; i--) {
        if (messages[i].querySelector('.fui-Divider') != undefined) {
            //we do not want dividers
            LogThis("skipping divider");
            continue;
        }
        var msgId = messages[i].querySelector('[id ^= "timestamp-"]');
        if (msgId == undefined) {
            LogThis("message with no time stamp... ");
            LogThis(messages[i]);
            continue;
        }
        msgId = msgId.id.replace("timestamp-", "");


        if (messagesToDisplay.findIndex(p => p.id === msgId) == -1) {
            LogThis(messagesToDisplay.length + " before adding to messagesToDisplay");
            var en = new entry(messagesToDisplay.length, msgId, messages[i]);
            messagesToDisplay.push(en);
            LogThis(messagesToDisplay.length + " after adding to messagesToDisplay");
        }
    }
}

async function delay(time) {
    return new Promise(resolve => setTimeout(resolve, time));
}

async function PrintIt() {
    LogThis(messagesToDisplay);
    var WinPrint = window.open('', '', 'left=0,top=0,width=800,height=900,toolbar=0,scrollbars=0,status=0');
    for (var i = messagesToDisplay.length - 1; i > -1; i--) {
        var msgDiv = messagesToDisplay[i].element.cloneNode(true);
        msgDiv = RemoveHeaderDiv(msgDiv);
        msgDiv = CleanupEmoji(msgDiv);
        WinPrint.document.body.appendChild(msgDiv);
    }
    //WinPrint.document.close();
    WinPrint.focus();
    //WinPrint.print();
    //WinPrint.close();
}

/*
 * the header contains the message preview
 * useless in what we are trying to do here
 */
function RemoveHeaderDiv(element) {
    var msgHeader = element.querySelector('[role="heading"]');
    if (msgHeader != undefined) {
        msgHeader.remove();
    }
    return element;
}

/* 
 * the css is not maintained in the new browser window, on purpose
 * and it breaks the Emojis
 * this fixes them
 */
function CleanupEmoji(element) {
    element.querySelectorAll('[itemtype="http://schema.skype.com/Emoji"]').forEach(p => p.setAttribute("src", ""))
    return element;
}

function LogThis(messageToLog) {
    if (logToConsole) {
        console.log(messageToLog);
    }
}

function entry(index, id, element) {
    this.index = index;
    this.id = id;
    this.element = element;
}
© www.soinside.com 2019 - 2024. All rights reserved.