Auto Read bee

Linuxdo auto read

Per 24-11-2024. Zie de nieuwste versie.

// ==UserScript==
// @name         Auto Read bee
// @namespace    http://tampermonkey.net/
// @version      1.0.5
// @description  Linuxdo auto read
// @author       bee
// @match        https://meta.discourse.org/*
// @match        https://linux.do/*
// @match        https://meta.appinn.net/*
// @match        https://community.openai.com/
// @grant        none
// @license      MIT
// @icon         https://www.google.com/s2/favicons?domain=linux.do
// ==/UserScript==

//set stop limit
const stopLimit = 350;
const readSuggestPost = false;
const defaultPost = "https://linux.do/t/topic/53589/1501";
const readDelay = 2000; // 滚动检查的间隔(毫秒)
const postCommentsCount = 200;// filter post under this comments, over this will PASS
const BASE_URL = `https://${window.location.hostname}`;
const refreshRate = 400;
const failRetry = 10;
const autoSelectFloor = true;
/**
 * This function set Floor infos value, return how much post been read.
 * @param {boolean} refresh - if true, reset all values
 * @returns {Object} - Returns an object with the result of the operation and the number of posts read.
 */
let lastFloor = null;
let startFloor = null;
let endFloor = null;
let readCounter = 0;
let failCounter = 0;
let directWaitCounter = 0
let readInterval;
let waitNextRefresh = 0;

function setFloor(refresh = false) {
    if (refresh) {
        lastFloor = null;
        startFloor = null;
        endFloor = null;
        failCounter = 0;
        return {result: "refreshed"};
    }
    try {
        let element_floor = document.querySelector('.timeline-replies');
        let floor_numbers = element_floor.textContent.trim().split(' / ');
        let readCountTemp = startFloor - lastFloor;
        if (readCountTemp > 0) {
            failCounter = 0;
        }
        if (lastFloor) {
            readCounter = parseInt(localStorage.getItem("readCounter") || "0", 10);
            readCounter += (readCountTemp < 20 && readCountTemp > 0)? readCountTemp : 0;
            localStorage.setItem("readCounter", readCounter.toString());
            //console.log("add read",startFloor - lastFloor," ==> ", readCounter);
        }
        lastFloor = startFloor;
        startFloor = parseInt(floor_numbers[0].replace(" ", ""));
        endFloor = parseInt(floor_numbers[1].replace(" ", ""));
        return {result: "success", readFloor: readCountTemp};
    } catch (error) {
        return {result: "fail"};
    }
}

// back to last read if exist
/**
 * This function checks if there is a 'last read' button on the page and clicks it if it exists.
 * The 'last read' button is typically used in forums to navigate to the last read post in a thread.
 *
 * @returns {boolean} - Returns true if the 'last read' button was found and clicked, false otherwise.
 */
function checkLastRead() {
    let buttonLastRead = document.querySelector('.timeline-last-read .btn');
    if (buttonLastRead) {
        buttonLastRead.click();
        return true;
    }
    return false;
}


function setBtnText_readToday() {
    let btnAutoRead = document.getElementById("btnAutoRead");
    btnAutoRead.textContent = localStorage.getItem("read") === "true" ? `◼ ( ${readCounter} )` : `▶ ( ${readCounter} )`;
}

function check_read_limit() {
    if (readCounter > stopLimit) {
        document.getElementById('btnAutoRead').click();
        return true;
    }
    return false;
}

// read post
function readPost(postFloor = 1, autoSelect = false) {
    let element = document.querySelectorAll('.boxed.onscreen-post');
    try {
        //find which element.id === post_${postFloor}
        let targetElement = null;
        if (autoSelect) {
            let switchTemp = false;
            let targetFound = false;
            for (let counterTemp = 0; element.length > counterTemp; counterTemp++) {
                if (isInViewport(element[counterTemp]) === false && switchTemp === true) {
                    if (targetFound === false) {
                        targetElement = element[counterTemp];
                        targetFound = true;
                        break;
                    }
                }
                if (isInViewport(element[counterTemp]) === true) {
                    switchTemp = true;
                }
            }
            if (targetElement) {
                scrollIntoBee(targetElement);
                return {result: "success"};
            } else {
                console.log("[WARN] Can't find floor ");
                scrollIntoBee(element[element.length - 1]);
                return {result: "moveBack"};
            }
        }
        element.forEach((e) => {
            if (e.id === `post_${postFloor}`) {
                targetElement = e;
            }
        });
        if (targetElement) {
            scrollIntoBee(targetElement);
            return {result: "success"};
        }
        scrollIntoBee(element[(element.length - 3) > 1 ? (element.length - 3) : element.length]);
        return {result: "success"};
    } catch (e) {
        scrollIntoBee(element[(element.length - 3) > 1 ? (element.length - 3) : 1]);
        console.log("[WARN] Can't find floor ");
        return {result: "fail"};
    }
}

function isInViewport(element) {
    const rect = element.getBoundingClientRect();
    return (rect.top >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight)
        //rect.left >= 0 &&
        //rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        //rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
}

async function directNextPost() {
    if (waitNextRefresh === 0) {
        waitNextRefresh++;
        return;
    }
    waitNextRefresh = 0;

    if (window.location.href.includes('/t/topic/') && readSuggestPost) {
        let suggestPost;
        suggestPost = document.querySelector('a[href="/unread"]');
        if (suggestPost) {
            suggestPost.click();
            console.log("[INFO] found unread ==> ○");
            return;
        }
        console.log("[WARN] unread post ==> X");

        /*suggestPost = document.querySelector('a.badge');//document.querySelector('a.raw-topic-link');
        if (suggestPost) {
            suggestPost.click();
            console.log("[INFO] found suggested post ==> ○");
            return;
        }
        console.log("[WARN] suggest post ==> X");

        suggestPost = document.querySelector('a[href="/new"]');
        if (suggestPost) {
            suggestPost.click();
            console.log("[INFO] found new ==> ○");
            return;
        }
        console.log("[WARN] new post ==> X , back to home.");

        suggestPost = document.querySelector('a[href="/"]');
        if (suggestPost) {
            suggestPost.click();
            console.log("[INFO] found index ==> ○");
            return;
        }*/
    }
    if (window.location.href.includes('/search') && directWaitCounter < 5) {
        let links = document.querySelectorAll('a[href^="/t/topic"]');
        let hrefs;
        if (links && 0 < links.length) {
            directWaitCounter = 0;
            hrefs = Array.from(links, link => link.getAttribute('href'));
            const tempUrl = hrefs.shift();
            localStorage.setItem("unreadList", JSON.stringify(hrefs));
            console.log("[INFO] Got ", hrefs.length, " unread posts");
            gotoUrl(tempUrl);
            return;
        }
        directWaitCounter++;
        console.log("[WARN] Got 0 unread posts");
    }
    if (window.location.href.includes('/search')) {
        directWaitCounter = 0;
        gotoUrl("/ubm");//useBackupMethod
    }
    if (window.location.href === `${BASE_URL}/unread` /*||
        window.location.href === `${BASE_URL}/`*/) {
        let suggestPost = document.querySelector('a[class*="badge badge-notification"]');
        if (suggestPost) {
            console.log("[INFO] found post ==> ○");
            suggestPost.click();
        }
        return;
    }
    /*if (window.location.href === `${BASE_URL}/new`) {
        let suggestPost = document.querySelector('a[href*="/t/topic/"]');
        if (suggestPost) {
            console.log("[INFO] found post ==> ○");
            suggestPost.click();
        }
        return;
    }*/
    let unreadList = JSON.parse(localStorage.getItem("unreadList"));
    if (!unreadList || unreadList.length <= 0) {
        gotoUrl("/search?expanded=true&q=in%3Aunseen%20min_posts%3A20");
        return;
    }
    if (0 < unreadList.length) {
        let tempUrl = unreadList.shift();
        let userUrl = window.location.href.match(/\/t\/topic\/(\d+)?/);
        if (userUrl !== null && userUrl[1] === tempUrl.match(/\/t\/topic\/(\d+)?/)[1]) {
            localStorage.setItem("unreadList", JSON.stringify(unreadList));
            tempUrl = unreadList.shift();
            console.log("[INFO] Delete done post");
        }
        console.log("[INFO] Least ", unreadList.length, " unread posts in storage");
        if (tempUrl) {
            gotoUrl(tempUrl);
        }
        return;
    }

    // backup method
    console.log("[WARN] Use api to get unread posts");
    const topicListStr = localStorage.getItem("topicList");
    if (!topicListStr) {
        await getLatestTopic();
    }
    const topicList = JSON.parse(topicListStr);
    if (topicList && 0 < topicList.length) {
        // 从未读列表中取出第一个
        let topic = topicList.shift();
        if (window.location.href.includes(`/t/topic/${topic.id}`)) {
            localStorage.setItem("topicList", JSON.stringify(topicList));
            topic = topicList.shift();
        }
        //window.location.href = `${BASE_URL}/t/topic/${topic.id}`;
        gotoUrl(`/t/topic/${topic.id}`);
    }

}

function gotoUrl(url) {
    let directPost = document.querySelector('a[href="/"]');
    directPost.href = `${url}`;
    directPost.click();
    directPost.href = "/";
}

function scrollIntoBee(element, marginOffset = 4) {
    const temp_marginTop = (element.style.marginTop === undefined || '') ? '0px' : element.style.marginTop;
    //decrease 130px from temp_marginTop
    element.style.marginTop = `-${marginOffset}rem`;
    element.scrollIntoView({behavior: "smooth"});
    element.style.marginTop = temp_marginTop;
}

// 获取最新文章列表
async function getLatestTopic() {
    let latestPage = 0;
    const latestPageStr = localStorage.getItem("latestPage");
    if (latestPageStr) {
        latestPage = Number(latestPageStr);
    }
    latestPage++;

    const url = `${BASE_URL}/latest.json?no_definitions=true&page=${latestPage}`;
    try {
        const response = await fetch(url);
        const result = await response.json();
        if (result) {
            const topicList = [];
            result.topic_list.topics.forEach((topic) => {
                // 未读以及评论数小于50
                if (!topic.unseen && postCommentsCount > topic.posts_count) {
                    topicList.push(topic);
                }
            });
            localStorage.setItem("latestPage", latestPage.toString());
            localStorage.setItem("topicList", JSON.stringify(topicList));
            console.log(`[INFO] Got ${topicList.length} topics`);
        }
    } catch (error) {
        console.log(`[FAIL] Fail to get topics : `);
        console.error(error);
    }
}

function waitReady() {
    let element = document.querySelector('.loading-indicator-container');
    return element && element.classList.contains('ready');
}

// 打开新的文章
function openNewTopic() {
    const topicListStr = localStorage.getItem("topicList");
    let suggestPost = document.querySelector('a.raw-topic-link');//document.querySelector('.badge.badge-notification.unread-posts');
    if (suggestPost && readSuggestPost) {
        //suggestPost.click();
        window.location.href = suggestPost.getAttribute('href');
    } else if (topicListStr) {
        const topicList = JSON.parse(topicListStr);
        if (topicList && 0 < topicList.length) {
            // 从未读列表中取出第一个
            const topic = topicList.shift();
            localStorage.setItem("topicList", JSON.stringify(topicList));
            window.location.href = `${BASE_URL}/t/topic/${topic.id}`;
        } else {
            // 获取最新文章列表
            getLatestTopic();
            // 打开新的文章
            openNewTopic();
        }
    } else {
        // 获取最新文章列表
        getLatestTopic();
        // 打开新的文章
        openNewTopic();
    }
}

function addStartBtn() {
    //let ul = document.querySelector('.icons.d-header-icons');btn btn-icon-text btn-default sidebar__panel-switch-button
    let ul = document.querySelector('.sidebar-footer-wrapper');
    let li = document.createElement('li');
    li.className = "sidebar-section-link-wrapper";

    let a = document.createElement('a');
    a.id = "startA";
    a.className = "ember-view sidebar-section-link sidebar-row";
    //let a2 = document.createElement('a');
    //a2.id = "directUrl";
    //a2.className = "ember-view sidebar-section-link sidebar-row";
    //a2.href = "";

    let span1 = document.createElement('span');
    span1.className = "sidebar-section-link-prefix icon";

    let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.classList.add("fa", "d-icon", "d-icon-angle-double-right", "svg-icon", "prefix-icon", "svg-string");

    let use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
    use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#angle-double-right');

    let span2 = document.createElement('span');
    span2.className = "sidebar-section-link-content-text";
    span2.id = "btnAutoRead";
    span2.textContent = '▶ ( - )';

    svg.appendChild(use);
    span1.appendChild(svg);
    a.appendChild(span1);
    a.appendChild(span2);
    //li.appendChild(a2);
    li.appendChild(a);

    ul.prepend(li);
    a.onclick = function () {
        const currentlyReading = localStorage.getItem("read") === "true";
        const newReadState = !currentlyReading;
        localStorage.setItem("read", newReadState.toString());
        span2.textContent = newReadState ? `◼ ( ${readCounter} )` : `▶ ( ${readCounter} )`;
        if (newReadState) {
            autoRunStart();
        } else {
            clearInterval(readInterval);
        }
    };
}

function addAutoBtn() {
    //let ul = document.querySelector('.icons.d-header-icons');btn btn-icon-text btn-default sidebar__panel-switch-button
    let ul = document.querySelector('.sidebar-footer-wrapper');
    let li = document.createElement('li');
    li.className = "sidebar-section-link-wrapper";

    let a = document.createElement('a');
    a.id = "startA2";
    a.className = "ember-view sidebar-section-link sidebar-row";
    //let a2 = document.createElement('a');
    //a2.id = "directUrl";
    //a2.className = "ember-view sidebar-section-link sidebar-row";
    //a2.href = "";

    let span1 = document.createElement('span');
    span1.className = "sidebar-section-link-prefix icon";

    let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.classList.add("fa", "d-icon", "d-icon-angle-down", "svg-icon", "prefix-icon", "svg-string");

    let use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
    use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#angle-down');

    let span2 = document.createElement('span');
    span2.className = "sidebar-section-link-content-text";
    span2.id = "btnAutoRead2";
    span2.textContent = '▼';

    svg.appendChild(use);
    span1.appendChild(svg);
    a.appendChild(span1);
    a.appendChild(span2);
    //li.appendChild(a2);
    li.appendChild(a);

    ul.prepend(li);
    a.onclick = function () {
        const currentlyReading = localStorage.getItem("read") === "true";
        const newReadState = !currentlyReading;
        localStorage.setItem("read", newReadState.toString());
        document.getElementById("btnAutoRead").textContent = newReadState ? `◼ ( ${readCounter} )` : `▶ ( ${readCounter} )`;
        if (newReadState) {
            autoRunStart(true);
        } else {
            clearInterval(readInterval);
        }
    };
}

function addCleanBtn() {
    //let ul = document.querySelector('.icons.d-header-icons');
    let ul = document.querySelector('.sidebar-footer-wrapper');
    let li = document.createElement('li');
    li.className = "sidebar-section-link-wrapper";

    let a = document.createElement('a');
    a.id = "cleanA";
    a.className = "ember-view sidebar-section-link sidebar-row";

    let span1 = document.createElement('span');
    span1.className = "sidebar-section-link-prefix icon";

    let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.classList.add("fa", "d-icon", "d-icon-discourse-sparkles", "svg-icon", "prefix-icon", "svg-string");

    let use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
    use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#discourse-sparkles');

    let span2 = document.createElement('span');
    span2.className = "sidebar-section-link-content-text";
    span2.id = "btnAutoReadReset";
    span2.textContent = '✨';

    svg.appendChild(use);
    span1.appendChild(svg);
    a.appendChild(span1);
    a.appendChild(span2);
    li.appendChild(a);

    ul.prepend(li);
    li.onclick = function () {
        readCounter = 0;
        localStorage.setItem("readCounter", readCounter.toString());
        document.getElementById('btnAutoRead').textContent = localStorage.getItem("read") === "true" ? `◼ ( ${readCounter} )` : `▶ ( ${readCounter} )`;
    };
}

function isNewPost() {
    if (localStorage.getItem("readUrl").split("/").slice(0, 6).join("/") !== window.location.href.split("/").slice(0, 6).join("/")) {
        localStorage.setItem("readUrl", window.location.href.split("/").slice(0, 6).join("/"));
        return true;
    }
    return false;
}

function autoRunStart(singleTopic = false) {
    readInterval = setInterval(async () => {
        if (check_read_limit() || !waitReady) {
            return;
        }
        let errorElement = document.querySelector('.page-not-found');
        if (errorElement) {
            console.log("[WARN] Page not found");
            await directNextPost();
            return;
        }
        let retryElement = document.querySelector('.btn.btn-icon-text.btn-primary.topic-retry');
        let retryElement2 = document.querySelector('.error-page');
        if (retryElement) {
            retryElement.click();
            return;
        }
        if (retryElement2) {
            document.querySelector('.btn.btn-icon-text.btn-primary').click();
        }
        if (window.location.href.includes('/t/topic/')) {
            const isNew = isNewPost();
            if (isNew) {
                failCounter = 0;
                console.log("[INFO] New post")
                setFloor(true);
            }
            checkLastRead();
            const existFloor = setFloor();
            if (existFloor.result !== "success") {
                let element = document.querySelectorAll('.boxed.onscreen-post');
                if (element) {
                    if (singleTopic) {
                        document.getElementById('btnAutoRead').click();
                        return;
                    }
                    await directNextPost();
                    console.log("[INFO] Direct post...")
                }
                return;
            }
            setBtnText_readToday();
            if (startFloor === endFloor) {
                if (singleTopic) {
                    document.getElementById('btnAutoRead').click();
                    return;
                }
                await directNextPost();
                console.log("[INFO] Direct post...")
                return;
            }
            if (failCounter < failRetry) {
                if (existFloor.readFloor === 0) {
                    if (endFloor - startFloor < 3) {
                        if (singleTopic) {
                            document.getElementById('btnAutoRead').click();
                            return;
                        }
                        await directNextPost();
                        console.log("[INFO] Direct post...")
                        return;
                    }
                    failCounter++;
                    if (failCounter > failRetry / 2) {
                        console.log("[INFO] +800...");
                        window.scrollBy(0, 800);
                    }
                } else {
                    failCounter = 0;
                    //return;
                }
                //console.log("[INFO] Reading...", existFloor.readFloor === 0 ? startFloor + 3 : startFloor, " ==> ", endFloor);
                let readResult;
                if (failCounter === failRetry - 1) {
                    readResult = readPost((startFloor - 5) > 1 ? (startFloor - 5) : 1);
                } else {
                    readResult = readPost((startFloor + 1) < endFloor ? (startFloor + 1) : endFloor, autoSelectFloor);
                    //console.log("[INFO] try...", (startFloor + 1), " ==> ", failCounter);
                }
                if (readResult.result === "fail") {
                    //failCounter++;
                    console.log("[WARN] Retry read floor : ", startFloor + 1, " ==> ", failCounter);
                    return;
                }
                if (readResult.result === "moveBack" || endFloor - startFloor < 5) {
                    if (singleTopic) {
                        document.getElementById('btnAutoRead').click();
                        return;
                    }
                    await directNextPost();
                    console.log("[INFO] Direct post...")
                    return;
                }
                return;

            }
            window.scrollBy(0, 200);
            return;
        }
        if (singleTopic) {
            document.getElementById('btnAutoRead').click();
            return;
        }
        await directNextPost();
        /*if (window.location.href.includes('/t/topic/')) {
            checkLastRead();
            setFloor(true);
            readPost(1);
        }*/
    }, (refreshRate < 2000 && autoSelectFloor) ? (refreshRate + 2000) : (refreshRate));
}

// 检查是否是第一次运行脚本
function checkFirstRun() {
    if (localStorage.getItem("isFirstRun") === null || localStorage.getItem("readUrl") === null) {
        //init
        console.log("[INFO] Init data");
        localStorage.setItem("isFirstRun", "false");
        localStorage.setItem("read", "false"); // 开始时自动滚动关闭
        localStorage.setItem("autoLikeEnabled", "false"); //默认关闭自动点赞
        localStorage.setItem("unreadList", "[]");
        localStorage.setItem("readUrl", "https://linux.do/t/topic/1");
    }

    addCleanBtn();
    addAutoBtn();
    addStartBtn();
}

// 1. 创建一个函数来处理 article 元素
function handleArticle(articleElement) {
    // 2. 提取 article 的 id 數字
    const postId = parseInt(articleElement.id.replace('post_', ''));

    // 3. 生成文字
    const text = postId + ' F';

    // 4. 在 [[代碼插入處]] 插入文字 (修改部分)
    const avatarDiv = articleElement.querySelector('.topic-avatar');
    if (avatarDiv === null) return;
    const existingText = avatarDiv.querySelector(`div[id='post-${postId}-f']`); // 檢查是否存在新添加的文字

    if (!existingText) { // 如果不存在,才插入
        const newText = document.createElement('div');
        newText.textContent = text;
        newText.id = `post-${postId}-f`; // 添加 ID
        newText.style.textAlign = 'center'; // 設置文字居中
        newText.style.cursor = 'pointer'; // 設置鼠標指針為鏈接樣式

        // 添加鼠標懸停事件
        newText.addEventListener('mouseover', () => {
            newText.style.cursor = 'pointer'; // 設置鼠標指針為鏈接樣式
        });

        // 添加點擊事件
        newText.addEventListener('click', async () => {
            // 檢查按鈕是否已存在
            if (newText.querySelector(`#copyPostIdButton-${postId}`)) return;

            // 創建按鈕
            const copyPostIdButton = document.createElement('button');
            copyPostIdButton.id = `copyPostIdButton-${postId}`;
            copyPostIdButton.className = 'btn btn-icon-text btn-default';
            copyPostIdButton.textContent = 'id';
            copyPostIdButton.addEventListener('click', () => {
                navigator.clipboard.writeText(articleElement.dataset.postId)
                    .then(() => {
                        //console.log('Data-post-id copied to clipboard!');
                        alert('copied !');
                    })
                    .catch(err => {
                        //console.error('Failed to copy: ', err);
                        alert('Failed to copy: ', err);
                    });
            });

            const copyRequestUrlButton = document.createElement('button');
            copyRequestUrlButton.id = `copyRequestUrlButton-${postId}`;
            copyRequestUrlButton.className = 'btn btn-icon-text btn-default';
            copyRequestUrlButton.textContent = 'URL';
            copyRequestUrlButton.addEventListener('click', () => {
                //const requestUrl = `https://linux.do/discourse-reactions/posts/${articleElement.dataset.postId}/reactions-users.json`;
                const requestUrl = `https://linux.do/user_actions.json?limit=2000&username=${articleElement.innerHTML.match(/data-user-card="([^"]+)"/)[1]}&filter=2`;
                navigator.clipboard.writeText(requestUrl)
                    .then(() => {
                        //console.log('Request URL copied to clipboard!');
                        alert('copied !');
                    })
                    .catch(err => {
                        //console.error('Failed to copy: ', err);
                        alert('Failed to copy: ', err);
                    });
            });

            const copyUserListButton = document.createElement('button');
            copyUserListButton.id = `copyUserListButton-${postId}`;
            copyUserListButton.className = 'btn btn-icon-text btn-default';
            copyUserListButton.textContent = 'List';
            copyUserListButton.addEventListener('click', async () => {
                const postId = parseInt(articleElement.dataset.postId);
                const requestUrl1 = `https://linux.do/user_actions.json?limit=2000&username=${articleElement.innerHTML.match(/data-user-card="([^"]+)"/)[1]}&filter=2`;
                const requestUrl2 = `https://linux.do/discourse-reactions/posts/reactions-received.json?username=${articleElement.innerHTML.match(/data-user-card="([^"]+)"/)[1]}`;

                try {
                    const [response1, response2] = await Promise.all([fetch(requestUrl1), fetch(requestUrl2)]);
                    const data1 = await response1.json();
                    const data2 = await response2.json();

                    const userList1 = data1.user_actions
                        .filter(action => action.post_id === postId)
                        .map((action, index) => `${index + 1}. ${action.acting_username}`);

                    const userList2 = data2
                        .filter(item => item.post_id === postId)
                        .map((item, index) => `${userList1.length + index + 1}. ${item.user.username}`);

                    const userList = [...userList1, ...userList2];

                    await navigator.clipboard.writeText(userList.join('\n'));
                    //console.log('User list copied to clipboard!');
                    alert('copied !');
                } catch (err) {
                    //console.error('Failed to copy: ', err);
                    alert('Failed !');
                }
            });
            /*copyUserListButton.addEventListener('click', async () => {
                const requestUrl = `https://linux.do/discourse-reactions/posts/${articleElement.dataset.postId}/reactions-users.json`;
                const response = await fetch(requestUrl);
                const data = await response.json();
                const userList = data.reaction_users
                    .flatMap(reaction => reaction.users)
                    .map((user, index) => `${index + 1}. ${user.username}`);

                navigator.clipboard.writeText(userList.join('\n'))
                    .then(() => {
                        console.log('User list copied to clipboard!');
                    })
                    .catch(err => {
                        console.error('Failed to copy: ', err);
                    });
            });*/

            // 將按鈕添加到 newText 元素
            newText.appendChild(copyPostIdButton);
            newText.appendChild(copyRequestUrlButton);
            newText.appendChild(copyUserListButton);
        });

        avatarDiv.appendChild(newText);
    }
}

function addFloorListener() {
    // 6. 处理现有的 article 元素
    const articleElements = Array.from(document.querySelectorAll('article'))
        .filter(articleElement => articleElement.id !== undefined);

    articleElements.forEach(articleElement => {
        handleArticle(articleElement);
    });

    // 7. 监听新的 article 元素的添加
    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {

            //console.log(mutation);
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach(node => {
                    /*if (node.tagName === 'ARTICLE') {
                      handleArticle(node);
                        console.log("run F");
                    }*/
                    // 6. 处理现有的 article 元素
                    const articleElements = Array.from(document.querySelectorAll('article'))
                        .filter(articleElement => articleElement.id !== undefined);

                    articleElements.forEach(articleElement => {
                        handleArticle(articleElement);
                    });
                });
            }
        });
    });
    observer.observe(document.body, {childList: true, subtree: true});
}

(function () {
    ("use strict");
    checkFirstRun();
    addFloorListener();
})();